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

ModuleConflictDetector::detectConflicts()   C

Complexity

Conditions 8
Paths 18

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8.0109

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 37
ccs 17
cts 18
cp 0.9444
rs 5.3846
cc 8
eloc 18
nc 18
nop 1
crap 8.0109
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 modules.
16
 *
17
 * Modules may claim "tokens" for themselves. A token, in that sense, can be
18
 * any integer or string. If modules claim the same token, a conflict is
19
 * raised:
20
 *
21
 * ```php
22
 * use Puli\Manager\Conflict\ModuleConflictDetector;
23
 *
24
 * $detector = new ModuleConflictDetector();
25
 * $detector->claim('/app/config', 'module1');
26
 * $detector->claim('/app/views', 'module2');
27
 *
28
 * $conflicts = $detector->detectConflicts(array('/app/config', '/app/views'));
29
 * // => array()
30
 *
31
 * $detector->claim('/app/config', 'module2');
32
 *
33
 * $conflicts = $detector->detectConflicts(array('/app/config', '/app/views'));
34
 * // => array(ModuleConflict)
35
 * ```
36
 *
37
 * You can resolve conflicts by passing an {@link OverrideGraph} to the
38
 * detector. The override graph has module names as nodes. When the conflict
39
 * graph contains an edge from module A to module B, then module A is
40
 * considered to be overridden by module B. Claims for the same resources will
41
 * not result in conflicts for these modules:
42
 *
43
 * ```php
44
 * use Puli\Manager\Conflict\OverrideGraph;
45
 * use Puli\Manager\Conflict\ModuleConflictDetector;
46
 *
47
 * $graph = new OverrideGraph();
48
 * $graph->addModuleName('module1');
49
 * $graph->addModuleName('module2');
50
 *
51
 * // module1 is overridden by module2
52
 * $graph->addEdge('module1', 'module2');
53
 *
54
 * $detector = new ModuleConflictDetector($graph);
55
 * $detector->claim('/app/config', 'module1');
56
 * $detector->claim('/app/config', 'module2');
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 ModuleConflictDetector
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
     *                                          module is overridden by which
84
     *                                          other module.
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 module.
93
     *
94
     * @param int|string $token      The claimed token. Can be any integer or
95
     *                               string.
96
     * @param string     $moduleName The module name.
97
     */
98 64
    public function claim($token, $moduleName)
99
    {
100 64
        if (!isset($this->tokens[$token])) {
101 64
            $this->tokens[$token] = array();
102
        }
103
104 64
        $this->tokens[$token][$moduleName] = true;
105 64
    }
106
107
    /**
108
     * Releases a module's claim for a token.
109
     *
110
     * @param int|string $token      The claimed token. Can be any integer or
111
     *                               string.
112
     * @param string     $moduleName The module name.
113
     */
114 16
    public function release($token, $moduleName)
115
    {
116 16
        unset($this->tokens[$token][$moduleName]);
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 modules
125
     * that are not connected by an edge in the override graph. In other words,
126
     * if two modules 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
Documentation introduced by
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 ModuleConflict[] 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
            $moduleNames = array_keys($this->tokens[$token]);
150
151
            // Token claimed by only one module
152 64
            if (1 === count($moduleNames)) {
153 58
                continue;
154
            }
155
156 38
            $sortedNames = $this->overrideGraph->getSortedModuleNames($moduleNames);
157 38
            $conflictingNames = array();
158
159
            // An edge must exist between each module 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 ModuleConflict($token, array_keys($conflictingNames));
170
            }
171
        }
172
173 73
        return $conflicts;
174
    }
175
}
176