Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
26.52% covered (danger)
26.52%
96 / 362
4.55% covered (danger)
4.55%
1 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
DependenciesCommand
26.52% covered (danger)
26.52%
96 / 362
4.55% covered (danger)
4.55%
1 / 22
1346.05
0.00% covered (danger)
0.00%
0 / 1
 configure
100.00% covered (success)
100.00%
69 / 69
100.00% covered (success)
100.00%
1 / 1
3
 getIOLogger
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
17.33
 getReadOnlyFileSystem
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 execute
27.59% covered (danger)
27.59%
8 / 29
0.00% covered (danger)
0.00%
0 / 1
3.52
 loadProjectComposerPackage
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
2.00
 loadConfigFromComposerJson
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 updateConfigFromCli
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 buildDependencyList
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
56
 enumerateFiles
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 enumeratePsr4Namespaces
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 enumerateAutoloadedFiles
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 scanFilesForSymbols
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 markSymbolsForRenaming
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 determineChanges
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 analyseFilesToCopy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 copyFiles
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
20
 performReplacements
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 performReplacementsInProjectFiles
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
6
 addLicenses
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 generateAutoloader
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
30
 generateAliasesFile
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 cleanUp
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace BrianHenryIE\Strauss\Console\Commands;
4
5use BrianHenryIE\Strauss\Composer\ComposerPackage;
6use BrianHenryIE\Strauss\Composer\ProjectComposerPackage;
7use BrianHenryIE\Strauss\Files\DiscoveredFiles;
8use BrianHenryIE\Strauss\Files\File;
9use BrianHenryIE\Strauss\Helpers\FileSystem;
10use BrianHenryIE\Strauss\Helpers\ReadOnlyFileSystem;
11use BrianHenryIE\Strauss\Helpers\SymlinkProtectFilesystemAdapter;
12use BrianHenryIE\Strauss\Pipeline\Aliases\Aliases;
13use BrianHenryIE\Strauss\Pipeline\Autoload;
14use BrianHenryIE\Strauss\Pipeline\Autoload\VendorComposerAutoload;
15use BrianHenryIE\Strauss\Pipeline\AutoloadedFilesEnumerator;
16use BrianHenryIE\Strauss\Pipeline\ChangeEnumerator;
17use BrianHenryIE\Strauss\Pipeline\Cleanup\Cleanup;
18use BrianHenryIE\Strauss\Pipeline\Cleanup\InstalledJson;
19use BrianHenryIE\Strauss\Pipeline\Copier;
20use BrianHenryIE\Strauss\Pipeline\DependenciesEnumerator;
21use BrianHenryIE\Strauss\Pipeline\FileCopyScanner;
22use BrianHenryIE\Strauss\Pipeline\FileEnumerator;
23use BrianHenryIE\Strauss\Pipeline\FileSymbolScanner;
24use BrianHenryIE\Strauss\Pipeline\Licenser;
25use BrianHenryIE\Strauss\Pipeline\MarkSymbolsForRenaming;
26use BrianHenryIE\Strauss\Pipeline\Prefixer;
27use BrianHenryIE\Strauss\Types\DiscoveredSymbols;
28use BrianHenryIE\Strauss\Types\NamespaceSymbol;
29use Composer\Factory;
30use Composer\InstalledVersions;
31use Elazar\Flystream\FilesystemRegistry;
32use Elazar\Flystream\ServiceLocator;
33use Elazar\Flystream\StripProtocolPathNormalizer;
34use Exception;
35use League\Flysystem\Config;
36use BrianHenryIE\Strauss\Helpers\PathPrefixer;
37use League\Flysystem\WhitespacePathNormalizer;
38use Psr\Log\LoggerInterface;
39use Psr\Log\LogLevel;
40use Psr\Log\NullLogger;
41use Psr\Log\Test\TestLogger;
42use Symfony\Component\Console\Command\Command;
43use Symfony\Component\Console\Input\InputArgument;
44use Symfony\Component\Console\Input\InputInterface;
45use Symfony\Component\Console\Logger\ConsoleLogger;
46use Symfony\Component\Console\Output\OutputInterface;
47
48class DependenciesCommand extends AbstractRenamespacerCommand
49{
50    /** @var Prefixer */
51    protected Prefixer $replacer;
52
53    protected DependenciesEnumerator $dependenciesEnumerator;
54
55    /** @var array<string,ComposerPackage> */
56    protected array $flatDependencyTree = [];
57
58    /**
59     * ArrayAccess of \BrianHenryIE\Strauss\File objects indexed by their path relative to the output target directory.
60     *
61     * Each object contains the file's relative and absolute paths, the package and autoloaders it came from,
62     * and flags indicating should it / has it been copied / deleted etc.
63     *
64     */
65    protected DiscoveredFiles $discoveredFiles;
66    protected DiscoveredSymbols $discoveredSymbols;
67
68    /**
69     * Set name and description, add CLI arguments, call parent class to add dry-run, verbosity options.
70     *
71     * @used-by \Symfony\Component\Console\Command\Command::__construct
72     * @override {@see \Symfony\Component\Console\Command\Command::configure()} empty method.
73     *
74     * @return void
75     */
76    protected function configure()
77    {
78        $this->setName('dependencies');
79        $this->setDescription("Copy composer's `require` and prefix their namespace and classnames.");
80        $this->setHelp('');
81
82        $this->addOption(
83            'updateCallSites',
84            null,
85            InputArgument::OPTIONAL,
86            'Should replacements also be performed in project files? true|list,of,paths|false'
87        );
88
89        $this->addOption(
90            'deleteVendorPackages',
91            null,
92            4,
93            'Should original packages be deleted after copying? true|false',
94            false
95        );
96        // Is there a nicer way to add aliases?
97        $this->addOption(
98            'delete_vendor_packages',
99            null,
100            4,
101            '',
102            false
103        );
104
105        $this->addOption(
106            'dry-run',
107            null,
108            4,
109            'Do not actually make any changes',
110            false
111        );
112
113        $this->addOption(
114            'info',
115            null,
116            4,
117            'output level',
118            false
119        );
120
121        $this->addOption(
122            'debug',
123            null,
124            4,
125            'output level',
126            false
127        );
128
129        if (version_compare(InstalledVersions::getVersion('symfony/console'), '7.2', '<')) {
130            $this->addOption(
131                'silent',
132                's',
133                4,
134                'output level',
135                false
136            );
137        }
138
139        $localFilesystemLocation = PHP_OS_FAMILY === 'Windows' ? substr(getcwd(), 0, 3) : '/';
140
141        $pathPrefixer = new PathPrefixer($localFilesystemLocation, DIRECTORY_SEPARATOR);
142
143        $symlinkProtectFilesystemAdapter = new SymlinkProtectFilesystemAdapter(
144            $localFilesystemLocation,
145            null,
146            $pathPrefixer,
147            $this->logger
148        );
149
150        $this->filesystem = new Filesystem(
151            $symlinkProtectFilesystemAdapter,
152            [
153                Config::OPTION_DIRECTORY_VISIBILITY => 'public',
154            ],
155            null,
156            $pathPrefixer
157        );
158
159        parent::configure();
160    }
161
162    /**
163     * @param InputInterface $input The command line input to check for `--debug`, `--silent` etc.
164     * @param OutputInterface $output The Symfony object that actually prints the messages.
165     */
166    protected function getIOLogger(InputInterface $input, OutputInterface $output): LoggerInterface
167    {
168        $isDryRun = isset($this->config) && $this->config->isDryRun();
169
170        // Who would want to dry-run without output?
171        if (!$isDryRun && $input->hasOption('silent') && $input->getOption('silent') !== false) {
172            return new NullLogger();
173        }
174
175        $logLevel = [LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL];
176
177        if ($input->hasOption('info') && $input->getOption('info') !== false) {
178            $logLevel[LogLevel::INFO]= OutputInterface::VERBOSITY_NORMAL;
179        }
180
181        if ($isDryRun || ($input->hasOption('debug') && $input->getOption('debug') !== false)) {
182            $logLevel[LogLevel::INFO]= OutputInterface::VERBOSITY_NORMAL;
183            $logLevel[LogLevel::DEBUG]= OutputInterface::VERBOSITY_NORMAL;
184        }
185
186        return isset($this->logger) && $this->logger instanceof TestLogger
187            ? $this->logger
188            : new ConsoleLogger($output, $logLevel);
189    }
190
191    protected function getReadOnlyFileSystem(FileSystem $filesystem): FileSystem
192    {
193        $normalizer = new WhitespacePathNormalizer();
194        $normalizer = new StripProtocolPathNormalizer(['mem'], $normalizer);
195
196        $pathPrefixer = new PathPrefixer('mem://', '/');
197
198        $this->filesystem =
199            new FileSystem(
200                new ReadOnlyFileSystem(
201                    $this->filesystem->getAdapter(),
202                ),
203                [],
204                $normalizer,
205                $pathPrefixer
206            );
207
208        /**
209         * Register a file stream mem:// to handle file operations by third party libraries.
210         *
211         * @var FilesystemRegistry $registry
212         */
213        $registry = ServiceLocator::get(FilesystemRegistry::class);
214        $registry->register('mem', $this->filesystem);
215
216        return $filesystem;
217    }
218
219    /**
220     * @param InputInterface $input
221     * @param OutputInterface $output
222     *
223     * @return int
224     * @see Command::execute()
225     *
226     */
227    protected function execute(InputInterface $input, OutputInterface $output): int
228    {
229        $this->setLogger($this->getIOLogger($input, $output));
230
231        $workingDir       = getcwd() . '/';
232        $this->workingDir = $workingDir;
233
234        try {
235            $this->logger->notice('Starting... '/** version */); // + PHP version
236
237            $this->loadProjectComposerPackage();
238            $this->loadConfigFromComposerJson();
239            $this->updateConfigFromCli($input);
240
241            parent::execute($input, $output);
242
243            $this->buildDependencyList();
244
245            $this->enumerateFiles();
246
247            $this->discoveredSymbols = new DiscoveredSymbols();
248
249            $this->enumeratePsr4Namespaces();
250            $this->enumerateAutoloadedFiles();
251            $this->scanFilesForSymbols();
252            $this->analyseFilesToCopy();
253            $this->markSymbolsForRenaming();
254            $this->determineChanges();
255            $this->copyFiles();
256
257            $this->performReplacements();
258
259            $this->performReplacementsInProjectFiles();
260
261            $this->addLicenses();
262
263            $this->cleanUp();
264
265            $this->generateAutoloader();
266
267            // After files have been deleted, we may need aliases.
268            $this->generateAliasesFile();
269
270            $this->logger->notice('Done');
271        } catch (Exception $e) {
272            $this->logger->error($e->getMessage());
273            return Command::FAILURE;
274        }
275
276        return Command::SUCCESS;
277    }
278
279    /**
280     * Load the project's composer package using the current working directory.
281     *
282     * @throws Exception
283     */
284    protected function loadProjectComposerPackage(): void
285    {
286        $this->logger->notice('Loading package...');
287
288        $composerFilePath = $this->filesystem->makeAbsolute(
289            $this->filesystem->normalizePath(
290                $this->workingDir . '/' . Factory::getComposerFile()
291            )
292        );
293        $defaultComposerFilePath = $this->filesystem->makeAbsolute($this->workingDir . '/composer.json');
294        if ($composerFilePath !== $defaultComposerFilePath) {
295            $this->logger->info('Using: ' . $composerFilePath);
296        }
297
298        $composerFilePath = $this->filesystem->normalizePath($composerFilePath);
299        $composerFilePath = $this->filesystem->makeAbsolute($composerFilePath);
300        $this->projectComposerPackage = new ProjectComposerPackage($composerFilePath);
301
302        // TODO: Print the config that Strauss is using.
303        // Maybe even highlight what is default config and what is custom config.
304    }
305
306    /**
307     * Load Strauss config from the project's composer.json.
308     */
309    protected function loadConfigFromComposerJson(): void
310    {
311        $this->logger->notice('Loading composer.json config...');
312
313        $this->config = $this->projectComposerPackage->getStraussConfig();
314    }
315
316    protected function updateConfigFromCli(InputInterface $input): void
317    {
318        $this->logger->notice('Loading cli config...');
319
320        $this->config->updateFromCli($input);
321    }
322
323    /**
324     * 2. Built flat list of packages and dependencies.
325     *
326     * 2.1 Initiate getting dependencies for the project composer.json.
327     *
328     * @see DependenciesCommand::flatDependencyTree
329     */
330    protected function buildDependencyList(): void
331    {
332        $this->logger->notice('Building dependency list...');
333
334        $this->dependenciesEnumerator = new DependenciesEnumerator(
335            $this->config,
336            $this->filesystem,
337            $this->logger
338        );
339        $this->flatDependencyTree = $this->dependenciesEnumerator->getAllDependencies();
340
341        $this->config->setPackagesToCopy(
342            array_filter($this->flatDependencyTree, function ($dependency) {
343                return !in_array($dependency, $this->config->getExcludePackagesFromCopy());
344            },
345            ARRAY_FILTER_USE_KEY)
346        );
347
348        $this->config->setPackagesToPrefix(
349            array_filter($this->flatDependencyTree, function ($dependency) {
350                return !in_array($dependency, $this->config->getExcludePackagesFromPrefixing());
351            },
352            ARRAY_FILTER_USE_KEY)
353        );
354
355        foreach ($this->flatDependencyTree as $dependency) {
356            // Sort of duplicating the logic above.
357            $dependency->setCopy(
358                !in_array($dependency->getPackageName(), $this->config->getExcludePackagesFromCopy())
359            );
360
361            if ($this->config->isDeleteVendorPackages()) {
362                $dependency->setDelete(true);
363            }
364        }
365
366        // TODO: Print the dependency tree that Strauss has determined.
367
368        $symlinkedDependencies = array_filter($this->flatDependencyTree, fn ($dependency) => $dependency->getPackageAbsolutePath() !== $dependency->getRealPath());
369
370        if (!empty($symlinkedDependencies) &&
371            ($this->config->isDeleteVendorFiles() || ($this->config->getAbsoluteTargetDirectory() === $this->config->getAbsoluteVendorDirectory()))
372        ) {
373            $list = implode(
374                ', ',
375                array_map(
376                    fn($dependency) => $dependency->getPackageName(),
377                    $symlinkedDependencies
378                )
379            );
380            $this->logger->error(
381                sprintf(
382                    'Symlinked package%s detected: %s',
383                    count($symlinkedDependencies) ? 's' : '',
384                    $list
385                )
386            );
387            // https://stackoverflow.com/a/65009324/336146
388            $this->logger->notice('Use `COMPOSER_MIRROR_PATH_REPOS=1 composer install` to copy symlinked packages to vendor directory.');
389            throw new Exception();
390        }
391    }
392
393
394    protected function enumerateFiles(): void
395    {
396        $this->logger->notice('Enumerating files...');
397
398        $fileEnumerator = new FileEnumerator(
399            $this->config,
400            $this->filesystem,
401            $this->logger
402        );
403
404        $this->discoveredFiles = $fileEnumerator->compileFileListForDependencies($this->flatDependencyTree);
405    }
406
407    /**
408     * TODO: currently this must run after ::determineChanges() so the discoveredSymbols object exists,
409     * but logically it should run first.
410     */
411    protected function enumeratePsr4Namespaces(): void
412    {
413        foreach ($this->config->getPackagesToPrefix() as $package) {
414            $autoloadKey = $package->getAutoload();
415            if (! isset($autoloadKey['psr-4'])) {
416                continue;
417            }
418
419            $psr4autoloadKey = $autoloadKey['psr-4'];
420            $namespaces = array_keys($psr4autoloadKey);
421
422            $file = new File(
423                $package->getPackageAbsolutePath() . '/composer.json',
424                '/../composer.json',
425                $package->getPackageAbsolutePath() . '/composer.json',
426            );
427
428            foreach ($namespaces as $namespace) {
429                // TODO: log.
430                $symbol = new NamespaceSymbol(
431                    trim($namespace, '\\'),
432                    $file,
433                    '\\',
434                    $package
435                );
436                // TODO: respect all config options.
437//              $symbol->setReplacement($this->config->getNamespacePrefix() . '\\' . trim($namespace, '\\'));
438                $this->discoveredSymbols->add($symbol);
439            }
440        }
441    }
442
443    protected function enumerateAutoloadedFiles(): void
444    {
445        $this->logger->notice('Enumerating autoload files...');
446
447        $autoloadFilesEnumerator = new AutoloadedFilesEnumerator(
448            $this->config,
449            $this->filesystem,
450            $this->logger
451        );
452        $autoloadFilesEnumerator->scanForAutoloadedFiles($this->flatDependencyTree);
453    }
454
455    protected function scanFilesForSymbols(): void
456    {
457        $this->logger->notice('Scanning files...');
458
459        $fileSymbolScanner = new FileSymbolScanner(
460            $this->config,
461            $this->discoveredSymbols,
462            $this->filesystem,
463            $this->logger
464        );
465
466        $fileSymbolScanner->findInFiles($this->discoveredFiles);
467    }
468
469    protected function markSymbolsForRenaming(): void
470    {
471
472        $markSymbolsForRenaming = new MarkSymbolsForRenaming(
473            $this->config,
474            $this->filesystem,
475            $this->logger
476        );
477
478        $markSymbolsForRenaming->scanSymbols($this->discoveredSymbols);
479    }
480
481    protected function determineChanges(): void
482    {
483        $this->logger->notice('Determining changes...');
484
485        $changeEnumerator = new ChangeEnumerator(
486            $this->config,
487            $this->logger
488        );
489        $changeEnumerator->determineReplacements($this->discoveredSymbols);
490    }
491
492    protected function analyseFilesToCopy(): void
493    {
494        (new FileCopyScanner($this->config, $this->filesystem, $this->logger))->scanFiles($this->discoveredFiles);
495    }
496
497    protected function copyFiles(): void
498    {
499
500        if ($this->config->isTargetDirectoryVendor()) {
501            // Nothing to do.
502            return;
503        }
504
505        $this->logger->notice('Copying files...');
506
507        $copier = new Copier(
508            $this->discoveredFiles,
509            $this->config,
510            $this->filesystem,
511            $this->logger
512        );
513
514
515        $copier->prepareTarget();
516        $copier->copy();
517
518        foreach ($this->flatDependencyTree as $package) {
519            if ($package->isCopy()) {
520                $package->setDidCopy(true);
521            }
522        }
523
524        $installedJson = new InstalledJson(
525            $this->config,
526            $this->filesystem,
527            $this->logger
528        );
529        $installedJson->copyInstalledJson();
530    }
531
532
533    // 5. Update namespaces and class names.
534    // Replace references to updated namespaces and classnames throughout the dependencies.
535    protected function performReplacements(): void
536    {
537        $this->logger->notice('Performing replacements...');
538
539        $this->replacer = new Prefixer(
540            $this->config,
541            $this->filesystem,
542            $this->logger
543        );
544
545        $this->replacer->replaceInFiles(
546            $this->discoveredSymbols,
547            $this->discoveredFiles->getFiles()
548        );
549    }
550
551    protected function performReplacementsInProjectFiles(): void
552    {
553        // TODO: this doesn't do tests?!
554        $relativeCallSitePaths =
555            $this->config->getUpdateCallSites()
556            ?? $this->projectComposerPackage->getFlatAutoloadKey();
557
558        if (empty($relativeCallSitePaths)) {
559            return;
560        }
561
562        $callSitePaths = array_map(
563            fn($path) => $this->workingDir . '/' . $path,
564            $relativeCallSitePaths
565        );
566
567        $projectReplace = new Prefixer(
568            $this->config,
569            $this->filesystem,
570            $this->logger
571        );
572
573        $fileEnumerator = new FileEnumerator(
574            $this->config,
575            $this->filesystem,
576            $this->logger
577        );
578
579        $projectFiles = $fileEnumerator->compileFileListForPaths($callSitePaths);
580
581        $phpFiles = array_filter(
582            $projectFiles->getFiles(),
583            fn($file) => $file->isPhpFile()
584        );
585
586        $phpFilesAbsolutePaths = array_map(
587            fn($file) => $file->getSourcePath(),
588            $phpFiles
589        );
590
591        // TODO: Warn when a file that was specified is not found
592        // $this->logger->warning('Expected file not found from project autoload: ' . $absolutePath);
593
594        $projectReplace->replaceInProjectFiles($this->discoveredSymbols, $phpFilesAbsolutePaths);
595    }
596
597    protected function addLicenses(): void
598    {
599        $this->logger->notice('Adding licenses...');
600
601        $author = $this->projectComposerPackage->getAuthor();
602
603        $dependencies = $this->flatDependencyTree;
604
605        $licenser = new Licenser(
606            $this->config,
607            $dependencies,
608            $author,
609            $this->filesystem,
610            $this->logger
611        );
612
613        $licenser->copyLicenses();
614
615        $modifiedFiles = $this->replacer->getModifiedFiles();
616        $licenser->addInformationToUpdatedFiles($modifiedFiles);
617    }
618
619    /**
620     * 6. Generate autoloader.
621     */
622    protected function generateAutoloader(): void
623    {
624        if (isset($this->projectComposerPackage->getAutoload()['classmap'])
625            && in_array(
626                $this->config->getAbsoluteTargetDirectory(),
627                array_map(
628                    fn(string $entry) => trim($entry, '\\/'),
629                    $this->projectComposerPackage->getAutoload()['classmap']
630                ),
631                true
632            )
633        ) {
634            $this->logger->notice('Skipping autoloader generation as target directory is in Composer classmap. Run `composer dump-autoload`.');
635            return;
636        }
637
638        $this->logger->notice('Generating autoloader...');
639
640        $allFilesAutoloaders = $this->dependenciesEnumerator->getAllFilesAutoloaders();
641        $filesAutoloaders = array();
642        foreach ($allFilesAutoloaders as $packageName => $packageFilesAutoloader) {
643            if (in_array($packageName, $this->config->getExcludePackagesFromCopy())) {
644                continue;
645            }
646            $filesAutoloaders[$packageName] = $packageFilesAutoloader;
647        }
648
649        $classmap = new Autoload(
650            $this->config,
651            $filesAutoloaders,
652            $this->filesystem,
653            $this->logger
654        );
655
656        $classmap->generate($this->flatDependencyTree, $this->discoveredSymbols);
657    }
658
659    /**
660     * When namespaces are prefixed which are used by both require and require-dev dependencies,
661     * the require-dev dependencies need class aliases specified to point to the new class names/namespaces.
662     */
663    protected function generateAliasesFile(): void
664    {
665        if (!$this->config->isCreateAliases()) {
666            return;
667        }
668
669        $this->logger->notice('Generating aliases file...');
670
671        $aliases = new Aliases(
672            $this->config,
673            $this->filesystem,
674            $this->logger
675        );
676        $aliases->writeAliasesFileForSymbols($this->discoveredSymbols);
677
678        $vendorComposerAutoload = new VendorComposerAutoload(
679            $this->config,
680            $this->filesystem,
681            $this->logger
682        );
683        $vendorComposerAutoload->addAliasesFileToComposer();
684        $vendorComposerAutoload->addVendorPrefixedAutoloadToVendorAutoload();
685    }
686
687    /**
688     * 7.
689     * Delete source files if desired.
690     * Delete empty directories in destination.
691     */
692    protected function cleanUp(): void
693    {
694
695        $this->logger->notice('Cleaning up...');
696
697        $cleanup = new Cleanup(
698            $this->config,
699            $this->filesystem,
700            $this->logger
701        );
702
703        // This will check the config to check should it delete or not.
704        $cleanup->deleteFiles($this->flatDependencyTree, $this->discoveredFiles);
705
706        $cleanup->cleanupVendorInstalledJson($this->flatDependencyTree, $this->discoveredSymbols);
707        if ($this->config->isDeleteVendorFiles() || $this->config->isDeleteVendorPackages()) {
708            // Rebuild the autoloader after cleanup.
709            // This is needed because cleanup may have deleted files that were in the autoloader.
710            $cleanup->rebuildVendorAutoloader();
711        }
712    }
713}