Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
DependenciesEnumerator
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 5
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getAllDependencies
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 recursiveGetAllDependencies
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
72
 getAllFilesAutoloaders
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 removeVirtualPackagesFilter
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Build a list of ComposerPackage objects for all dependencies.
4 */
5
6namespace BrianHenryIE\Strauss;
7
8use BrianHenryIE\Strauss\Composer\ComposerPackage;
9use BrianHenryIE\Strauss\Composer\Extra\StraussConfig;
10use Exception;
11use League\Flysystem\Filesystem;
12use League\Flysystem\Local\LocalFilesystemAdapter;
13
14class DependenciesEnumerator
15{
16    /**
17     * The only path variable with a leading slash.
18     * All directories in project end with a slash.
19     *
20     * @var string
21     */
22    protected string $workingDir;
23
24    /** @var string */
25    protected string $vendorDir;
26
27    /**
28     * @var string[]
29     */
30    protected array $requiredPackageNames;
31
32    /** @var Filesystem */
33    protected Filesystem $filesystem;
34
35    /** @var string[]  */
36    protected array $virtualPackages = array(
37        'php-http/client-implementation'
38    );
39
40    /** @var array<string, ComposerPackage> */
41    protected array $flatDependencyTree = array();
42
43    /**
44     * Record the files autoloaders for later use in building our own autoloader.
45     *
46     * Package-name: [ dir1, file1, file2, ... ].
47     *
48     * @var array<string, string[]>
49     */
50    protected array $filesAutoloaders = [];
51
52    /**
53     * @var array{}|array<string, array{files?:array<string>,classmap?:array<string>,"psr-4":array<string|array<string>>}> $overrideAutoload
54     */
55    protected array $overrideAutoload = array();
56
57    /**
58     * Constructor.
59     *
60     * @param string $workingDir
61     * @param StraussConfig $config
62     */
63    public function __construct(
64        string $workingDir,
65        StraussConfig $config
66    ) {
67        $this->workingDir = $workingDir;
68        $this->vendorDir = $config->getVendorDirectory();
69        $this->overrideAutoload = $config->getOverrideAutoload();
70        $this->requiredPackageNames = $config->getPackages();
71
72        $this->filesystem = new Filesystem(new LocalFilesystemAdapter($this->workingDir));
73    }
74
75    /**
76     * @return array<string, ComposerPackage> Packages indexed by package name.
77     * @throws Exception
78     */
79    public function getAllDependencies(): array
80    {
81        $this->recursiveGetAllDependencies($this->requiredPackageNames);
82        return $this->flatDependencyTree;
83    }
84
85    /**
86     * @param string[] $requiredPackageNames
87     */
88    protected function recursiveGetAllDependencies(array $requiredPackageNames): void
89    {
90        $requiredPackageNames = array_filter($requiredPackageNames, array( $this, 'removeVirtualPackagesFilter' ));
91
92        foreach ($requiredPackageNames as $requiredPackageName) {
93            // Avoid infinite recursion.
94            if (isset($this->flatDependencyTree[$requiredPackageName])) {
95                continue;
96            }
97
98            $packageComposerFile = $this->workingDir . $this->vendorDir
99                                   . $requiredPackageName . DIRECTORY_SEPARATOR . 'composer.json';
100
101            $overrideAutoload = $this->overrideAutoload[ $requiredPackageName ] ?? null;
102
103            if (file_exists($packageComposerFile)) {
104                $requiredComposerPackage = ComposerPackage::fromFile($packageComposerFile, $overrideAutoload);
105            } else {
106                $fileContents           = file_get_contents($this->workingDir . 'composer.lock');
107                if (false === $fileContents) {
108                    throw new Exception('Failed to read contents of ' . $this->workingDir . 'composer.lock');
109                }
110                $composerLock           = json_decode($fileContents, true);
111                $requiredPackageComposerJson = null;
112                foreach ($composerLock['packages'] as $packageJson) {
113                    if ($requiredPackageName === $packageJson['name']) {
114                        $requiredPackageComposerJson = $packageJson;
115                        break;
116                    }
117                }
118                if (is_null($requiredPackageComposerJson)) {
119                    // e.g. composer-plugin-api.
120                    continue;
121                }
122
123                $requiredComposerPackage = ComposerPackage::fromComposerJsonArray($requiredPackageComposerJson, $overrideAutoload);
124            }
125
126            $this->flatDependencyTree[$requiredComposerPackage->getPackageName()] = $requiredComposerPackage;
127            $nextRequiredPackageNames                                             = $requiredComposerPackage->getRequiresNames();
128
129            $this->recursiveGetAllDependencies($nextRequiredPackageNames);
130        }
131    }
132
133    /**
134     * Get the recorded files autoloaders.
135     *
136     * @return array<string, array<string>>
137     */
138    public function getAllFilesAutoloaders(): array
139    {
140        $filesAutoloaders = array();
141        foreach ($this->flatDependencyTree as $packageName => $composerPackage) {
142            if (isset($composerPackage->getAutoload()['files'])) {
143                $filesAutoloaders[$packageName] = $composerPackage->getAutoload()['files'];
144            }
145        }
146        return $filesAutoloaders;
147    }
148
149    /**
150     * Unset PHP, ext-*, ...
151     *
152     * @param string $requiredPackageName
153     */
154    protected function removeVirtualPackagesFilter(string $requiredPackageName): bool
155    {
156        return ! (
157            0 === strpos($requiredPackageName, 'ext')
158            || 'php' === $requiredPackageName
159            || in_array($requiredPackageName, $this->virtualPackages)
160        );
161    }
162}