SyncRepositoryPath::execute()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
ccs 5
cts 6
cp 0.8333
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 2.0185
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filesystemPathsBefore of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
Bug Best Practice introduced by
The expression $filesystemPathsAfter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
110 6
            $this->add($this->repositoryPath, $filesystemPathsAfter);
111 20
        } elseif ($filesystemPathsBefore && !$filesystemPathsAfter) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filesystemPathsBefore of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
Bug Best Practice introduced by
The expression $filesystemPathsAfter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
112 5
            $this->remove($this->repositoryPath);
113 15
        } elseif ($filesystemPathsBefore && $filesystemPathsAfter) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filesystemPathsBefore of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
Bug Best Practice introduced by
The expression $filesystemPathsAfter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filesystemPaths of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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