Test Failed
Pull Request — master (#35)
by Jan-Marten
10:48
created

PackageRequirementsResolver::resolve()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 21
nc 2
nop 1
dl 0
loc 36
rs 9.584
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright MediaCT. All rights reserved.
4
 * https://www.mediact.nl
5
 */
6
7
namespace Mediact\DependencyGuard\Composer\Locker;
8
9
use Composer\Package\Locker;
10
use Composer\Package\PackageInterface;
11
use SplObjectStorage;
12
13
class PackageRequirementsResolver implements PackageRequirementsResolverInterface
14
{
15
    /** @var SplObjectStorage|PackageInterface[][][] */
16
    private $resolvedLockers;
17
18
    /**
19
     * Constructor.
20
     *
21
     * @param SplObjectStorage|null $resolvedLockers
22
     */
23
    public function __construct(SplObjectStorage $resolvedLockers = null)
24
    {
25
        $this->resolvedLockers = $resolvedLockers ?? new SplObjectStorage();
26
    }
27
28
    /**
29
     * Resolve the dependents graph for the given Composer locker.
30
     *
31
     * @param Locker $locker
32
     *
33
     * @return array|PackageInterface[][]
34
     */
35
    public function resolve(Locker $locker): array
36
    {
37
        if (!$this->resolvedLockers->contains($locker)) {
38
            $lockedPackages = $locker->getLockedRepository()->getPackages();
39
40
            $this->resolvedLockers->attach(
41
                $locker,
42
                array_map(
43
                    function (array $packages) use ($lockedPackages) : array {
44
                        return array_reduce(
45
                            $lockedPackages,
46
                            function (
47
                                array $carry,
48
                                PackageInterface $package
49
                            ) use ($packages) : array {
50
                                if (in_array(
51
                                    $package->getName(),
52
                                    $packages,
53
                                    true
54
                                )) {
55
                                    $carry[] = $package;
56
                                }
57
58
                                return $carry;
59
                            },
60
                            []
61
                        );
62
                    },
63
                    $this->resolveGraph(
64
                        ...$locker->getLockData()['packages'] ?? []
65
                    )
66
                )
67
            );
68
        }
69
70
        return $this->resolvedLockers->offsetGet($locker);
71
    }
72
73
    /**
74
     * Get the dependent packages for the given package, using the given locker.
75
     *
76
     * @param string $package
77
     * @param Locker $locker
78
     *
79
     * @return array|PackageInterface[]
80
     */
81
    public function getDependents(string $package, Locker $locker): array
82
    {
83
        return $this->resolve($locker)[$package] ?? [];
84
    }
85
86
    /**
87
     * Resolve the dependents graph for the given packages.
88
     *
89
     * @param array ...$packages
90
     *
91
     * @return string[]
92
     */
93
    private function resolveGraph(array ...$packages): array
94
    {
95
        $graph = array_reduce(
96
            $packages,
97
            function (array $carry, array $package): array {
98
                foreach (array_keys($package['require'] ?? []) as $link) {
99
                    if (!preg_match('/^[^\/]+\/[^\/]+$/', $link)) {
100
                        // Most likely a platform requirement.
101
                        // E.g.: php
102
                        // E.g.: ext-openssl
103
                        continue;
104
                    }
105
106
                    if (!array_key_exists($link, $carry)) {
107
                        $carry[$link] = [];
108
                    }
109
110
                    if (!in_array($package['name'], $carry[$link], true)) {
111
                        $carry[$link][] = $package['name'];
112
                    }
113
                }
114
115
                return $carry;
116
            },
117
            []
118
        );
119
120
        // While the graph keeps receiving updates, keep on resolving.
121
        for ($previousGraph = []; $graph !== $previousGraph;) {
122
            // Do not update the previous graph before the current iteration
123
            // has started.
124
            $previousGraph = $graph;
125
126
            // For each package and its dependents in the graph ...
127
            foreach ($graph as $package => $dependents) {
128
                // ... Update the dependents of the package with grandparents.
129
                $graph[$package] = array_reduce(
130
                    // Determine grandparents by looking up the parents of the
131
                    // available dependents.
132
                    $dependents,
133
                    function (array $carry, string $parent) use ($graph): array {
134
                        foreach ($graph[$parent] ?? [] as $grandparent) {
135
                            if (!in_array($grandparent, $carry, true)) {
136
                                $carry[] = $grandparent;
137
                            }
138
                        }
139
140
                        return $carry;
141
                    },
142
                    // Start out with the current list of dependents.
143
                    $dependents
144
                );
145
            }
146
        }
147
148
        return $graph;
149
    }
150
}
151