Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.33% covered (warning)
73.33%
33 / 45
70.59% covered (warning)
70.59%
12 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
ComposerPackage
73.33% covered (warning)
73.33%
33 / 45
70.59% covered (warning)
70.59%
12 / 17
38.82
0.00% covered (danger)
0.00%
0 / 1
 fromFile
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 fromComposerJsonArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
77.27% covered (warning)
77.27%
17 / 22
0.00% covered (danger)
0.00%
0 / 1
9.95
 getPackageName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRelativePath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPackageAbsolutePath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAutoload
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRequiresNames
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getLicense
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCopy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCopy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDidCopy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 didCopy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDoDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDidDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 didDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Object for getting typed values from composer.json.
4 *
5 * Use this for dependencies. Use ProjectComposerPackage for the primary composer.json.
6 */
7
8namespace BrianHenryIE\Strauss\Composer;
9
10use Composer\Composer;
11use Composer\Factory;
12use Composer\IO\NullIO;
13
14/**
15 * @phpstan-type AutoloadKey array{files?:array<string>,classmap?:array<string>,"psr-4"?:array<string,string|array<string>>}
16 */
17class ComposerPackage
18{
19    /**
20     * The composer.json file as parsed by Composer.
21     *
22     * @see \Composer\Factory::create
23     *
24     * @var \Composer\Composer
25     */
26    protected \Composer\Composer $composer;
27
28    /**
29     * The name of the project in composer.json.
30     *
31     * e.g. brianhenryie/my-project
32     *
33     * @var string
34     */
35    protected string $packageName;
36
37    /**
38     * Virtual packages and meta packages do not have a composer.json.
39     * Some packages are installed in a different directory name than their package name.
40     *
41     * @var ?string
42     */
43    protected ?string $relativePath = null;
44
45    /**
46     * Packages can be symlinked from outside the current project directory.
47     *
48     * @var ?string
49     */
50    protected ?string $packageAbsolutePath = null;
51
52    /**
53     * The discovered files, classmap, psr0 and psr4 autoload keys discovered (as parsed by Composer).
54     *
55     * @var AutoloadKey
56     */
57    protected array $autoload = [];
58
59    /**
60     * The names in the composer.json's "requires" field (without versions).
61     *
62     * @var string[]
63     */
64    protected array $requiresNames = [];
65
66    protected string $license;
67
68    /**
69     * Should the package be copied to the vendor-prefixed/target directory? Default: true.
70     */
71    protected bool $isCopy = true;
72    /**
73     * Has the package been copied to the vendor-prefixed/target directory? False until the package is copied.
74     */
75    protected bool $didCopy = false;
76    /**
77     * Should the package be deleted from the vendor directory? Default: false.
78     */
79    protected bool $isDelete = false;
80    /**
81     * Has the package been deleted from the vendor directory? False until the package is deleted.
82     */
83    protected bool $didDelete = false;
84
85    /**
86     * @param string $absolutePath The absolute path to composer.json
87     * @param ?array{files?:array<string>, classmap?:array<string>, psr?:array<string,string|array<string>>} $overrideAutoload Optional configuration to replace the package's own autoload definition with
88     *                                    another which Strauss can use.
89     * @return ComposerPackage
90     */
91    public static function fromFile(string $absolutePath, array $overrideAutoload = null): ComposerPackage
92    {
93        $composer = Factory::create(new NullIO(), $absolutePath, true);
94
95        return new ComposerPackage($composer, $overrideAutoload);
96    }
97
98    /**
99     * This is used for virtual packages, which don't have a composer.json.
100     *
101     * @param array{name?:string, license?:string, requires?:array<string,string>, autoload?:AutoloadKey} $jsonArray composer.json decoded to array
102     * @param ?AutoloadKey $overrideAutoload New autoload rules to replace the existing ones.
103     */
104    public static function fromComposerJsonArray($jsonArray, array $overrideAutoload = null): ComposerPackage
105    {
106        $factory = new Factory();
107        $io = new NullIO();
108        $composer = $factory->createComposer($io, $jsonArray, true);
109
110        return new ComposerPackage($composer, $overrideAutoload);
111    }
112
113    /**
114     * Create a PHP object to represent a composer package.
115     *
116     * @param Composer $composer
117     * @param ?AutoloadKey $overrideAutoload Optional configuration to replace the package's own autoload definition with another which Strauss can use.
118     */
119    public function __construct(Composer $composer, array $overrideAutoload = null)
120    {
121        $this->composer = $composer;
122
123        $this->packageName = $composer->getPackage()->getName();
124
125        $composerJsonFileAbsolute = $composer->getConfig()->getConfigSource()->getName();
126
127        $absolutePath = realpath(dirname($composerJsonFileAbsolute));
128        if (false !== $absolutePath) {
129            $this->packageAbsolutePath = $absolutePath . '/';
130        }
131
132        $vendorDirectory = $this->composer->getConfig()->get('vendor-dir');
133        if (file_exists($vendorDirectory . '/' . $this->packageName)) {
134            $this->relativePath = $this->packageName;
135            $this->packageAbsolutePath = realpath($vendorDirectory . '/' . $this->packageName) . '/';
136        // If the package is symlinked, the path will be outside the working directory.
137        } elseif (0 !== strpos($absolutePath, getcwd()) && 1 === preg_match('/.*[\/\\\\]([^\/\\\\]*[\/\\\\][^\/\\\\]*)[\/\\\\][^\/\\\\]*/', $vendorDirectory, $output_array)) {
138            $this->relativePath = $output_array[1];
139        } elseif (1 === preg_match('/.*[\/\\\\]([^\/\\\\]+[\/\\\\][^\/\\\\]+)[\/\\\\]composer.json/', $composerJsonFileAbsolute, $output_array)) {
140        // Not every package gets installed to a folder matching its name (crewlabs/unsplash).
141            $this->relativePath = $output_array[1];
142        }
143
144        if (!is_null($overrideAutoload)) {
145            $composer->getPackage()->setAutoload($overrideAutoload);
146        }
147
148        $this->autoload = $composer->getPackage()->getAutoload();
149
150        foreach ($composer->getPackage()->getRequires() as $_name => $packageLink) {
151            $this->requiresNames[] = $packageLink->getTarget();
152        }
153
154        // Try to get the license from the package's composer.json, assume proprietary (all rights reserved!).
155        $this->license = !empty($composer->getPackage()->getLicense())
156            ? implode(',', $composer->getPackage()->getLicense())
157            : 'proprietary?';
158    }
159
160    /**
161     * Composer package project name.
162     *
163     * vendor/project-name
164     *
165     * @return string
166     */
167    public function getPackageName(): string
168    {
169        return $this->packageName;
170    }
171
172    /**
173     * Is this relative to vendor?
174     */
175    public function getRelativePath(): ?string
176    {
177        return $this->relativePath . '/';
178    }
179
180    public function getPackageAbsolutePath(): ?string
181    {
182        return $this->packageAbsolutePath;
183    }
184
185    /**
186     *
187     * e.g. ['psr-4' => [ 'BrianHenryIE\Project' => 'src' ]]
188     * e.g. ['psr-4' => [ 'BrianHenryIE\Project' => ['src','lib] ]]
189     * e.g. ['classmap' => [ 'src', 'lib' ]]
190     * e.g. ['files' => [ 'lib', 'functions.php' ]]
191     *
192     * @return AutoloadKey
193     */
194    public function getAutoload(): array
195    {
196        return $this->autoload;
197    }
198
199    /**
200     * The names of the packages in the composer.json's "requires" field (without version).
201     *
202     * Excludes PHP, ext-*, since we won't be copying or prefixing them.
203     *
204     * @return string[]
205     */
206    public function getRequiresNames(): array
207    {
208        // Unset PHP, ext-*.
209        $removePhpExt = function ($element) {
210            return !( 0 === strpos($element, 'ext') || 'php' === $element );
211        };
212
213        return array_filter($this->requiresNames, $removePhpExt);
214    }
215
216    public function getLicense():string
217    {
218        return $this->license;
219    }
220
221    /**
222     * Should the file be copied? (defaults to yes)
223     */
224    public function setCopy(bool $isCopy): void
225    {
226        $this->isCopy = $isCopy;
227    }
228
229    /**
230     * Should the file be copied? (defaults to yes)
231     */
232    public function isCopy(): bool
233    {
234        return $this->isCopy;
235    }
236
237    /**
238     * Has the file been copied? (defaults to no)
239     */
240    public function setDidCopy(bool $didCopy): void
241    {
242        $this->didCopy = $didCopy;
243    }
244
245    /**
246     * Has the file been copied? (defaults to no)
247     */
248    public function didCopy(): bool
249    {
250        return $this->didCopy;
251    }
252
253    /**
254     * Should the file be deleted? (defaults to no)
255     */
256    public function setDelete(bool $isDelete): void
257    {
258        $this->isDelete = $isDelete;
259    }
260
261    /**
262     * Should the file be deleted? (defaults to no)
263     */
264    public function isDoDelete(): bool
265    {
266        return $this->isDelete;
267    }
268
269    /**
270     * Has the file been deleted? (defaults to no)
271     */
272    public function setDidDelete(bool $didDelete): void
273    {
274        $this->didDelete = $didDelete;
275    }
276
277    /**
278     * Has the file been deleted? (defaults to no)
279     */
280    public function didDelete(): bool
281    {
282        return $this->didDelete;
283    }
284}