Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ManagedConfigPathSynchronizer
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 synchronize
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 isManagedConfigPath
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 normalize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\GrumPhp;
21
22use Symfony\Component\Filesystem\Path;
23
24/**
25 * Synchronizes deprecated DevTools-managed GrumPHP composer metadata without leaking package paths into consumers.
26 */
27 class ManagedConfigPathSynchronizer
28{
29    /**
30     * @var string relative packaged config suffix managed by DevTools installs
31     */
32    private const string MANAGED_CONFIG_SUFFIX = 'vendor/fast-forward/dev-tools/grumphp.yml';
33
34    /**
35     * Removes deprecated DevTools-managed GrumPHP config-default-path entries while preserving consumer-owned values.
36     *
37     * @param array<string, mixed> $extra the composer.json extra payload
38     * @param string $workingDirectory the consumer project directory
39     * @param string $managedConfigPath the active packaged GrumPHP config path
40     *
41     * @return array<string, mixed> the synchronized composer extra payload
42     */
43    public function synchronize(array $extra, string $workingDirectory, string $managedConfigPath): array
44    {
45        if (isset($extra['grumphp']) && ! \is_array($extra['grumphp'])) {
46            return $extra;
47        }
48
49        $grumphpExtra = $extra['grumphp'] ?? [];
50        $configDefaultPath = $grumphpExtra['config-default-path'] ?? null;
51
52        if (\is_string($configDefaultPath) && $this->isManagedConfigPath(
53            $configDefaultPath,
54            $workingDirectory,
55            $managedConfigPath
56        )) {
57            unset($grumphpExtra['config-default-path']);
58        }
59
60        if ([] === $grumphpExtra) {
61            unset($extra['grumphp']);
62
63            return $extra;
64        }
65
66        $extra['grumphp'] = $grumphpExtra;
67
68        return $extra;
69    }
70
71    /**
72     * Reports whether a config-default-path value is managed by DevTools.
73     *
74     * @param string $configDefaultPath the stored composer extra path value
75     * @param string $workingDirectory the consumer project directory
76     * @param string $managedConfigPath the active packaged GrumPHP config path
77     */
78    public function isManagedConfigPath(
79        string $configDefaultPath,
80        string $workingDirectory,
81        string $managedConfigPath,
82    ): bool {
83        $normalizedConfigPath = $this->normalize($configDefaultPath);
84        $normalizedManagedRelativePath = $this->normalize(Path::makeRelative($managedConfigPath, $workingDirectory));
85
86        return $normalizedConfigPath === $normalizedManagedRelativePath
87            || str_ends_with($normalizedConfigPath, self::MANAGED_CONFIG_SUFFIX);
88    }
89
90    /**
91     * Normalizes a path for stable comparisons across platforms.
92     *
93     * @param string $path the path to normalize
94     */
95    private function normalize(string $path): string
96    {
97        return trim(str_replace('\\', '/', Path::canonicalize($path)), '/');
98    }
99}