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