Completed
Push — master ( 09b86b...ba0798 )
by Andreas
20:05 queued 12s
created

CollectionPersister::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 7
cp 0.8571
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2.0116
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Persisters;
6
7
use Closure;
8
use Doctrine\ODM\MongoDB\DocumentManager;
9
use Doctrine\ODM\MongoDB\LockException;
10
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
11
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface;
12
use Doctrine\ODM\MongoDB\UnitOfWork;
13
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
14
use LogicException;
15
use UnexpectedValueException;
16
use const E_USER_DEPRECATED;
17
use function array_diff_key;
18
use function array_fill_keys;
19
use function array_flip;
20
use function array_intersect_key;
21
use function array_keys;
22
use function array_map;
23
use function array_reverse;
24
use function array_values;
25
use function assert;
26
use function count;
27
use function end;
28
use function get_class;
29
use function implode;
30
use function sort;
31
use function sprintf;
32
use function strpos;
33
use function trigger_error;
34
35
/**
36
 * The CollectionPersister is responsible for persisting collections of embedded
37
 * or referenced documents. When a PersistentCollection is scheduledForDeletion
38
 * in the UnitOfWork by calling PersistentCollection::clear() or is
39
 * de-referenced in the domain application code, CollectionPersister::delete()
40
 * will be called. When documents within the PersistentCollection are added or
41
 * removed, CollectionPersister::update() will be called, which may set the
42
 * entire collection or delete/insert individual elements, depending on the
43
 * mapping strategy.
44
 *
45
 * @internal
46
 *
47
 * @final
48
 */
