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\Repository\Mapping; |
13
|
|
|
|
14
|
|
|
use Exception; |
15
|
|
|
use LogicException; |
16
|
|
|
use Puli\Manager\Api\Repository\PathMapping; |
17
|
|
|
use Puli\Manager\Conflict\DependencyGraph; |
18
|
|
|
use Puli\Manager\Transaction\AtomicOperation; |
19
|
|
|
use Puli\Repository\Api\EditableRepository; |
20
|
|
|
use Puli\Repository\Resource\DirectoryResource; |
21
|
|
|
use Puli\Repository\Resource\FileResource; |
22
|
|
|
use Webmozart\PathUtil\Path; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Synchronizes all mappings for a repository path with the repository. |
26
|
|
|
* |
27
|
|
|
* @since 1.0 |
28
|
|
|
* |
29
|
|
|
* @author Bernhard Schussek <[email protected]> |
30
|
|
|
*/ |
31
|
|
|
class SyncRepositoryPath implements AtomicOperation |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
private $repositoryPath; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var EditableRepository |
40
|
|
|
*/ |
41
|
|
|
private $repo; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var PathMappingCollection |
45
|
|
|
*/ |
46
|
|
|
private $mappings; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var DependencyGraph |
50
|
|
|
*/ |
51
|
|
|
private $overrideGraph; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var string[][][] |
55
|
|
|
*/ |
56
|
|
|
private $enabledFilesystemPathsBefore; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var string[][][] |
60
|
|
|
*/ |
61
|
|
|
private $enabledFilesystemPathsAfter; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param string $repositoryPath |
65
|
|
|
* @param EditableRepository $repo |
66
|
|
|
* @param PathMappingCollection $mappings |
67
|
|
|
* @param DependencyGraph $overrideGraph |
68
|
|
|
*/ |
69
|
26 |
|
public function __construct($repositoryPath, EditableRepository $repo, PathMappingCollection $mappings, DependencyGraph $overrideGraph) |
70
|
|
|
{ |
71
|
26 |
|
$this->repositoryPath = $repositoryPath; |
72
|
26 |
|
$this->repo = $repo; |
73
|
26 |
|
$this->mappings = $mappings; |
74
|
26 |
|
$this->overrideGraph = $overrideGraph; |
75
|
26 |
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Records which mappings are currently enabled for the repository path. |
79
|
|
|
*/ |
80
|
26 |
|
public function takeSnapshot() |
81
|
|
|
{ |
82
|
26 |
|
$this->enabledFilesystemPathsBefore = $this->getEnabledFilesystemPaths($this->repositoryPath); |
83
|
26 |
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* {@inheritdoc} |
87
|
|
|
*/ |
88
|
25 |
|
public function execute() |
89
|
|
|
{ |
90
|
25 |
|
if (null === $this->enabledFilesystemPathsBefore) { |
91
|
|
|
throw new LogicException('takeSnapshot() was not called'); |
92
|
|
|
} |
93
|
|
|
|
94
|
25 |
|
$this->enabledFilesystemPathsAfter = $this->getEnabledFilesystemPaths($this->repositoryPath); |
95
|
|
|
|
96
|
25 |
|
$this->sync($this->enabledFilesystemPathsBefore, $this->enabledFilesystemPathsAfter); |
97
|
25 |
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* {@inheritdoc} |
101
|
|
|
*/ |
102
|
6 |
|
public function rollback() |
103
|
|
|
{ |
104
|
6 |
|
$this->sync($this->enabledFilesystemPathsAfter, $this->enabledFilesystemPathsBefore); |
105
|
6 |
|
} |
106
|
|
|
|
107
|
25 |
|
private function sync(array $filesystemPathsBefore, array $filesystemPathsAfter) |
108
|
|
|
{ |
109
|
25 |
|
if (!$filesystemPathsBefore && $filesystemPathsAfter) { |
|
|
|
|
110
|
6 |
|
$this->add($this->repositoryPath, $filesystemPathsAfter); |
111
|
20 |
|
} elseif ($filesystemPathsBefore && !$filesystemPathsAfter) { |
|
|
|
|
112
|
5 |
|
$this->remove($this->repositoryPath); |
113
|
15 |
|
} elseif ($filesystemPathsBefore && $filesystemPathsAfter) { |
|
|
|
|
114
|
14 |
|
$this->replace($filesystemPathsBefore, $filesystemPathsAfter); |
115
|
|
|
} |
116
|
25 |
|
} |
117
|
|
|
|
118
|
14 |
|
private function remove($repositoryPath) |
119
|
|
|
{ |
120
|
14 |
|
$this->repo->remove($repositoryPath); |
121
|
14 |
|
} |
122
|
|
|
|
123
|
15 |
|
private function add($repositoryPath, array $filesystemPaths) |
124
|
|
|
{ |
125
|
|
|
try { |
126
|
15 |
|
$this->addInOrder($filesystemPaths); |
127
|
|
|
} catch (Exception $e) { |
128
|
|
|
$this->repo->remove($repositoryPath); |
129
|
|
|
|
130
|
|
|
throw $e; |
131
|
|
|
} |
132
|
15 |
|
} |
133
|
|
|
|
134
|
20 |
|
private function addInOrder(array $filesystemPaths) |
135
|
|
|
{ |
136
|
20 |
|
foreach ($filesystemPaths as $moduleName => $filesystemPathsByRepoPath) { |
137
|
20 |
|
foreach ($filesystemPathsByRepoPath as $repoPath => $filesystemPaths) { |
138
|
20 |
|
foreach ($filesystemPaths as $filesystemPath) { |
139
|
20 |
|
$this->repo->add($repoPath, $this->createResource($filesystemPath)); |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
} |
143
|
20 |
|
} |
144
|
|
|
|
145
|
14 |
|
private function replace(array $filesystemPathsBefore, array $filesystemPathsAfter) |
146
|
|
|
{ |
147
|
14 |
|
$filesystemPathsAfterSuffix = $filesystemPathsAfter; |
148
|
14 |
|
$filesystemPathsAfterPrefix = array_splice($filesystemPathsAfterSuffix, 0, count($filesystemPathsBefore)); |
149
|
|
|
|
150
|
14 |
|
if ($filesystemPathsBefore === $filesystemPathsAfterPrefix) { |
151
|
|
|
// Optimization: If the module names before are a prefix of the |
152
|
|
|
// module names after, we can simply add the mappings for the |
153
|
|
|
// remaining module names |
154
|
|
|
// Note: array_splice() already removed the prefix of $filesystemPathsAfterSuffix |
155
|
9 |
|
$this->addInOrder($filesystemPathsAfterSuffix); |
156
|
|
|
|
157
|
9 |
|
return; |
158
|
|
|
} |
159
|
|
|
|
160
|
9 |
|
$shortestRepositoryPath = $this->getShortestRepositoryPath($filesystemPathsBefore, $filesystemPathsAfter); |
161
|
|
|
|
162
|
|
|
// Remove the shortest repository path before adding |
163
|
9 |
|
$this->remove($shortestRepositoryPath); |
164
|
|
|
|
165
|
|
|
try { |
166
|
9 |
|
$this->add($shortestRepositoryPath, $filesystemPathsAfter); |
167
|
|
|
} catch (Exception $e) { |
168
|
|
|
try { |
169
|
|
|
// Try to restore the previous paths before failing |
170
|
|
|
$this->add($shortestRepositoryPath, $filesystemPathsBefore); |
171
|
|
|
} catch (Exception $e) { |
172
|
|
|
// We are already handling an exception |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
throw $e; |
176
|
|
|
} |
177
|
9 |
|
} |
178
|
|
|
|
179
|
20 |
|
private function createResource($filesystemPath) |
180
|
|
|
{ |
181
|
20 |
|
return is_dir($filesystemPath) |
182
|
19 |
|
? new DirectoryResource($filesystemPath) |
183
|
20 |
|
: new FileResource($filesystemPath); |
184
|
|
|
} |
185
|
|
|
|
186
|
9 |
|
private function getShortestRepositoryPath(array $filesystemPathsBefore, array $filesystemPathsAfter) |
187
|
|
|
{ |
188
|
9 |
|
$repositoryPaths = array(); |
189
|
|
|
|
190
|
9 |
|
foreach ($filesystemPathsBefore as $filesystemPathsByRepoPaths) { |
191
|
9 |
|
foreach ($filesystemPathsByRepoPaths as $repoPath => $filesystemPath) { |
192
|
9 |
|
$repositoryPaths[$repoPath] = true; |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
9 |
|
foreach ($filesystemPathsAfter as $filesystemPathsByRepoPaths) { |
197
|
9 |
|
foreach ($filesystemPathsByRepoPaths as $repoPath => $filesystemPath) { |
198
|
9 |
|
$repositoryPaths[$repoPath] = true; |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
202
|
9 |
|
ksort($repositoryPaths); |
203
|
9 |
|
reset($repositoryPaths); |
204
|
|
|
|
205
|
9 |
|
return key($repositoryPaths); |
206
|
|
|
} |
207
|
|
|
|
208
|
26 |
|
private function getEnabledFilesystemPaths($repositoryPath) |
209
|
|
|
{ |
210
|
|
|
// Get a copy so that we can remove the entries that we processed |
211
|
|
|
// already |
212
|
26 |
|
$inMappings = $this->mappings->toArray(); |
213
|
26 |
|
$outMappings = array(); |
214
|
26 |
|
$filesystemPaths = array(); |
215
|
|
|
|
216
|
26 |
|
$this->filterEnabledMappings($repositoryPath, $inMappings, $outMappings); |
217
|
|
|
|
218
|
26 |
|
foreach ($outMappings as $mappingPath => $mappingsByModule) { |
219
|
24 |
|
foreach ($mappingsByModule as $moduleName => $mapping) { |
220
|
24 |
|
$filesystemPaths[$moduleName][$mappingPath] = $mapping->getFilesystemPaths(); |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
224
|
26 |
|
if (!$filesystemPaths) { |
|
|
|
|
225
|
12 |
|
return array(); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
// Sort primary keys (module names) |
229
|
24 |
|
$sortedNames = $this->overrideGraph->getSortedModuleNames(array_keys($filesystemPaths)); |
230
|
24 |
|
$filesystemPaths = array_replace(array_flip($sortedNames), $filesystemPaths); |
231
|
|
|
|
232
|
|
|
// Sort secondary keys (repository paths) |
233
|
24 |
|
foreach ($filesystemPaths as $moduleName => $pathsByModule) { |
234
|
24 |
|
ksort($filesystemPaths[$moduleName]); |
235
|
|
|
} |
236
|
|
|
|
237
|
24 |
|
return $filesystemPaths; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @param string $repositoryPath |
242
|
|
|
* @param PathMapping[][] $inMappings |
243
|
|
|
* @param PathMapping[][] $outMappings |
244
|
|
|
* @param bool[] $processedPaths |
245
|
|
|
*/ |
246
|
26 |
|
private function filterEnabledMappings($repositoryPath, array &$inMappings, array &$outMappings, array &$processedPaths = array()) |
247
|
|
|
{ |
248
|
26 |
|
$repositoryPaths = array(); |
249
|
26 |
|
$processedPaths[$repositoryPath] = true; |
250
|
|
|
|
251
|
26 |
|
foreach ($inMappings as $mappingPath => $mappingsByModule) { |
252
|
25 |
|
foreach ($mappingsByModule as $moduleName => $mapping) { |
253
|
25 |
|
if (!$mapping->isEnabled()) { |
254
|
5 |
|
continue; |
255
|
|
|
} |
256
|
|
|
|
257
|
24 |
|
$nestedMappingPaths = $mapping->listRepositoryPaths(); |
258
|
|
|
|
259
|
|
|
// Check that the mapping actually contains the path |
260
|
24 |
|
if (!Path::isBasePath($repositoryPath, $mappingPath) && !in_array($repositoryPath, $nestedMappingPaths, true)) { |
261
|
3 |
|
continue; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
// Don't check this mapping anymore in recursive calls |
265
|
24 |
|
unset($inMappings[$mappingPath][$moduleName]); |
266
|
|
|
|
267
|
24 |
|
if (empty($inMappings[$mappingPath])) { |
268
|
24 |
|
unset($inMappings[$mappingPath]); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
// Add mapping to output |
272
|
24 |
|
$outMappings[$mappingPath][$moduleName] = $mapping; |
273
|
|
|
|
274
|
24 |
|
foreach ($nestedMappingPaths as $nestedMappingPath) { |
275
|
25 |
|
$repositoryPaths[$nestedMappingPath] = true; |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
// Continue to search for mappings for the repository paths we collected |
281
|
|
|
// already until there are no more mappings |
282
|
26 |
|
if (!empty($inMappings)) { |
283
|
8 |
|
foreach ($repositoryPaths as $nestedMappingPath => $true) { |
284
|
|
|
// Don't process paths twice |
285
|
5 |
|
if (!isset($processedPaths[$nestedMappingPath])) { |
286
|
5 |
|
$this->filterEnabledMappings($nestedMappingPath, $inMappings, $outMappings, $processedPaths); |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
} |
290
|
26 |
|
} |
291
|
|
|
} |
292
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.