Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.25% covered (warning)
77.25%
129 / 167
54.00% covered (warning)
54.00%
27 / 50
CRAP
0.00% covered (danger)
0.00%
0 / 1
StraussConfig
77.25% covered (warning)
77.25%
129 / 167
54.00% covered (warning)
54.00%
27 / 50
245.42
0.00% covered (danger)
0.00%
0 / 1
 __construct
93.83% covered (success)
93.83%
76 / 81
0.00% covered (danger)
0.00%
0 / 1
28.18
 getTargetDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTargetDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVendorDirectory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setVendorDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespacePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 setNamespacePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getClassmapPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setClassmapPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFunctionsPrefix
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setFunctionsPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConstantsPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setConstantsPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUpdateCallSites
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUpdateCallSites
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
7
 setExcludeFromCopy
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getExcludePackagesFromCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeNamespacesFromCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeFilePatternsFromCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setExcludeFromPrefix
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getExcludePackagesFromPrefixing
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setExcludePackagesFromPrefixing
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeNamespacesFromPrefixing
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExcludeFilePatternsFromPrefixing
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOverrideAutoload
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOverrideAutoload
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDeleteVendorFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDeleteVendorPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDeleteVendorFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDeleteVendorPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPackagesToCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPackagesToCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPackagesToPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPackagesToPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isClassmapOutput
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setClassmapOutput
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setExcludePackages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNamespaceReplacementPatterns
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setNamespaceReplacementPatterns
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isIncludeModifiedDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setIncludeModifiedDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isIncludeAuthor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setIncludeAuthor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDryRun
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDryRun
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateFromCli
55.00% covered (warning)
55.00%
11 / 20
0.00% covered (danger)
0.00%
0 / 1
35.50
 isCreateAliases
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 getProjectDirectory
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2/**
3 * The extra/strauss key in composer.json.
4 */
5
6namespace BrianHenryIE\Strauss\Composer\Extra;
7
8use BrianHenryIE\Strauss\Composer\ComposerPackage;
9use BrianHenryIE\Strauss\Config\AliasesConfigInterface;
10use BrianHenryIE\Strauss\Config\AutoloadConfigInterface;
11use BrianHenryIE\Strauss\Config\ChangeEnumeratorConfigInterface;
12use BrianHenryIE\Strauss\Config\CleanupConfigInterface;
13use BrianHenryIE\Strauss\Config\CopierConfigInterface;
14use BrianHenryIE\Strauss\Config\FileCopyScannerConfigInterface;
15use BrianHenryIE\Strauss\Config\FileEnumeratorConfig;
16use BrianHenryIE\Strauss\Config\FileSymbolScannerConfigInterface;
17use BrianHenryIE\Strauss\Config\PrefixerConfigInterface;
18use BrianHenryIE\Strauss\Console\Commands\DependenciesCommand;
19use BrianHenryIE\Strauss\Pipeline\Autoload\DumpAutoload;
20use Composer\Composer;
21use Exception;
22use JsonMapper\JsonMapperFactory;
23use JsonMapper\Middleware\Rename\Rename;
24use Symfony\Component\Console\Input\InputInterface;
25
26class StraussConfig implements
27    AliasesConfigInterface,
28    AutoloadConfigInterface,
29    ChangeEnumeratorConfigInterface,
30    CleanupConfigInterface,
31    CopierConfigInterface,
32    FileSymbolScannerConfigInterface,
33    FileEnumeratorConfig,
34    FileCopyScannerConfigInterface,
35    PrefixerConfigInterface,
36    ReplaceConfigInterface
37{
38    /**
39     * The directory containing `composer.json`. Probably `cwd()`.
40     */
41    protected string $projectDirectory;
42
43    /**
44     * The output directory.
45     */
46    protected string $targetDirectory = 'vendor-prefixed';
47
48    /**
49     * The vendor directory.
50     *
51     * Probably 'vendor/'
52     */
53    protected string $vendorDirectory = 'vendor';
54
55    /**
56     * `namespacePrefix` is the prefix to be given to any namespaces.
57     * Presumably this will take the form `My_Project_Namespace\dep_directory`.
58     *
59     * @link https://www.php-fig.org/psr/psr-4/
60     */
61    protected ?string $namespacePrefix = null;
62
63    /**
64     * @var string
65     */
66    protected ?string $classmapPrefix = null;
67
68    /**
69     * Null to disable. Otherwise, suggested it is all lowercase with a trailing underscore.
70     *
71     * @var string|bool|null
72     */
73    protected $functionsPrefix;
74
75    /**
76     * @var ?string
77     */
78    protected ?string $constantsPrefix = null;
79
80    /**
81     * Should replacements be performed in project files?
82     *
83     * When null, files in the project's `autoload` key are scanned and changes which have been performed on the
84     * vendor packages are reflected in the project files.
85     *
86     * When an array of relative file paths are provided, the files in those directories are updated.
87     *
88     * An empty array disables updating project files.
89     *
90     * @var ?string[]
91     */
92    protected ?array $updateCallSites = array();
93
94    /**
95     * Packages to copy and (maybe) prefix.
96     *
97     * If this is empty, the "requires" list in the project composer.json is used.
98     *
99     * @var string[]
100     */
101    protected array $packages = [];
102
103    /**
104     *
105     * @var array<string,ComposerPackage>
106     */
107    protected array $packagesToCopy = [];
108
109    /**
110     *
111     * @var array<string,ComposerPackage>
112     */
113    protected array $packagesToPrefix = [];
114
115    /**
116     * Back-compatibility with Mozart.
117     *
118     * @var string[]
119     */
120    private array $excludePackages;
121
122    /**
123     * 'exclude_from_copy' in composer/extra config.
124     *
125     * @var array{packages: string[], namespaces: string[], file_patterns: string[]}
126     */
127    protected array $excludeFromCopy = array('file_patterns'=>array(),'namespaces'=>array(),'packages'=>array());
128
129    /**
130     * @var array{packages: string[], namespaces: string[], file_patterns: string[]}
131     */
132    protected array $excludeFromPrefix = array('file_patterns'=>array(),'namespaces'=>array(),'packages'=>array());
133
134    /**
135     * An array of autoload keys to replace packages' existing autoload key.
136     *
137     * e.g. when
138     * * A package has no autoloader
139     * * A package specified both a PSR-4 and a classmap but only needs one
140     * ...
141     *
142     * @var array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}>|array{} $overrideAutoload
143     */
144    protected array $overrideAutoload = [];
145
146    /**
147     * After completing prefixing should the source files be deleted?
148     * This does not affect symlinked directories.
149     */
150    protected bool $deleteVendorFiles = false;
151
152    /**
153     * After completing prefixing should the source packages be deleted?
154     * This does not affect symlinked directories.
155     */
156    protected bool $deleteVendorPackages = false;
157
158    protected bool $classmapOutput;
159
160    /**
161     * A dictionary of regex captures => regex replacements.
162     *
163     * E.g. used to avoid repetition of the plugin vendor name in namespaces.
164     * `"~BrianHenryIE\\\\(.*)~" : "BrianHenryIE\\WC_Cash_App_Gateway\\\\$1"`.
165     *
166     * @var array<string, string> $namespaceReplacementPatterns
167     */
168    protected array $namespaceReplacementPatterns = array();
169
170    /**
171     * Should a modified date be included in the header for modified files?
172     *
173     * @var bool
174     */
175    protected $includeModifiedDate = true;
176
177    /**
178     * Should the author name be included in the header for modified files?
179     *
180     * @var bool
181     */
182    protected $includeAuthor = true;
183
184    /**
185     * Should the changes be printed to console rather than files modified?
186     */
187    protected bool $dryRun = false;
188
189    /**
190     * Read any existing Mozart config.
191     * Overwrite it with any Strauss config.
192     * Provide sensible defaults.
193     *
194     * @param Composer $composer
195     *
196     * @throws Exception
197     */
198    public function __construct(?Composer $composer = null)
199    {
200
201        $configExtraSettings = null;
202
203        // Backwards compatibility with Mozart.
204        if (isset($composer, $composer->getPackage()->getExtra()['mozart'])) {
205            $configExtraSettings = (object)$composer->getPackage()->getExtra()['mozart'];
206
207            // Default setting for Mozart.
208            $this->setDeleteVendorFiles(true);
209        }
210
211        if (isset($composer, $composer->getPackage()->getExtra()['strauss'])) {
212            $configExtraSettings = (object)$composer->getPackage()->getExtra()['strauss'];
213        }
214
215        if (!is_null($configExtraSettings)) {
216            $mapper = (new JsonMapperFactory())->bestFit();
217
218            $rename = new Rename();
219            $rename->addMapping(StraussConfig::class, 'dep_directory', 'targetDirectory');
220            $rename->addMapping(StraussConfig::class, 'dep_namespace', 'namespacePrefix');
221
222            $rename->addMapping(StraussConfig::class, 'exclude_packages', 'excludePackages');
223            $rename->addMapping(StraussConfig::class, 'delete_vendor_files', 'deleteVendorFiles');
224            $rename->addMapping(StraussConfig::class, 'delete_vendor_packages', 'deleteVendorPackages');
225
226            $rename->addMapping(StraussConfig::class, 'exclude_prefix_packages', 'excludePackagesFromPrefixing');
227
228
229            $rename->addMapping(StraussConfig::class, 'function_prefix', 'functionsPrefix');
230
231            $mapper->unshift($rename);
232            $mapper->push(new \JsonMapper\Middleware\CaseConversion(
233                \JsonMapper\Enums\TextNotation::UNDERSCORE(),
234                \JsonMapper\Enums\TextNotation::CAMEL_CASE()
235            ));
236
237            $mapper->mapObject($configExtraSettings, $this);
238        }
239
240        // Defaults.
241        // * Use PSR-4 autoloader key
242        // * Use PSR-0 autoloader key
243        // * Use the package name
244        if (! isset($this->namespacePrefix)) {
245            if (isset($composer, $composer->getPackage()->getAutoload()['psr-4'])) {
246                $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-4']));
247            } elseif (isset($composer, $composer->getPackage()->getAutoload()['psr-0'])) {
248                $this->setNamespacePrefix(array_key_first($composer->getPackage()->getAutoload()['psr-0']));
249            } elseif (isset($composer) && '__root__' !== $composer->getPackage()->getName()) {
250                $packageName = $composer->getPackage()->getName();
251                $namespacePrefix = preg_replace('/[^\w\/]+/', '_', $packageName);
252                $namespacePrefix = str_replace('/', '\\', $namespacePrefix) . '\\';
253                $namespacePrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) {
254                    return strtoupper($match[0]);
255                }, $namespacePrefix);
256                $this->setNamespacePrefix($namespacePrefix);
257            } elseif (isset($this->classmapPrefix)) {
258                $namespacePrefix = rtrim($this->getClassmapPrefix(), '_');
259                $this->setNamespacePrefix($namespacePrefix);
260            }
261        }
262
263        if (! isset($this->classmapPrefix)) {
264            if (isset($composer, $composer->getPackage()->getAutoload()['psr-4'])) {
265                $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-4']);
266                $classmapPrefix = str_replace("\\", "_", $autoloadKey);
267                $this->setClassmapPrefix($classmapPrefix);
268            } elseif (isset($composer, $composer->getPackage()->getAutoload()['psr-0'])) {
269                $autoloadKey = array_key_first($composer->getPackage()->getAutoload()['psr-0']);
270                $classmapPrefix = str_replace("\\", "_", $autoloadKey);
271                $this->setClassmapPrefix($classmapPrefix);
272            } elseif (isset($composer) && '__root__' !== $composer->getPackage()->getName()) {
273                $packageName = $composer->getPackage()->getName();
274                $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $packageName);
275                $classmapPrefix = str_replace('/', '\\', $classmapPrefix);
276                // Uppercase the first letter of each word.
277                $classmapPrefix = preg_replace_callback('/(?<=^|_|\\\\)[a-z]/', function ($match) {
278                    return strtoupper($match[0]);
279                }, $classmapPrefix);
280                $classmapPrefix = str_replace("\\", "_", $classmapPrefix);
281                $this->setClassmapPrefix($classmapPrefix);
282            } elseif (isset($this->namespacePrefix)) {
283                $classmapPrefix = preg_replace('/[^\w\/]+/', '_', $this->getNamespacePrefix());
284                $classmapPrefix = rtrim($classmapPrefix, '_') . '_';
285                $this->setClassmapPrefix($classmapPrefix);
286            }
287        }
288
289//        if (!isset($this->namespacePrefix) || !isset($this->classmapPrefix)) {
290//            throw new Exception('Prefix not set. Please set `namespace_prefix`, `classmap_prefix` in composer.json/extra/strauss.');
291//        }
292
293        if (isset($composer) && empty($this->packages)) {
294            $this->packages = array_map(function (\Composer\Package\Link $element) {
295                return $element->getTarget();
296            }, $composer->getPackage()->getRequires());
297        }
298
299        // If the bool flag for classmapOutput wasn't set in the Json config.
300        if (!isset($this->classmapOutput)) {
301            $this->classmapOutput = true;
302            // Check each autoloader.
303            if (isset($composer)) {
304                foreach ($composer->getPackage()->getAutoload() as $autoload) {
305                    // To see if one of its paths.
306                    foreach ($autoload as $entry) {
307                        $paths = (array) $entry;
308                        foreach ($paths as $path) {
309                            // Matches the target directory.
310                            if (trim($path, '\\/') . '/' === $this->getTargetDirectory()) {
311                                $this->classmapOutput = false;
312                                break 3;
313                            }
314                        }
315                    }
316                }
317            }
318        }
319
320        // TODO: Throw an exception if any regex patterns in config are invalid.
321        // https://stackoverflow.com/questions/4440626/how-can-i-validate-regex
322        // preg_match('~Valid(Regular)Expression~', null) === false);
323
324        if (isset($configExtraSettings, $configExtraSettings->updateCallSites)) {
325            if (true === $configExtraSettings->updateCallSites) {
326                $this->updateCallSites = null;
327            } elseif (false === $configExtraSettings->updateCallSites) {
328                $this->updateCallSites = array();
329            } elseif (is_array($configExtraSettings->updateCallSites)) {
330                $this->updateCallSites = $configExtraSettings->updateCallSites;
331            } else {
332                // uh oh.
333            }
334        }
335    }
336
337    /**
338     * `target_directory` will always be returned without a leading slash and with a trailing slash.
339     *
340     * @return string
341     */
342    public function getTargetDirectory(): string
343    {
344        return $this->getProjectDirectory() . trim($this->targetDirectory, '\\/') . '/';
345    }
346
347    /**
348     * @param string $targetDirectory
349     */
350    public function setTargetDirectory(string $targetDirectory): void
351    {
352        $this->targetDirectory = $targetDirectory;
353    }
354
355    /**
356     * @return string
357     */
358    public function getVendorDirectory(): string
359    {
360        return $this->getProjectDirectory() . trim($this->vendorDirectory, '\\/') . '/';
361    }
362
363    /**
364     * @param string $vendorDirectory
365     */
366    public function setVendorDirectory(string $vendorDirectory): void
367    {
368        $this->vendorDirectory = $vendorDirectory;
369    }
370
371    /**
372     * With no trailing slash and no leading slash.
373     */
374    public function getNamespacePrefix(): ?string
375    {
376        return !isset($this->namespacePrefix) ? null : trim($this->namespacePrefix, '\\');
377    }
378
379    /**
380     * @param string $namespacePrefix
381     */
382    public function setNamespacePrefix(string $namespacePrefix): void
383    {
384        $this->namespacePrefix = $namespacePrefix;
385    }
386
387    /**
388     * @return string
389     */
390    public function getClassmapPrefix(): ?string
391    {
392        return $this->classmapPrefix;
393    }
394
395    /**
396     * @param string $classmapPrefix
397     */
398    public function setClassmapPrefix(string $classmapPrefix): void
399    {
400        $this->classmapPrefix = $classmapPrefix;
401    }
402
403    /**
404     * @return string
405     */
406    public function getFunctionsPrefix(): ?string
407    {
408        if (!isset($this->functionsPrefix)) {
409            return strtolower($this->getClassmapPrefix());
410        }
411        if (empty($this->functionsPrefix)) {
412            return null;
413        }
414
415        return $this->functionsPrefix;
416    }
417
418    /**
419     * @param string|bool|null $functionsPrefix
420     */
421    public function setFunctionsPrefix($functionsPrefix): void
422    {
423        $this->functionsPrefix = $functionsPrefix;
424    }
425
426    /**
427     * @return string
428     */
429    public function getConstantsPrefix(): ?string
430    {
431        return $this->constantsPrefix;
432    }
433
434    /**
435     * @param string $constantsPrefix
436     */
437    public function setConstantsPrefix(string $constantsPrefix): void
438    {
439        $this->constantsPrefix = $constantsPrefix;
440    }
441
442    /**
443     * List of files and directories to update call sites in. Empty to disable. Null infers from the project's autoload key.
444     *
445     * @return string[]|null
446     */
447    public function getUpdateCallSites(): ?array
448    {
449        return $this->updateCallSites;
450    }
451
452    /**
453     * @param string[]|null $updateCallSites
454     */
455    public function setUpdateCallSites($updateCallSites): void
456    {
457        if (is_array($updateCallSites) && count($updateCallSites) === 1 && $updateCallSites[0] === true) {
458            // Setting `null` instructs Strauss to update call sites in the project's autoload key.
459            $this->updateCallSites = null;
460        } elseif (is_array($updateCallSites) && count($updateCallSites) === 1 && $updateCallSites[0] === false) {
461            $this->updateCallSites = array();
462        } else {
463            $this->updateCallSites = $updateCallSites;
464        }
465    }
466
467    /**
468     * @param array{packages?:array<string>, namespaces?:array<string>, file_patterns?:array<string>} $excludeFromCopy
469     */
470    public function setExcludeFromCopy(array $excludeFromCopy): void
471    {
472        foreach (array( 'packages', 'namespaces', 'file_patterns' ) as $key) {
473            if (isset($excludeFromCopy[$key])) {
474                $this->excludeFromCopy[$key] = $excludeFromCopy[$key];
475            }
476        }
477    }
478
479    /**
480     * @return string[]
481     */
482    public function getExcludePackagesFromCopy(): array
483    {
484        return $this->excludeFromCopy['packages'] ?? array();
485    }
486
487    /**
488     * @return string[]
489     */
490    public function getExcludeNamespacesFromCopy(): array
491    {
492        return $this->excludeFromCopy['namespaces'] ?? array();
493    }
494
495    /**
496     * @return string[]
497     */
498    public function getExcludeFilePatternsFromCopy(): array
499    {
500        return $this->excludeFromCopy['file_patterns'] ?? array();
501    }
502
503    /**
504     * @param array{packages?:array<string>, namespaces?:array<string>, file_patterns?:array<string>} $excludeFromPrefix
505     */
506    public function setExcludeFromPrefix(array $excludeFromPrefix): void
507    {
508        if (isset($excludeFromPrefix['packages'])) {
509            $this->excludeFromPrefix['packages'] = $excludeFromPrefix['packages'];
510        }
511        if (isset($excludeFromPrefix['namespaces'])) {
512            $this->excludeFromPrefix['namespaces'] = $excludeFromPrefix['namespaces'];
513        }
514        if (isset($excludeFromPrefix['file_patterns'])) {
515            $this->excludeFromPrefix['file_patterns'] = $excludeFromPrefix['file_patterns'];
516        }
517    }
518
519    /**
520     * When prefixing, do not prefix these packages (which have been copied).
521     *
522     * @return string[]
523     */
524    public function getExcludePackagesFromPrefixing(): array
525    {
526        return $this->excludeFromPrefix['packages'] ?? array();
527    }
528
529    /**
530     * @param string[] $excludePackagesFromPrefixing
531     */
532    public function setExcludePackagesFromPrefixing(array $excludePackagesFromPrefixing): void
533    {
534        $this->excludeFromPrefix['packages'] = $excludePackagesFromPrefixing;
535    }
536
537    /**
538     * @return string[]
539     */
540    public function getExcludeNamespacesFromPrefixing(): array
541    {
542        return $this->excludeFromPrefix['namespaces'] ?? array();
543    }
544
545    /**
546     * @return string[]
547     */
548    public function getExcludeFilePatternsFromPrefixing(): array
549    {
550        return $this->excludeFromPrefix['file_patterns'] ?? array();
551    }
552
553
554    /**
555     * @return array{}|array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}> $overrideAutoload Dictionary of package name: autoload rules.
556     */
557    public function getOverrideAutoload(): array
558    {
559        return $this->overrideAutoload;
560    }
561
562    /**
563     * @param array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}> $overrideAutoload Dictionary of package name: autoload rules.
564     */
565    public function setOverrideAutoload(array $overrideAutoload): void
566    {
567        $this->overrideAutoload = $overrideAutoload;
568    }
569
570    /**
571     * @return bool
572     */
573    public function isDeleteVendorFiles(): bool
574    {
575        return $this->deleteVendorFiles;
576    }
577
578    /**
579     * @return bool
580     */
581    public function isDeleteVendorPackages(): bool
582    {
583        return $this->deleteVendorPackages;
584    }
585
586    /**
587     * @param bool $deleteVendorFiles
588     */
589    public function setDeleteVendorFiles(bool $deleteVendorFiles): void
590    {
591        $this->deleteVendorFiles = $deleteVendorFiles;
592    }
593
594    /**
595     * @param bool $deleteVendorPackages
596     */
597    public function setDeleteVendorPackages(bool $deleteVendorPackages): void
598    {
599        $this->deleteVendorPackages = $deleteVendorPackages;
600    }
601
602    /**
603     * @return string[]
604     */
605    public function getPackages(): array
606    {
607        return $this->packages;
608    }
609
610    /**
611     * @param string[] $packages
612     */
613    public function setPackages(array $packages): void
614    {
615        $this->packages = $packages;
616    }
617
618    /**
619     * @used-by DumpAutoload::createInstalledVersionsFiles()
620     * @return array<string,ComposerPackage>
621     */
622    public function getPackagesToCopy(): array
623    {
624        return $this->packagesToCopy;
625    }
626
627    /**
628     * @used-by DependenciesCommand::buildDependencyList()
629     */
630    public function setPackagesToCopy(array $packagesToCopy): void
631    {
632        $this->packagesToCopy = $packagesToCopy;
633    }
634
635    public function getPackagesToPrefix(): array
636    {
637        return $this->packagesToPrefix;
638    }
639
640    public function setPackagesToPrefix(array $packagesToPrefix): void
641    {
642        $this->packagesToPrefix = $packagesToPrefix;
643    }
644    /**
645     * TODO: Can we name this `isClassmapOutputEnabled`?
646     */
647    public function isClassmapOutput(): bool
648    {
649        return $this->classmapOutput;
650    }
651
652    /**
653     * @param bool $classmapOutput
654     */
655    public function setClassmapOutput(bool $classmapOutput): void
656    {
657        $this->classmapOutput = $classmapOutput;
658    }
659
660    /**
661     * Backwards compatibility with Mozart.
662     *
663     * @param string[] $excludePackages
664     */
665    public function setExcludePackages(array $excludePackages): void
666    {
667        $this->excludeFromPrefix['packages'] = $excludePackages;
668    }
669
670    /**
671     * @return array<string,string>
672     */
673    public function getNamespaceReplacementPatterns(): array
674    {
675        return $this->namespaceReplacementPatterns;
676    }
677
678    /**
679     * @param array<string,string> $namespaceReplacementPatterns
680     */
681    public function setNamespaceReplacementPatterns(array $namespaceReplacementPatterns): void
682    {
683        $this->namespaceReplacementPatterns = $namespaceReplacementPatterns;
684    }
685
686    /**
687     * @return bool
688     */
689    public function isIncludeModifiedDate(): bool
690    {
691        return $this->includeModifiedDate;
692    }
693
694    /**
695     * @param bool $includeModifiedDate
696     */
697    public function setIncludeModifiedDate(bool $includeModifiedDate): void
698    {
699        $this->includeModifiedDate = $includeModifiedDate;
700    }
701
702
703    /**
704     * @return bool
705     */
706    public function isIncludeAuthor(): bool
707    {
708        return $this->includeAuthor;
709    }
710
711    /**
712     * @param bool $includeAuthor
713     */
714    public function setIncludeAuthor(bool $includeAuthor): void
715    {
716        $this->includeAuthor = $includeAuthor;
717    }
718
719    /**
720     * Should expected changes be printed to console rather than files modified?
721     */
722    public function isDryRun(): bool
723    {
724        return $this->dryRun;
725    }
726
727    /**
728     * Disable making changes to files; output changes to console instead.
729     */
730    public function setDryRun(bool $dryRun): void
731    {
732        $this->dryRun = $dryRun;
733    }
734
735    /**
736     * @param InputInterface $input To access the command line options.
737     */
738    public function updateFromCli(InputInterface $input): void
739    {
740
741        // strauss --updateCallSites=false (default)
742        // strauss --updateCallSites=true
743        // strauss --updateCallSites=src,input,extra
744
745        if ($input->hasOption('updateCallSites') && $input->getOption('updateCallSites') !== null) {
746            $updateCallSitesInput = $input->getOption('updateCallSites');
747
748            if ('false' === $updateCallSitesInput) {
749                $this->updateCallSites = array();
750            } elseif ('true' === $updateCallSitesInput) {
751                $this->updateCallSites = null;
752            } elseif (! is_null($updateCallSitesInput)) {
753                $this->updateCallSites = explode(',', $updateCallSitesInput);
754            }
755        }
756
757        if ($input->hasOption('deleteVendorPackages')  && $input->getOption('deleteVendorPackages') !== false) {
758            $isDeleteVendorPackagesCommandLine = $input->getOption('deleteVendorPackages') === 'true'
759                || $input->getOption('deleteVendorPackages') === null;
760            $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine);
761        } elseif ($input->hasOption('delete_vendor_packages') && $input->getOption('delete_vendor_packages') !== false) {
762            $isDeleteVendorPackagesCommandLine = $input->getOption('delete_vendor_packages') === 'true'
763                || $input->getOption('delete_vendor_packages') === null;
764            $this->setDeleteVendorPackages($isDeleteVendorPackagesCommandLine);
765        }
766
767        if ($input->hasOption('dry-run') && $input->getOption('dry-run') !== false) {
768            // If we're here, the parameter was passed in the CLI command.
769            $this->dryRun = empty($input->getOption('dry-run'))
770                ? true
771                : filter_var($input->getOption('dry-run'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
772        }
773    }
774
775    /**
776     * Should we create the `autoload_aliases.php` file in `vendor/composer`?
777     *
778     * TODO:
779     * [x] YES when we are deleting vendor packages or files
780     * [ ] NO when we are running composer install `--no-dev`
781     * [ ] SOMETIMES: see https://github.com/BrianHenryIE/strauss/issues/144
782     * [ ] Add `aliases` to `extra` in `composer.json`
783     * [ ] Add `--aliases=true` CLI option
784     */
785    public function isCreateAliases(): bool
786    {
787        return $this->deleteVendorPackages || $this->deleteVendorFiles || $this->targetDirectory === 'vendor';
788    }
789
790    public function getProjectDirectory(): string
791    {
792        $projectDirectory = $this->projectDirectory ?? getcwd() . '/';
793
794        return $this->isDryRun()
795            ? 'mem:/' . $projectDirectory
796            : $projectDirectory;
797    }
798}