Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.26% covered (warning)
58.26%
127 / 218
36.36% covered (danger)
36.36%
4 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
InstalledJson
58.26% covered (warning)
58.26%
127 / 218
36.36% covered (danger)
36.36%
4 / 11
331.65
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 copyInstalledJson
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 getJsonFile
66.67% covered (warning)
66.67%
10 / 15
0.00% covered (danger)
0.00%
0 / 1
2.15
 updatePackagePaths
36.36% covered (danger)
36.36%
8 / 22
0.00% covered (danger)
0.00%
0 / 1
15.28
 pathExistsInPackage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 removeMissingAutoloadKeyPaths
31.58% covered (danger)
31.58%
18 / 57
0.00% covered (danger)
0.00%
0 / 1
148.12
 removeMovedPackagesAutoloadKeyFromVendorDirInstalledJson
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 removeMovedPackagesAutoloadKeyFromTargetDirInstalledJson
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
9.75
 updateNamespaces
70.73% covered (warning)
70.73%
29 / 41
0.00% covered (danger)
0.00%
0 / 1
22.42
 cleanTargetDirInstalledJson
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
3
 cleanupVendorInstalledJson
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Changes "install-path" to point to vendor-prefixed target directory.
4 *
5 * * create new vendor-prefixed/composer/installed.json file with copied packages
6 * * when delete is enabled, update package paths in the original vendor/composer/installed.json
7 * * when delete is enabled, remove dead entries in the original vendor/composer/installed.json
8 * * update psr-0 autoload keys to have matching classmap entries
9 *
10 * @see vendor/composer/installed.json
11 *
12 * TODO: when delete_vendor_files is used, the original directory still exists so the paths are not updated.
13 *
14 * @package brianhenryie/strauss
15 */
16
17namespace BrianHenryIE\Strauss\Pipeline\Cleanup;
18
19use BrianHenryIE\Strauss\Composer\ComposerPackage;
20use BrianHenryIE\Strauss\Config\CleanupConfigInterface;
21use BrianHenryIE\Strauss\Helpers\FileSystem;
22use BrianHenryIE\Strauss\Types\DiscoveredSymbols;
23use Composer\Json\JsonFile;
24use Composer\Json\JsonValidationException;
25use Exception;
26use League\Flysystem\FilesystemException;
27use Psr\Log\LoggerAwareTrait;
28use Psr\Log\LoggerInterface;
29use Seld\JsonLint\ParsingException;
30
31/**
32 * @phpstan-type InstalledJsonPackageSourceArray array{type:string, url:string, reference:string}
33 * @phpstan-type InstalledJsonPackageDistArray array{type:string, url:string, reference:string, shasum:string}
34 * @phpstan-type InstalledJsonPackageAutoloadPsr0Array array<string,string|array<string>>
35 * @phpstan-type InstalledJsonPackageAutoloadPsr4Array array<string,string|array<string>>
36 * @phpstan-type InstalledJsonPackageAutoloadClassmapArray string[]
37 * @phpstan-type InstalledJsonPackageAutoloadFilesArray string[]
38 * @phpstan-type InstalledJsonPackageAutoloadArray array{"psr-4"?:InstalledJsonPackageAutoloadPsr4Array, classmap?:InstalledJsonPackageAutoloadClassmapArray, files?:InstalledJsonPackageAutoloadFilesArray, "psr-0"?:InstalledJsonPackageAutoloadPsr0Array}
39 * @phpstan-type InstalledJsonPackageAuthorArray array{name:string,email:string}
40 * @phpstan-type InstalledJsonPackageSupportArray array{issues:string, source:string}
41 *
42 * @phpstan-type InstalledJsonPackageArray array{name:string, version:string, version_normalized:string, source:InstalledJsonPackageSourceArray, dist:InstalledJsonPackageDistArray, require:array<string,string>, require-dev:array<string,string>, time:string, type:string, installation-source:string, autoload?:InstalledJsonPackageAutoloadArray, notification-url:string, license:array<string>, authors:array<InstalledJsonPackageAuthorArray>, description:string, homepage:string, keywords:array<string>, support:InstalledJsonPackageSupportArray, install-path:string}
43 *
44 * @phpstan-type InstalledJsonArray array{packages:array<InstalledJsonPackageArray>, dev?:bool, dev-package-names:array<string>}
45 */
46class InstalledJson
47{
48    use LoggerAwareTrait;
49
50    protected CleanupConfigInterface $config;
51
52    protected FileSystem $filesystem;
53
54    public function __construct(
55        CleanupConfigInterface $config,
56        FileSystem $filesystem,
57        LoggerInterface $logger
58    ) {
59        $this->config = $config;
60        $this->filesystem = $filesystem;
61
62        $this->setLogger($logger);
63    }
64
65    /**
66     * @throws FilesystemException
67     */
68    public function copyInstalledJson(): void
69    {
70        $source = $this->config->getVendorDirectory() . 'composer/installed.json';
71        $target = $this->config->getTargetDirectory() . 'composer/installed.json';
72
73        $this->logger->info('Copying {sourcePath} to {targetPath}', [
74            'sourcePath' => $source,
75            'targetPath' => $target
76        ]);
77
78        $this->filesystem->copy(
79            $source,
80            $target
81        );
82
83        $this->logger->info('Copied {sourcePath} to {targetPath}', [
84            'sourcePath' => $source,
85            'targetPath' => $target
86        ]);
87
88        $this->logger->debug($this->filesystem->read($this->config->getTargetDirectory() . 'composer/installed.json'));
89    }
90
91    /**
92     * @throws JsonValidationException
93     * @throws ParsingException
94     * @throws Exception
95     */
96    protected function getJsonFile(string $vendorDir): JsonFile
97    {
98        $installedJsonFile = new JsonFile(
99            sprintf(
100                '%scomposer/installed.json',
101                $vendorDir
102            )
103        );
104        if (!$installedJsonFile->exists()) {
105            $this->logger->error(
106                'Expected {installedJsonFilePath} does not exist.',
107                ['installedJsonFilePath' => $installedJsonFile->getPath()]
108            );
109            throw new Exception('Expected vendor/composer/installed.json does not exist.');
110        }
111
112        $installedJsonFile->validateSchema(JsonFile::LAX_SCHEMA);
113
114        $this->logger->info('Loaded file: {installedJsonFilePath}', ['installedJsonFilePath' => $installedJsonFile->getPath()]);
115
116        return $installedJsonFile;
117    }
118
119    /**
120     * @param InstalledJsonArray $installedJsonArray
121     * @param array<string,ComposerPackage> $flatDependencyTree
122     * @return InstalledJsonArray
123     */
124    protected function updatePackagePaths(array $installedJsonArray, array $flatDependencyTree, string $path): array
125    {
126
127        foreach ($installedJsonArray['packages'] as $key => $package) {
128            if (in_array($package['name'], $this->config->getExcludePackagesFromCopy(), true)) {
129                unset($installedJsonArray['packages'][$key]);
130                continue;
131            }
132
133            // Skip packages that were never copied in the first place.
134            if (!in_array($package['name'], array_keys($flatDependencyTree))) {
135                $this->logger->debug('Skipping package: ' . $package['name']);
136                continue;
137            }
138            $this->logger->info('Checking package: ' . $package['name']);
139
140            // `composer/` is here because the install-path is relative to the `vendor/composer` directory.
141            $packageDir = $path . 'composer/' . $package['install-path'] . '/';
142            if (!$this->filesystem->directoryExists($packageDir)) {
143                $this->logger->debug('Package directory does not exist at : ' . $packageDir);
144
145                $newInstallPath = $path . str_replace('../', '', $package['install-path']);
146
147                if (!$this->filesystem->directoryExists($newInstallPath)) {
148                    // Should `unset($installedJsonArray['packages'][$key])`?
149                    // Is this post `delete_vendor_packages`?
150                    $this->logger->warning('Package directory unexpectedly DOES NOT exist: ' . $newInstallPath);
151                    continue;
152                }
153
154                $newRelativePath = $this->filesystem->getRelativePath(
155                    $path . 'composer/',
156                    $newInstallPath
157                );
158
159                $installedJsonArray['packages'][$key]['install-path'] = $newRelativePath;
160            } else {
161                $this->logger->debug('Original package directory exists at : ' . $packageDir);
162            }
163        }
164        return $installedJsonArray;
165    }
166
167    /**
168     * @param InstalledJsonPackageArray $packageArray
169     * @throws FilesystemException
170     */
171    protected function pathExistsInPackage(string $vendorDir, array $packageArray, string $relativePath): bool
172    {
173        return $this->filesystem->exists(
174            $vendorDir . 'composer/' . $packageArray['install-path'] . '/' . $relativePath
175        );
176    }
177
178    /**
179     * Remove autoload key entries from `installed.json` whose file or directory does not exist after deleting.
180     *
181     * @param InstalledJsonArray $installedJsonArray
182     * @return InstalledJsonArray
183     * @throws FilesystemException
184     */
185    protected function removeMissingAutoloadKeyPaths(array $installedJsonArray, string $vendorDir, string $installedJsonPath): array
186    {
187        foreach ($installedJsonArray['packages'] as $packageIndex => $packageArray) {
188            if (!isset($packageArray['autoload'])) {
189                $this->logger->info(
190                    'Package {packageName} has no autoload key in {installedJsonPath}',
191                    ['packageName' => $packageArray['name'],'installedJsonPath'=>$installedJsonPath]
192                );
193                continue;
194            }
195            // delete_vendor_files
196            $path = $vendorDir . 'composer/' . $packageArray['install-path'];
197            $pathExists = $this->filesystem->directoryExists($path);
198            // delete_vendor_packages
199            if (!$pathExists) {
200                $this->logger->info(
201                    'Removing package autoload key from {installedJsonPath}: {packageName}',
202                    ['packageName' => $packageArray['name'],'installedJsonPath'=>$installedJsonPath]
203                );
204                $installedJsonArray['packages'][$packageIndex]['autoload'] = [];
205            }
206            foreach ($installedJsonArray['packages'][$packageIndex]['autoload'] ?? [] as $type => $autoload) {
207                switch ($type) {
208                    case 'files':
209                    case 'classmap':
210                        // Ensure we filter the current autoload bucket and keep only existing paths
211                        $filtered = array_filter(
212                            (array) $autoload,
213                            function ($relativePath) use ($vendorDir, $packageArray): bool {
214                                return is_string($relativePath) && $this->pathExistsInPackage($vendorDir, $packageArray, $relativePath);
215                            }
216                        );
217                        // Reindex to produce a clean list of strings
218                        $installedJsonArray['packages'][$packageIndex]['autoload'][$type] = array_values($filtered);
219                        break;
220                    case 'psr-0':
221                    case 'psr-4':
222                        foreach ($autoload as $namespace => $paths) {
223                            switch (true) {
224                                case is_array($paths):
225                                    // e.g. [ 'psr-4' => [ 'BrianHenryIE\Project' => ['src','lib] ] ]
226                                    $validPaths = [];
227                                    foreach ($paths as $path) {
228                                        if ($this->pathExistsInPackage($vendorDir, $packageArray, $path)) {
229                                            $validPaths[] = $path;
230                                        } else {
231                                            $this->logger->debug('Removing non-existent path from autoload: ' . $path);
232                                        }
233                                    }
234                                    if (!empty($validPaths)) {
235                                        $installedJsonArray['packages'][$packageIndex]['autoload'][$type][$namespace] = $validPaths;
236                                    } else {
237                                        $this->logger->debug('Removing autoload key: ' . $type);
238                                        unset($installedJsonArray['packages'][$packageIndex]['autoload'][$type][$namespace]);
239                                    }
240                                    break;
241                                case is_string($paths):
242                                    // e.g. [ 'psr-4' => [ 'BrianHenryIE\Project' => 'src' ] ]
243                                    if (!$this->pathExistsInPackage($vendorDir, $packageArray, $paths)) {
244                                        $this->logger->debug('Removing autoload key: ' . $type . ' for ' . $paths);
245                                        unset($installedJsonArray['packages'][$packageIndex]['autoload'][$type][$namespace]);
246                                    }
247                                    break;
248                                default:
249                                    $this->logger->warning('Unexpectedly got neither a string nor array for autoload key in installed.json: ' . $type . ' ' . json_encode($paths));
250                                    break;
251                            }
252                        }
253                        break;
254                    case 'exclude-from-classmap':
255                        break;
256                    default:
257                        $this->logger->warning(
258                            'Unexpected autoload type in {installedJsonPath}: {type}',
259                            ['installedJsonPath'=>$installedJsonPath,'type'=>$type]
260                        );
261                        break;
262                }
263            }
264        }
265        /** @var InstalledJsonArray $installedJsonArray */
266        $installedJsonArray = $installedJsonArray;
267        return $installedJsonArray;
268    }
269
270    /**
271     * Remove the autoload key for packages from `installed.json` whose target directory does not exist after deleting.
272     *
273     * E.g. after the file is copied to the target directory, this will remove dev dependencies and unmodified dependencies from the second installed.json
274     *
275     * @param InstalledJsonArray $installedJsonArray
276     * @param array<string,ComposerPackage> $flatDependencyTree
277     * @return InstalledJsonArray
278     */
279    protected function removeMovedPackagesAutoloadKeyFromVendorDirInstalledJson(array $installedJsonArray, array $flatDependencyTree, string $installedJsonPath): array
280    {
281        /**
282         * @var int $key
283         * @var InstalledJsonPackageArray $packageArray
284         */
285        foreach ($installedJsonArray['packages'] as $key => $packageArray) {
286            $packageName = $packageArray['name'];
287            $package = $flatDependencyTree[$packageName] ?? null;
288            if (!$package) {
289                // Probably a dev dependency that we aren't tracking.
290                continue;
291            }
292
293            if ($package->didDelete()) {
294                $this->logger->info(
295                    'Removing deleted package autoload key from {installedJsonPath}: {packageName}',
296                    ['installedJsonPath' => $installedJsonPath, 'packageName' => $packageName]
297                );
298                $installedJsonArray['packages'][$key]['autoload'] = [];
299            }
300        }
301        return $installedJsonArray;
302    }
303
304    /**
305     * Remove the autoload key for packages from `vendor-prefixed/composer/installed.json` whose target directory does not exist in `vendor-prefixed`.
306     *
307     * E.g. after the file is copied to the target directory, this will remove dev dependencies and unmodified dependencies from the second installed.json
308     *
309     * @param InstalledJsonArray $installedJsonArray
310     * @param array<string,ComposerPackage> $flatDependencyTree
311     * @return InstalledJsonArray
312     */
313    protected function removeMovedPackagesAutoloadKeyFromTargetDirInstalledJson(array $installedJsonArray, array $flatDependencyTree, string $installedJsonPath): array
314    {
315        /**
316         * @var int $key
317         * @var InstalledJsonPackageArray $packageArray
318         */
319        foreach ($installedJsonArray['packages'] as $key => $packageArray) {
320            $packageName = $packageArray['name'];
321
322            $remove = false;
323
324            if (!in_array($packageName, array_keys($flatDependencyTree))) {
325                // If it's not a package we were ever considering copying, then we can remove it.
326                $remove = true;
327            } else {
328                $package = $flatDependencyTree[$packageName] ?? null;
329                if (!$package) {
330                    // Probably a dev dependency.
331                    continue;
332                }
333                if (!$package->didCopy()) {
334                    // If it was marked not to copy, then we know it's not in the vendor-prefixed directory, and we can remove it.
335                    $remove = true;
336                }
337            }
338
339            if ($remove) {
340                $this->logger->info(
341                    'Removing deleted package autoload key from {installedJsonPath}: {packageName}',
342                    ['installedJsonPath' => $installedJsonPath, 'packageName' => $packageName]
343                );
344                $installedJsonArray['packages'][$key]['autoload'] = [];
345            }
346        }
347        return $installedJsonArray;
348    }
349
350    /**
351     * @param InstalledJsonArray $installedJsonArray
352     * @return InstalledJsonArray
353     */
354    protected function updateNamespaces(array $installedJsonArray, DiscoveredSymbols $discoveredSymbols): array
355    {
356        $discoveredNamespaces = $discoveredSymbols->getNamespaces();
357
358        foreach ($installedJsonArray['packages'] as $key => $package) {
359            if (!isset($package['autoload'])) {
360                // woocommerce/action-scheduler
361                $this->logger->info('Package has no autoload key: ' . $package['name'] . ' ' . $package['type']);
362                continue;
363            }
364
365            $autoload_key = $package['autoload'];
366            if (!isset($autoload_key['classmap'])) {
367                $autoload_key['classmap'] = [];
368            }
369            foreach ($autoload_key as $type => $autoload) {
370                switch ($type) {
371                    case 'psr-0':
372                        /** @var string $relativePath */
373                        foreach (array_values((array) $autoload_key[$type]) as $relativePath) {
374                            $packageRelativePath = $package['install-path'];
375                            if (1 === preg_match('#.*'.preg_quote($this->filesystem->normalize($this->config->getTargetDirectory()), '#').'/(.*)#', $packageRelativePath, $matches)) {
376                                $packageRelativePath = $matches[1];
377                            }
378                            // Convert psr-0 autoloading to classmap autoloading
379                            if ($this->filesystem->directoryExists($this->config->getTargetDirectory() . 'composer/' . $packageRelativePath . $relativePath)) {
380                                $autoload_key['classmap'][] = $relativePath;
381                            }
382                        }
383                        // Intentionally fall through
384                        // Although the PSR-0 implementation here is a bit of a hack.
385                    case 'psr-4':
386                        /**
387                         * e.g.
388                         * * {"psr-4":{"Psr\\Log\\":"Psr\/Log\/"}}
389                         * * {"psr-4":{"":"src\/"}}
390                         * * {"psr-4":{"Symfony\\Polyfill\\Mbstring\\":""}}
391                         * * {"psr-4":{"Another\\Package\\":["src","includes"]}}
392                         * * {"psr-0":{"PayPal":"lib\/"}}
393                         */
394                        foreach ($autoload_key[$type] ?? [] as $originalNamespace => $packageRelativeDirectory) {
395                            // Replace $originalNamespace with updated namespace
396
397                            // Just for dev â€“ find a package like this and write a test for it.
398                            if (empty($originalNamespace)) {
399                                // In the case of `nesbot/carbon`, it uses an empty namespace but the classes are in the `Carbon`
400                                // namespace, so using `override_autoload` should be a good solution if this proves to be an issue.
401                                // The package directory will be updated, so for whatever reason the original empty namespace
402                                // works, maybe the updated namespace will work too.
403                                $this->logger->warning('Empty namespace found in autoload. Behaviour is not fully documented: ' . $package['name']);
404                                continue;
405                            }
406
407                            $trimmedOriginalNamespace = trim($originalNamespace, '\\');
408
409                            $this->logger->info('Checking '.$type.' namespace: ' . $trimmedOriginalNamespace);
410
411                            if (isset($discoveredNamespaces[$trimmedOriginalNamespace])) {
412                                $namespaceSymbol = $discoveredNamespaces[$trimmedOriginalNamespace];
413                            } else {
414                                $this->logger->debug('Namespace not found in list of changes: ' . $trimmedOriginalNamespace);
415                                continue;
416                            }
417
418                            if ($trimmedOriginalNamespace === trim($namespaceSymbol->getReplacement(), '\\')) {
419                                $this->logger->debug('Namespace is unchanged: ' . $trimmedOriginalNamespace);
420                                continue;
421                            }
422
423                            // Update the namespace if it has changed.
424                            $this->logger->info('Updating namespace: ' . $trimmedOriginalNamespace . ' => ' . $namespaceSymbol->getReplacement());
425                            /** @phpstan-ignore offsetAccess.notFound */
426                            $autoload_key[$type][str_replace($trimmedOriginalNamespace, $namespaceSymbol->getReplacement(), $originalNamespace)] = $autoload_key[$type][$originalNamespace];
427                            unset($autoload_key[$type][$originalNamespace]);
428                        }
429                        break;
430                    default:
431                        /**
432                         * `files`, `classmap`, `exclude-from-classmap`
433                         * These don't contain namespaces in the autoload key.
434                         * * {"classmap":["src\/"]}
435                         * * {"files":["src\/functions.php"]}
436                         * * {"exclude-from-classmap":["\/Tests\/"]}
437                         *
438                         * Custom autoloader types might.
439                         */
440                        if (!in_array($type, ['files', 'classmap', 'exclude-from-classmap'])) {
441                            $this->logger->warning('Unexpected autoloader type: {type} in {packageName}.', [
442                                'type' => $type, 'packageName' => $package['name']
443                            ]);
444                        }
445                        break;
446                }
447            }
448            $installedJsonArray['packages'][$key]['autoload'] = array_filter($autoload_key);
449        }
450
451        return $installedJsonArray;
452    }
453
454    /**
455     * @param array<string,ComposerPackage> $flatDependencyTree
456     * @param DiscoveredSymbols $discoveredSymbols
457     * @throws Exception
458     * @throws FilesystemException
459     */
460    public function cleanTargetDirInstalledJson(array $flatDependencyTree, DiscoveredSymbols $discoveredSymbols): void
461    {
462        $targetDir = $this->config->getTargetDirectory();
463
464        $installedJsonFile = $this->getJsonFile($targetDir);
465
466        /**
467         * @var InstalledJsonArray $installedJsonArray
468         */
469        $installedJsonArray = $installedJsonFile->read();
470
471        $this->logger->debug(
472            '{installedJsonFilePath} before: {installedJsonArray}',
473            ['installedJsonFilePath' => $installedJsonFile->getPath(), 'installedJsonArray' => json_encode($installedJsonArray)]
474        );
475
476        $installedJsonArray = $this->updatePackagePaths($installedJsonArray, $flatDependencyTree, $this->config->getTargetDirectory());
477
478        $installedJsonArray = $this->removeMissingAutoloadKeyPaths($installedJsonArray, $this->config->getTargetDirectory(), $installedJsonFile->getPath());
479
480        $installedJsonArray = $this->removeMovedPackagesAutoloadKeyFromTargetDirInstalledJson(
481            $installedJsonArray,
482            $flatDependencyTree,
483            $installedJsonFile->getPath()
484        );
485
486        $installedJsonArray = $this->updateNamespaces($installedJsonArray, $discoveredSymbols);
487
488        foreach ($installedJsonArray['packages'] as $index => $package) {
489            if (!in_array($package['name'], array_keys($flatDependencyTree))) {
490                unset($installedJsonArray['packages'][$index]);
491            }
492        }
493
494        $installedJsonArray['dev'] = false;
495        $installedJsonArray['dev-package-names'] = [];
496
497        $this->logger->debug('Installed.json after: ' . json_encode($installedJsonArray));
498
499        $this->logger->info('Writing installed.json to ' . $targetDir);
500
501        $installedJsonFile->write($installedJsonArray);
502
503        $this->logger->info('Installed.json written to ' . $targetDir);
504    }
505
506    /**
507     * Composer creates a file `vendor/composer/installed.json` which is used when running `composer dump-autoload`.
508     * When `delete-vendor-packages` or `delete-vendor-files` is true, files and directories which have been deleted
509     * must also be removed from `installed.json` or Composer will throw an error.
510     *
511     * @param array<string,ComposerPackage> $flatDependencyTree
512     * @throws Exception
513     * @throws FilesystemException
514     */
515    public function cleanupVendorInstalledJson(array $flatDependencyTree, DiscoveredSymbols $discoveredSymbols): void
516    {
517
518        $vendorDir = $this->config->getVendorDirectory();
519
520        $vendorInstalledJsonFile = $this->getJsonFile($vendorDir);
521
522        $this->logger->info('Cleaning up {installedJsonPath}', ['installedJsonPath' => $vendorInstalledJsonFile->getPath()]);
523
524        /**
525         * @var InstalledJsonArray $installedJsonArray
526         */
527        $installedJsonArray = $vendorInstalledJsonFile->read();
528
529        $installedJsonArray = $this->removeMissingAutoloadKeyPaths($installedJsonArray, $this->config->getVendorDirectory(), $vendorInstalledJsonFile->getPath());
530
531        $installedJsonArray = $this->removeMovedPackagesAutoloadKeyFromVendorDirInstalledJson($installedJsonArray, $flatDependencyTree, $vendorInstalledJsonFile->getPath());
532
533        $installedJsonArray = $this->updatePackagePaths($installedJsonArray, $flatDependencyTree, $this->config->getVendorDirectory());
534
535        // Only relevant when source = target.
536        $installedJsonArray = $this->updateNamespaces($installedJsonArray, $discoveredSymbols);
537
538        $vendorInstalledJsonFile->write($installedJsonArray);
539    }
540}