Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 99
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 / 99
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 / 94
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 = $this->filesystem->makeAbsolute($dependency->getPackageAbsolutePath());
68        $fsDependencyPackageAbsolutePath = $this->filesystem->makeAbsolute($dependencyPackageAbsolutePath);
69
70        $classMapGenerator = new ClassMapGenerator();
71
72        $excluded = null;
73        $autoloadType = 'classmap';
74
75        // Used in Composer `ClassMapGenerator::scanPaths()`.
76        $excludedDirs = array_map(
77            fn(string $path) => $fsDependencyPackageAbsolutePath . '/' . $path,
78            $excludeFromClassmap
79        );
80
81        foreach ($autoloaders as $type => $value) {
82            // Might have to switch/case here.
83
84            /** @var ?string $namespace */
85            $namespace = null;
86
87            switch ($type) {
88                case 'files':
89                    $filesAbsolutePaths = array_map(
90                        fn(string $path) => $dependencyPackageAbsolutePath . '/' . $path,
91                        (array)$value
92                    );
93                    $filesAutoloaderFiles = $this->filesystem->findAllFilesAbsolutePaths($filesAbsolutePaths, true);
94                    foreach ($filesAutoloaderFiles as $filePackageAbsolutePath) {
95                        $filePackageRelativePath = $this->filesystem->getRelativePath(
96                            $dependencyPackageAbsolutePath,
97                            $filePackageAbsolutePath
98                        );
99                        $file = $dependency->getFile(FileSystem::normalizeDirSeparator($filePackageRelativePath));
100                        if (!$file) {
101                            $this->logger->warning("Expected discovered file at {relativePath} not found in package {packageName}", [
102                                'relativePath' => $filePackageRelativePath,
103                                'packageName' => $dependency->getPackageName(),
104                            ]);
105                        } else {
106                            $file->setIsAutoloaded(true);
107                            $file->setDoPrefix(true);
108                        }
109                    }
110                    break;
111                case 'classmap':
112                    $autoloadKeyPaths = array_map(
113                        fn(string $path) => $dependencyPackageAbsolutePath . '/' . ltrim($path, '/'),
114                        (array)$value
115                    );
116                    foreach ($autoloadKeyPaths as $autoloadKeyPath) {
117                        if (!$this->filesystem->exists($autoloadKeyPath)) {
118                            $this->logger->warning(
119                                "Skipping non-existent autoload path in {packageName}: {path}",
120                                ['packageName' => $dependency->getPackageName(), 'path' => $autoloadKeyPath]
121                            );
122                            continue;
123                        }
124                        $classMapGenerator->scanPaths(
125                            $this->filesystem->makeAbsolute($autoloadKeyPath),
126                            $excluded,
127                            $autoloadType,
128                            $namespace,
129                            $excludedDirs,
130                        );
131                    }
132
133                    break;
134                case 'psr-0':
135                case 'psr-4':
136                    foreach ((array)$value as $namespace => $namespaceRelativePaths) {
137                        $psrPaths = array_map(
138                            fn(string $path) => $dependencyPackageAbsolutePath . '/' . ltrim($path, '/'),
139                            (array)$namespaceRelativePaths
140                        );
141
142                        foreach ($psrPaths as $autoloadKeyPath) {
143                            if (!$this->filesystem->exists($autoloadKeyPath)) {
144                                $this->logger->warning(
145                                    "Skipping non-existent autoload path in {packageName}: {path}",
146                                    ['packageName' => $dependency->getPackageName(), 'path' => $autoloadKeyPath]
147                                );
148                                continue;
149                            }
150                            $classMapGenerator->scanPaths(
151                                $this->filesystem->makeAbsolute($autoloadKeyPath),
152                                $excluded,
153                                $autoloadType,
154                                $namespace,
155                                $excludedDirs,
156                            );
157                        }
158                    }
159                    break;
160                default:
161                    $this->logger->warning('Unexpected autoloader type');
162                    // TODO: include everything;
163                    break;
164            }
165        }
166
167        $classMap = $classMapGenerator->getClassMap();
168        $classMapPaths = $classMap->getMap();
169        foreach ($classMapPaths as $fileAbsolutePath) {
170            $relativePath = $this->filesystem->getRelativePath($dependency->getPackageAbsolutePath(), $fileAbsolutePath);
171            $file = $dependency->getFile($relativePath);
172            if (!$file) {
173                $this->logger->warning("Expected discovered file at {relativePath} not found in package {packageName}", [
174                    'relativePath' => $relativePath,
175                    'packageName' => $dependency->getPackageName(),
176                ]);
177            } else {
178                $file->setIsAutoloaded(true);
179                $file->setDoPrefix(true);
180            }
181        }
182    }
183}