Failed Conditions
Push — master ( d49c03...6343d0 )
by Bernhard
05:03
created

src/Conflict/PackageConflictDetector.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the puli/manager package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Manager\Conflict;
13
14
/**
15
 * Detects configuration conflicts between packages.
16
 *
17
 * Packages may claim "tokens" for themselves. A token, in that sense, can be
18
 * any integer or string. If packages claim the same token, a conflict is
19
 * raised:
20
 *
21
 * ```php
22
 * use Puli\Manager\Conflict\PackageConflictDetector;
23
 *
24
 * $detector = new PackageConflictDetector();
25
 * $detector->claim('/app/config', 'package1');
26
 * $detector->claim('/app/views', 'package2');
27
 *
28
 * $conflicts = $detector->detectConflicts(array('/app/config', '/app/views'));
29
 * // => array()
30
 *
31
 * $detector->claim('/app/config', 'package2');
32
 *
33
 * $conflicts = $detector->detectConflicts(array('/app/config', '/app/views'));
34
 * // => array(PackageConflict)
35
 * ```
36
 *
37
 * You can resolve conflicts by passing an {@link OverrideGraph} to the
38
 * detector. The override graph has package names as nodes. When the conflict
39
 * graph contains an edge from package A to package B, then package A is
40
 * considered to be overridden by package B. Claims for the same resources will
41
 * not result in conflicts for these packages:
42
 *
43
 * ```php
44
 * use Puli\Manager\Conflict\OverrideGraph;
45
 * use Puli\Manager\Conflict\PackageConflictDetector;
46
 *
47
 * $graph = new OverrideGraph();
48
 * $graph->addPackageName('package1');
49
 * $graph->addPackageName('package2');
50
 *
51
 * // package1 is overridden by package2
52
 * $graph->addEdge('package1', 'package2');
53
 *
54
 * $detector = new PackageConflictDetector($graph);
55
 * $detector->claim('/app/config', 'package1');
56
 * $detector->claim('/app/config', 'package2');
57
 *
58
 * // The conflict has been resolved
59
 * $conflict s= $detector->detectConflict(array('/app/config'));
60
 * // => array()
61
 * ```
62
 *
63
 * @since  1.0
64
 *
65
 * @author Bernhard Schussek <[email protected]>
66
 */
67
class PackageConflictDetector
68
{
69
    /**
70
     * @var OverrideGraph
71
     */
72
    private $overrideGraph;
73
74
    /**
75
     * @var bool[][]
76
     */
77
    private $tokens = array();
78
79
    /**
80
     * Creates a new conflict detector.
81
     *
82
     * @param OverrideGraph|null $overrideGraph The graph indicating which
83
     *                                          package is overridden by which
84
     *                                          other package.
85
     */
86 73
    public function __construct(OverrideGraph $overrideGraph = null)
87
    {
88 73
        $this->overrideGraph = $overrideGraph ?: new OverrideGraph();
89 73
    }
90
91
    /**
92
     * Claims a token for a package.
93
     *
94
     * @param int|string $token       The claimed token. Can be any integer or
95
     *                                string.
96
     * @param string     $packageName The package name.
97
     */
98 64
    public function claim($token, $packageName)
99
    {
100 64
        if (!isset($this->tokens[$token])) {
101 64
            $this->tokens[$token] = array();
102
        }
103
104 64
        $this->tokens[$token][$packageName] = true;
105 64
    }
106
107
    /**
108
     * Releases a package's claim for a token.
109
     *
110
     * @param int|string $token       The claimed token. Can be any integer or
111
     *                                string.
112
     * @param string     $packageName The package name.
113
     */
114 16
    public function release($token, $packageName)
115
    {
116 16
        unset($this->tokens[$token][$packageName]);
117 16
    }
118
119
    /**
120
     * Checks the passed tokens for conflicts.
121
     *
122
     * If no tokens are passed, all tokens are checked.
123
     *
124
     * A conflict is returned for every token that is claimed by two packages
125
     * that are not connected by an edge in the override graph. In other words,
126
     * if two packages A and B claim the same token, an edge must exist from A
127
     * to B (A is overridden by B) or from B to A (B is overridden by A).
128
     * Otherwise a conflict is returned.
129
     *
130
     * @param int[]|string[]|null $tokens The tokens to check. If `null`, all
0 ignored issues
show
Consider making the type for parameter $tokens a bit more specific; maybe use null|integer[].
Loading history...
131
     *                                    claimed tokens are checked for
132
     *                                    conflicts. You are advised to pass
133
     *                                    tokens if possible to improve the
134
     *                                    performance of the conflict detection.
135
     *
136
     * @return PackageConflict[] The detected conflicts.
137
     */
138 73
    public function detectConflicts(array $tokens = null)
139
    {
140 73
        $tokens = null === $tokens ? array_keys($this->tokens) : $tokens;
141 73
        $conflicts = array();
142
143 73
        foreach ($tokens as $token) {
144
            // Claim was released
145 64
            if (!isset($this->tokens[$token])) {
146
                continue;
147
            }
148
149 64
            $packageNames = array_keys($this->tokens[$token]);
150
151
            // Token claimed by only one package
152 64
            if (1 === count($packageNames)) {
153 58
                continue;
154
            }
155
156 38
            $sortedNames = $this->overrideGraph->getSortedPackageNames($packageNames);
157 38
            $conflictingNames = array();
158
159
            // An edge must exist between each package pair in the sorted set,
160
            // otherwise the dependencies are not sufficiently defined
161 38
            for ($i = 1, $l = count($sortedNames); $i < $l; ++$i) {
162 38
                if (!$this->overrideGraph->hasEdge($sortedNames[$i - 1], $sortedNames[$i])) {
163 26
                    $conflictingNames[$sortedNames[$i - 1]] = true;
164 26
                    $conflictingNames[$sortedNames[$i]] = true;
165
                }
166
            }
167
168 38
            if (count($conflictingNames) > 0) {
169 38
                $conflicts[] = new PackageConflict($token, array_keys($conflictingNames));
170
            }
171
        }
172
173 73
        return $conflicts;
174
    }
175
}
176