Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
AutoloadedFilesEnumerator
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 3
420
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
 scanForAutoloadedFiles
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 scanPackage
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 1
306
1<?php
2/**
3 * Use each package's autoload key to determine which files in the package are to be prefixed, apply exclusion rules.
4 */
5
6namespace BrianHenryIE\Strauss\Pipeline;
7
8use BrianHenryIE\Strauss\Composer\ComposerPackage;
9use BrianHenryIE\Strauss\Config\AutoloadFilesEnumeratorConfigInterface;
10use BrianHenryIE\Strauss\Helpers\FileSystem;
11use Composer\ClassMapGenerator\ClassMapGenerator;
12use League\Flysystem\FilesystemException;
13use Psr\Log\LoggerAwareTrait;
14use Psr\Log\LoggerInterface;
15
16class AutoloadedFilesEnumerator
17{
18    use LoggerAwareTrait;
19
20    protected AutoloadFilesEnumeratorConfigInterface $config;
21    protected FileSystem $filesystem;
22
23    public function __construct(
24        AutoloadFilesEnumeratorConfigInterface $config,
25        FileSystem $filesystem,
26        LoggerInterface $logger
27    ) {
28        $this->config = $config;
29        $this->filesystem = $filesystem;
30        $this->setLogger($logger);
31    }
32
33    /**
34     * @param ComposerPackage[] $dependencies
35     */
36    public function scanForAutoloadedFiles(array $dependencies): void
37    {
38        foreach ($dependencies as $dependency) {
39            $this->scanPackage($dependency);
40        }
41    }
42
43    /**
44     * Read the autoload keys of the dependencies and marks the appropriate files to be prefixed
45     * @throws FilesystemException
46     */
47    protected function scanPackage(ComposerPackage $dependency): void
48    {
49        $this->logger->debug('AutoloadFileEnumerator::scanPackage() {packageName}', [ 'packageName' => $dependency->getPackageName() ]);
50
51        $this->logger->info("Scanning for autoloaded files in package {packageName}", ['packageName' => $dependency->getPackageName()]);
52
53        $dependencyAutoloadKey = $dependency->getAutoload();
54        $excludeFromClassmap = isset($dependencyAutoloadKey['exclude_from_classmap']) ? $dependencyAutoloadKey['exclude_from_classmap'] : [];
55
56        /**
57         * Where $dependency->autoload is ~
58         *
59         * [ "psr-4" => [ "BrianHenryIE\Strauss" => "src" ] ]
60         * Exclude "exclude-from-classmap"
61         * @see https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps
62         */
63        $autoloaders = array_filter($dependencyAutoloadKey, function ($type) {
64            return 'exclude-from-classmap' !== $type;
65        }, ARRAY_FILTER_USE_KEY);
66
67        $dependencyPackageAbsolutePath = $dependency->getPackageAbsolutePath();
68
69        $classMapGenerator = new ClassMapGenerator();
70
71        $excluded = null;
72        $autoloadType = 'classmap';
73
74        $excludedDirs = array_map(
75            fn(string $path) => $dependencyPackageAbsolutePath . '/' . $path,
76            $excludeFromClassmap
77        );
78
79        foreach ($autoloaders as $type => $value) {
80            // Might have to switch/case here.
81
82            /** @var ?string $namespace */
83            $namespace = null;
84
85            switch ($type) {
86                case 'files':
87                    $filesAbsolutePaths = array_map(
88                        fn(string $path) => $dependencyPackageAbsolutePath . '/' . $path,
89                        (array)$value
90                    );
91                    $filesAutoloaderFiles = $this->filesystem->findAllFilesAbsolutePaths($filesAbsolutePaths, true);
92                    foreach ($filesAutoloaderFiles as $filePackageAbsolutePath) {
93                        $filePackageRelativePath = $this->filesystem->getRelativePath(
94                            $dependencyPackageAbsolutePath,
95                            $filePackageAbsolutePath
96                        );
97                        $file = $dependency->getFile($filePackageRelativePath);
98                        if (!$file) {
99                            $this->logger->warning("Expected discovered file at {relativePath} not found in package {packageName}", [
100                                'relativePath' => $filePackageRelativePath,
101                                'packageName' => $dependency->getPackageName(),
102                            ]);
103                        } else {
104                            $file->setIsAutoloaded(true);
105                            $file->setDoPrefix(true);
106                        }
107                    }
108                    break;
109                case 'classmap':
110                    $autoloadKeyPaths = array_map(
111                        fn(string $path) =>
112                            $this->filesystem->makeAbsolute(
113                                $dependencyPackageAbsolutePath . '/' . ltrim($path, '/')
114                            ),
115                        (array)$value
116                    );
117                    foreach ($autoloadKeyPaths as $autoloadKeyPath) {
118                        if (!$this->filesystem->exists($autoloadKeyPath)) {
119                            $this->logger->warning(
120                                "Skipping non-existent autoload path in {packageName}: {path}",
121                                ['packageName' => $dependency->getPackageName(), 'path' => $autoloadKeyPath]
122                            );
123                            continue;
124                        }
125                        $classMapGenerator->scanPaths(
126                            $autoloadKeyPath,
127                            $excluded,
128                            $autoloadType,
129                            $namespace,
130                            $excludedDirs,
131                        );
132                    }
133
134                    break;
135                case 'psr-0':
136                case 'psr-4':
137                    foreach ((array)$value as $namespace => $namespaceRelativePaths) {
138                        $psrPaths = array_map(
139                            fn(string $path) => $dependencyPackageAbsolutePath . '/' . ltrim($path, '/'),
140                            (array)$namespaceRelativePaths
141                        );
142
143                        foreach ($psrPaths as $autoloadKeyPath) {
144                            if (!$this->filesystem->exists($autoloadKeyPath)) {
145                                $this->logger->warning(
146                                    "Skipping non-existent autoload path in {packageName}: {path}",
147                                    ['packageName' => $dependency->getPackageName(), 'path' => $autoloadKeyPath]
148                                );
149                                continue;
150                            }
151                            $classMapGenerator->scanPaths(
152                                $autoloadKeyPath,
153                                $excluded,
154                                $autoloadType,
155                                $namespace,
156                                $excludedDirs,
157                            );
158                        }
159                    }
160                    break;
161                default:
162                    $this->logger->info('Unexpected autoloader type');
163                    // TODO: include everything;
164                    break;
165            }
166        }
167
168        $classMap = $classMapGenerator->getClassMap();
169        $classMapPaths = $classMap->getMap();
170        foreach ($classMapPaths as $fileAbsolutePath) {
171            $relativePath = $this->filesystem->getRelativePath($dependency->getPackageAbsolutePath(), $fileAbsolutePath);
172            $file = $dependency->getFile($relativePath);
173            if (!$file) {
174                $this->logger->warning("Expected discovered file at {relativePath} not found in package {packageName}", [
175                    'relativePath' => $relativePath,
176                    'packageName' => $dependency->getPackageName(),
177                ]);
178            } else {
179                $file->setIsAutoloaded(true);
180                $file->setDoPrefix(true);
181            }
182        }
183    }
184}