Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
DumpAutoload
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 4
56
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 generatedPrefixedAutoloader
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
12
 prefixNewAutoloader
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
2
 getSuffix
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BrianHenryIE\Strauss\Pipeline\Autoload;
4
5use BrianHenryIE\Strauss\Files\File;
6use BrianHenryIE\Strauss\Config\AutoloadConfigInterface;
7use BrianHenryIE\Strauss\Helpers\FileSystem;
8use BrianHenryIE\Strauss\Pipeline\FileEnumerator;
9use BrianHenryIE\Strauss\Pipeline\Prefixer;
10use BrianHenryIE\Strauss\Types\DiscoveredSymbols;
11use BrianHenryIE\Strauss\Types\NamespaceSymbol;
12use Composer\Autoload\AutoloadGenerator;
13use Composer\Config;
14use Composer\Factory;
15use Composer\IO\NullIO;
16use Composer\Json\JsonFile;
17use Composer\Repository\InstalledFilesystemRepository;
18use Psr\Log\LoggerAwareTrait;
19use Psr\Log\LoggerInterface;
20use Psr\Log\NullLogger;
21
22class DumpAutoload
23{
24    use LoggerAwareTrait;
25
26    protected AutoloadConfigInterface $config;
27
28    protected FileSystem $filesystem;
29
30    public function __construct(
31        AutoloadConfigInterface $config,
32        Filesystem $filesystem,
33        ?LoggerInterface $logger = null
34    ) {
35        $this->config = $config;
36        $this->filesystem = $filesystem;
37        $this->setLogger($logger ?? new NullLogger());
38    }
39
40    /**
41     * Uses `vendor/composer/installed.json` to output autoload files to `vendor-prefixed/composer`.
42     */
43    public function generatedPrefixedAutoloader(): void
44    {
45        /**
46         * Unfortunately, `::dump()` creates the target directories if they don't exist, even though it otherwise respects `::setDryRun()`.
47         */
48        if ($this->config->isDryRun()) {
49            return;
50        }
51
52        $relativeTargetDir = $this->filesystem->getRelativePath(
53            $this->config->getProjectDirectory(),
54            $this->config->getTargetDirectory()
55        );
56
57        $defaultVendorDirBefore = Config::$defaultConfig['vendor-dir'];
58
59        Config::$defaultConfig['vendor-dir'] = $relativeTargetDir;
60
61        $composer = Factory::create(new NullIO(), $this->config->getProjectDirectory() . 'composer.json');
62        $installationManager = $composer->getInstallationManager();
63        $package = $composer->getPackage();
64
65        $projectComposerJson = new JsonFile($this->config->getProjectDirectory() . 'composer.json');
66        $projectComposerJsonArray = $projectComposerJson->read();
67        if (isset($projectComposerJsonArray['config'], $projectComposerJsonArray['config']['vendor-dir'])) {
68            unset($projectComposerJsonArray['config']['vendor-dir']);
69        }
70
71        /**
72         * Cannot use `$composer->getConfig()`, need to create a new one so the vendor-dir is correct.
73         */
74        $config = new \Composer\Config(false, $this->config->getProjectDirectory());
75
76        $config->merge([
77            'config' => $projectComposerJsonArray['config'] ?? []
78        ]);
79
80        $generator = new ComposerAutoloadGenerator(
81            $this->config->getNamespacePrefix(),
82            $composer->getEventDispatcher()
83        );
84
85        $generator->setDryRun($this->config->isDryRun());
86        $generator->setClassMapAuthoritative(true);
87        $generator->setRunScripts(false);
88//        $generator->setApcu($apcu, $apcuPrefix);
89//        $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
90        $optimize = true; // $input->getOption('optimize') || $config->get('optimize-autoloader');
91        $generator->setDevMode(false);
92
93        $localRepo = new InstalledFilesystemRepository(new JsonFile($this->config->getTargetDirectory() . 'composer/installed.json'));
94
95        $strictAmbiguous = false; // $input->getOption('strict-ambiguous')
96
97        // This will output the autoload_static.php etc. files to `vendor-prefixed/composer`.
98        $generator->dump(
99            $config,
100            $localRepo,
101            $package,
102            $installationManager,
103            'composer',
104            $optimize,
105            $this->getSuffix(),
106            $composer->getLocker(),
107            $strictAmbiguous
108        );
109
110        /**
111         * Tests fail if this is absent.
112         *
113         * Arguably this should be in ::setUp() and tearDown() in the test classes, but if other tools run after Strauss
114         * then they might expect it to be unmodified.
115         */
116        Config::$defaultConfig['vendor-dir'] = $defaultVendorDirBefore;
117
118        $this->prefixNewAutoloader();
119    }
120
121    protected function prefixNewAutoloader(): void
122    {
123        $this->logger->debug('Prefixing the new Composer autoloader.');
124
125        $projectReplace = new Prefixer(
126            $this->config,
127            $this->filesystem,
128            $this->logger
129        );
130
131        $fileEnumerator = new FileEnumerator(
132            $this->config,
133            $this->filesystem
134        );
135
136        $projectFiles = $fileEnumerator->compileFileListForPaths([
137            $this->config->getTargetDirectory() . 'composer',
138        ]);
139
140        $phpFiles = array_filter(
141            $projectFiles->getFiles(),
142            fn($file) => $file->isPhpFile()
143        );
144
145        $phpFilesAbsolutePaths = array_map(
146            fn($file) => $file->getSourcePath(),
147            $phpFiles
148        );
149
150        $sourceFile = new File(__DIR__);
151        $composerNamespaceSymbol = new NamespaceSymbol(
152            'Composer\\Autoload',
153            $sourceFile
154        );
155        $composerNamespaceSymbol->setReplacement(
156            $this->config->getNamespacePrefix() . '\\Composer\\Autoload'
157        );
158
159        $discoveredSymbols = new DiscoveredSymbols();
160        $discoveredSymbols->add(
161            $composerNamespaceSymbol
162        );
163
164        $projectReplace->replaceInProjectFiles($discoveredSymbols, $phpFilesAbsolutePaths);
165    }
166
167    /**
168     * If there is an existing autoloader, it will use the same suffix. If there is not, it pulls the suffix from
169     * {Composer::getLocker()} and clashes with the existing autoloader.
170     *
171     * @see AutoloadGenerator::dump() 412:431
172     * @see https://github.com/composer/composer/blob/ae208dc1e182bd45d99fcecb956501da212454a1/src/Composer/Autoload/AutoloadGenerator.php#L429
173     */
174    protected function getSuffix(): ?string
175    {
176        return !$this->filesystem->fileExists($this->config->getTargetDirectory() . 'autoload.php')
177            ? bin2hex(random_bytes(16))
178            : null;
179    }
180}