49
class CollectionPersister
50
{
51
    /** @var DocumentManager */
52
    private $dm;
53
54
    /** @var PersistenceBuilder */
55
    private $pb;
56
57
    /** @var UnitOfWork */
58
    private $uow;
59
60 1133
    public function __construct(DocumentManager $dm, PersistenceBuilder $pb, UnitOfWork $uow)
61
    {
62 1133
        if (self::class !== static::class) {
63
            @trigger_error(sprintf('The class "%s" extends "%s" which will be final in MongoDB ODM 2.0.', static::class, self::class), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
64
        }
65 1133
        $this->dm  = $dm;
66 1133
        $this->pb  = $pb;
67 1133
        $this->uow = $uow;
68 1133
    }
69
70
    /**
71
     * Deletes a PersistentCollection instances completely from a document using $unset.
72
     *
73
     * @param PersistentCollectionInterface[] $collections
74
     * @param array                           $options
75
     */
76 38
    public function delete(object $parent, array $collections, array $options) : void
77
    {
78 38
        $unsetPathsMap = [];
79
80 38
        foreach ($collections as $collection) {
81 38
            $mapping = $collection->getMapping();
82 38
            if ($mapping['isInverseSide']) {
83
                continue; // ignore inverse side
84
            }
85 38
            if (CollectionHelper::isAtomic($mapping['strategy'])) {
86
                throw new UnexpectedValueException($mapping['strategy'] . ' delete collection strategy should have been handled by DocumentPersister. Please report a bug in issue tracker');
87
            }
88 38
            [$propertyPath]               = $this->getPathAndParent($collection);
0 ignored issues
show
Bug introduced by
The variable $propertyPath does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
89 38
            $unsetPathsMap[$propertyPath] = true;
90
        }
91
92 38
        if (empty($unsetPathsMap)) {
93
            return;
94
        }
95
96
        /** @var string[] $unsetPaths */
97 38
        $unsetPaths = array_keys($unsetPathsMap);
98
99 38
        $unsetPaths = array_fill_keys($this->excludeSubPaths($unsetPaths), true);
100 38
        $query      = ['$unset' => $unsetPaths];
101 38
        $this->executeQuery($parent, $query, $options);
102 37
    }
103
104
    /**
105
     * Updates a list PersistentCollection instances deleting removed rows and inserting new rows.
106
     *
107
     * @param PersistentCollectionInterface[] $collections
108
     * @param array                           $options
109
     */
110 106
    public function update(object $parent, array $collections, array $options) : void
111
    {
112 106
        $setStrategyColls     = [];
113 106
        $addPushStrategyColls = [];
114
115 106
        foreach ($collections as $coll) {
116 106
            $mapping = $coll->getMapping();
117
118 106
            if ($mapping['isInverseSide']) {
119
                continue; // ignore inverse side
120
            }
121 106
            switch ($mapping['strategy']) {
122
                case ClassMetadata::STORAGE_STRATEGY_ATOMIC_SET:
123
                case ClassMetadata::STORAGE_STRATEGY_ATOMIC_SET_ARRAY:
124
                    throw new UnexpectedValueException($mapping['strategy'] . ' update collection strategy should have been handled by DocumentPersister. Please report a bug in issue tracker');
125
126
                case ClassMetadata::STORAGE_STRATEGY_SET:
127
                case ClassMetadata::STORAGE_STRATEGY_SET_ARRAY:
128 14
                    $setStrategyColls[] = $coll;
129 14
                    break;
130
131
                case ClassMetadata::STORAGE_STRATEGY_ADD_TO_SET:
132
                case ClassMetadata::STORAGE_STRATEGY_PUSH_ALL:
133 97
                    $addPushStrategyColls[] = $coll;
134 97
                    break;
135
136
                default:
137
                    throw new UnexpectedValueException('Unsupported collection strategy: ' . $mapping['strategy']);
138
            }
139
        }
140
141 106
        if (! empty($setStrategyColls)) {
142 14
            $this->setCollections($parent, $setStrategyColls, $options);
143
        }
144 106
        if (empty($addPushStrategyColls)) {
145 9
            return;
146
        }
147
148 97
        $this->deleteElements($parent, $addPushStrategyColls, $options);
149 97
        $this->insertElements($parent, $addPushStrategyColls, $options);
150 96
    }
151
152
    /**
153
     * Sets a list of PersistentCollection instances.
154
     *
155
     * This method is intended to be used with the "set" or "setArray"
156
     * strategies. The "setArray" strategy will ensure that the collections is
157
     * set as a BSON array, which means the collections elements will be
158
     * reindexed numerically before storage.
159
     *
160
     * @param PersistentCollectionInterface[] $collections
161
     * @param array                           $options
162
     */
163 14
    private function setCollections(object $parent, array $collections, array $options) : void
164
    {
165 14
        $pathCollMap = [];
166 14
        $paths       = [];
167 14
        foreach ($collections as $coll) {
168 14
            [$propertyPath ]            = $this->getPathAndParent($coll);
0 ignored issues
show
Bug introduced by
The variable $propertyPath seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
169 14
            $pathCollMap[$propertyPath] = $coll;
0 ignored issues
show
Bug introduced by
The variable $propertyPath seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
170 14
            $paths[]                    = $propertyPath;
0 ignored issues
show
Bug introduced by
The variable $propertyPath seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
171
        }
172
173 14
        $paths = $this->excludeSubPaths($paths);
174
        /** @var PersistentCollectionInterface[] $setColls */
175 14
        $setColls   = array_intersect_key($pathCollMap, array_flip($paths));
176 14
        $setPayload = [];
177 14
        foreach ($setColls as $propertyPath => $coll) {
178 14
            $coll->initialize();
179 14
            $mapping                   = $coll->getMapping();
180 14
            $setData                   = $this->pb->prepareAssociatedCollectionValue(
181 14
                $coll,
182 14
                CollectionHelper::usesSet($mapping['strategy'])
183
            );
184 14
            $setPayload[$propertyPath] = $setData;
185
        }
186 14
        if (empty($setPayload)) {
187
            return;
188
        }
189
190 14
        $query = ['$set' => $setPayload];
191 14
        $this->executeQuery($parent, $query, $options);
192 14
    }
193
194
    /**
195
     * Deletes removed elements from a list of PersistentCollection instances.
196
     *
197
     * This method is intended to be used with the "pushAll" and "addToSet" strategies.
198
     *
199
     * @param PersistentCollectionInterface[] $collections
200
     * @param array                           $options
201
     */
202 97
    private function deleteElements(object $parent, array $collections, array $options) : void
203
    {
204 97
        $pathCollMap   = [];
205 97
        $paths         = [];
206 97
        $deleteDiffMap = [];
207
208 97
        foreach ($collections as $coll) {
209 97
            $coll->initialize();
210 97
            if (! $this->uow->isCollectionScheduledForUpdate($coll)) {
211 2
                continue;
212
            }
213 95
            $deleteDiff = $coll->getDeleteDiff();
214
215 95
            if (empty($deleteDiff)) {
216 73
                continue;
217
            }
218 31
            [$propertyPath ] = $this->getPathAndParent($coll);
0 ignored issues
show
Bug introduced by
The variable $propertyPath seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
219
220 31
            $pathCollMap[$propertyPath]   = $coll;
0 ignored issues
show
Bug introduced by
The variable $propertyPath seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
221 31
            $paths[]                      = $propertyPath;
0 ignored issues
show
Bug introduced by
The variable $propertyPath seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
222 31
            $deleteDiffMap[$propertyPath] = $deleteDiff;
0 ignored issues
show
Bug introduced by
The variable $propertyPath seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
223
        }
224
225 97
        $paths        = $this->excludeSubPaths($paths);
226 97
        $deleteColls  = array_intersect_key($pathCollMap, array_flip($paths));
227 97
        $unsetPayload = [];
228 97
        $pullPayload  = [];
229 97
        foreach ($deleteColls as $propertyPath => $coll) {
230 31
            $deleteDiff = $deleteDiffMap[$propertyPath];
231 31
            foreach ($deleteDiff as $key => $document) {
232 31
                $unsetPayload[$propertyPath . '.' . $key] = true;
233
            }
234 31
            $pullPayload[$propertyPath] = null;
235
        }
236
237 97
        if (! empty($unsetPayload)) {
238 31
            $this->executeQuery($parent, ['$unset' => $unsetPayload], $options);
239
        }
240 97
        if (empty($pullPayload)) {
241 75
            return;
242
        }
243
244
        /**
245
         * @todo This is a hack right now because we don't have a proper way to
246
         * remove an element from an array by its key. Unsetting the key results
247
         * in the element being left in the array as null so we have to pull
248
         * null values.
249
         */
250 31
        $this->executeQuery($parent, ['$pull' => $pullPayload], $options);
251 31
    }
252
253
    /**
254
     * Inserts new elements for a PersistentCollection instances.
255
     *
256
     * This method is intended to be used with the "pushAll" and "addToSet" strategies.
257
     *
258
     * @param PersistentCollectionInterface[] $collections
259
     * @param array                           $options
260
     */
261 97
    private function insertElements(object $parent, array $collections, array $options) : void
262
    {
263 97
        $pushAllPathCollMap  = [];
264 97
        $addToSetPathCollMap = [];
265 97
        $pushAllPaths        = [];
266 97
        $addToSetPaths       = [];
267 97
        $diffsMap            = [];
268
269 97
        foreach ($collections as $coll) {
270 97
            $coll->initialize();
271 97
            if (! $this->uow->isCollectionScheduledForUpdate($coll)) {
272 2
                continue;
273
            }
274 95
            $insertDiff = $coll->getInsertDiff();
275
276 95
            if (empty($insertDiff)) {
277 21
                continue;
278
            }
279
280 80
            $mapping  = $coll->getMapping();
281 80
            $strategy = $mapping['strategy'];
282
283 80
            [$propertyPath ]         = $this->getPathAndParent($coll);
0 ignored issues
show
Bug introduced by
The variable $propertyPath does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
284 80
            $diffsMap[$propertyPath] = $insertDiff;
285
286 80
            switch ($strategy) {
287
                case ClassMetadata::STORAGE_STRATEGY_PUSH_ALL:
288 75
                    $pushAllPathCollMap[$propertyPath] = $coll;
289 75
                    $pushAllPaths[]                    = $propertyPath;
290 75
                    break;
291
292
                case ClassMetadata::STORAGE_STRATEGY_ADD_TO_SET:
293 7
                    $addToSetPathCollMap[$propertyPath] = $coll;
294 7
                    $addToSetPaths[]                    = $propertyPath;
295 7
                    break;
296
297
                default:
298
                    throw new LogicException('Invalid strategy ' . $strategy . ' given for insertCollections');
299
            }
300
        }
301
302 97
        if (! empty($pushAllPaths)) {
303 75
            $this->pushAllCollections(
304 75
                $parent,
305 75
                $pushAllPaths,
306 75
                $pushAllPathCollMap,
307 75
                $diffsMap,
308 75
                $options
309
            );
310
        }
311 96
        if (empty($addToSetPaths)) {
312 90
            return;
313
        }
314
315 7
        $this->addToSetCollections(
316 7
            $parent,
317 7
            $addToSetPaths,
318 7
            $addToSetPathCollMap,
319 7
            $diffsMap,
320 7
            $options
321
        );
322 7
    }
323
324
    /**
325
     * Perform collections update for 'pushAll' strategy.
326
     *
327
     * @param object $parent       Parent object to which passed collections is belong.
328
     * @param array  $collsPaths   Paths of collections that is passed.
329
     * @param array  $pathCollsMap List of collections indexed by their paths.
330
     * @param array  $diffsMap     List of collection diffs indexed by collections paths.
331
     * @param array  $options
332
     */
333 75
    private function pushAllCollections(object $parent, array $collsPaths, array $pathCollsMap, array $diffsMap, array $options) : void
334
    {
335 75
        $pushAllPaths = $this->excludeSubPaths($collsPaths);
336
        /** @var PersistentCollectionInterface[] $pushAllColls */
337 75
        $pushAllColls   = array_intersect_key($pathCollsMap, array_flip($pushAllPaths));
338 75
        $pushAllPayload = [];
339 75
        foreach ($pushAllColls as $propertyPath => $coll) {
340 75
            $callback                      = $this->getValuePrepareCallback($coll);
341 75
            $value                         = array_values(array_map($callback, $diffsMap[$propertyPath]));
342 75
            $pushAllPayload[$propertyPath] = ['$each' => $value];
343
        }
344
345 75
        if (! empty($pushAllPayload)) {
346 75
            $this->executeQuery($parent, ['$push' => $pushAllPayload], $options);
347
        }
348
349 74
        $pushAllColls = array_diff_key($pathCollsMap, array_flip($pushAllPaths));
350 74
        foreach ($pushAllColls as $propertyPath => $coll) {
351 2
            $callback = $this->getValuePrepareCallback($coll);
352 2
            $value    = array_values(array_map($callback, $diffsMap[$propertyPath]));
353 2
            $query    = ['$push' => [$propertyPath => ['$each' => $value]]];
354 2
            $this->executeQuery($parent, $query, $options);
355
        }
356 74
    }
357
358
    /**
359
     * Perform collections update by 'addToSet' strategy.
360
     *
361
     * @param object $parent       Parent object to which passed collections is belong.
362
     * @param array  $collsPaths   Paths of collections that is passed.
363
     * @param array  $pathCollsMap List of collections indexed by their paths.
364
     * @param array  $diffsMap     List of collection diffs indexed by collections paths.
365
     * @param array  $options
366
     */
367 7
    private function addToSetCollections(object $parent, array $collsPaths, array $pathCollsMap, array $diffsMap, array $options) : void
368
    {
369 7
        $addToSetPaths = $this->excludeSubPaths($collsPaths);
370
        /** @var PersistentCollectionInterface[] $addToSetColls */
371 7
        $addToSetColls = array_intersect_key($pathCollsMap, array_flip($addToSetPaths));
372
373 7
        $addToSetPayload = [];
374 7
        foreach ($addToSetColls as $propertyPath => $coll) {
375 7
            $callback                       = $this->getValuePrepareCallback($coll);
376 7
            $value                          = array_values(array_map($callback, $diffsMap[$propertyPath]));
377 7
            $addToSetPayload[$propertyPath] = ['$each' => $value];
378
        }
379
380 7
        if (empty($addToSetPayload)) {
381
            return;
382
        }
383
384 7
        $this->executeQuery($parent, ['$addToSet' => $addToSetPayload], $options);
385 7
    }
386
387
    /**
388
     * Return callback instance for specified collection. This callback will prepare values for query from documents
389
     * that collection contain.
390
     */
391 80
    private function getValuePrepareCallback(PersistentCollectionInterface $coll) : Closure
392
    {
393 80
        $mapping = $coll->getMapping();
394 80
        if (isset($mapping['embedded'])) {
395
            return function ($v) use ($mapping) {
396 44
                return $this->pb->prepareEmbeddedDocumentValue($mapping, $v);
397 44
            };
398
        }
399
400
        return function ($v) use ($mapping) {
401 37
            return $this->pb->prepareReferencedDocumentValue($mapping, $v);
402 37
        };
403
    }
404
405
    /**
406
     * Gets the parent information for a given PersistentCollection. It will
407
     * retrieve the top-level persistent Document that the PersistentCollection
408
     * lives in. We can use this to issue queries when updating a
409
     * PersistentCollection that is multiple levels deep inside an embedded
410
     * document.
411
     *
412
     *     <code>
413
     *     list($path, $parent) = $this->getPathAndParent($coll)
414
     *     </code>
415
     */
416 118
    private function getPathAndParent(PersistentCollectionInterface $coll) : array
417
    {
418 118
        $mapping = $coll->getMapping();
419 118
        $fields  = [];
420 118
        $parent  = $coll->getOwner();
421 118
        while (($association = $this->uow->getParentAssociation($parent)) !== null) {
422 18
            [$m, $owner, $field] = $association;
0 ignored issues
show
Bug introduced by
The variable $m does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $owner does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $field does not exist. Did you mean $fields?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
423 18
            if (isset($m['reference'])) {
424
                break;
425
            }
426 18
            $parent   = $owner;
427 18
            $fields[] = $field;
0 ignored issues
show
Bug introduced by
The variable $field does not exist. Did you mean $fields?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
428
        }
429 118
        $propertyPath = implode('.', array_reverse($fields));
430 118
        $path         = $mapping['name'];
431 118
        if ($propertyPath) {
432 18
            $path = $propertyPath . '.' . $path;
433
        }
434 118
        return [$path, $parent];
435
    }
436
437
    /**
438
     * Executes a query updating the given document.
439
     */
440 118
    private function executeQuery(object $document, array $newObj, array $options) : void
441
    {
442 118
        $className = get_class($document);
443 118
        $class     = $this->dm->getClassMetadata($className);
444 118
        $id        = $class->getDatabaseIdentifierValue($this->uow->getDocumentIdentifier($document));
445 118
        $query     = ['_id' => $id];
446 118
        if ($class->isVersioned) {
447 5
            $query[$class->fieldMappings[$class->versionField]['name']] = $class->reflFields[$class->versionField]->getValue($document);
448
        }
449 118
        $collection = $this->dm->getDocumentCollection($className);
450 118
        $result     = $collection->updateOne($query, $newObj, $options);
451 118
        if ($class->isVersioned && ! $result->getMatchedCount()) {
452 2
            throw LockException::lockFailed($document);
453
        }
454 116
    }
455
456
    /**
457
     * Remove from passed paths list all sub-paths.
458
     *
459
     * @param string[] $paths
460
     *
461
     * @return string[]
462
     */
463 119
    private function excludeSubPaths(array $paths) : array
464
    {
465 119
        if (empty($paths)) {
466 75
            return $paths;
467
        }
468 118
        sort($paths);
469 118
        $uniquePaths = [$paths[0]];
470 118
        for ($i = 1, $count = count($paths); $i < $count; ++$i) {
471 12
            $lastUniquePath = end($uniquePaths);
472 12
            assert($lastUniquePath !== false);
473
474 12
            if (strpos($paths[$i], $lastUniquePath) === 0) {
475 10
                continue;
476
            }
477
478 4
            $uniquePaths[] = $paths[$i];
479
        }
480
481 118
        return $uniquePaths;
482
    }
483
}
484