Completed
Pull Request — develop (#423)
by
unknown
13:26
created

RoutableSubscriber::getSubscribedEvents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 7
cts 7
cp 1
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/*
4
 * This file is part of Sulu.
5
 *
6
 * (c) Sulu GmbH
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Sulu\Bundle\ArticleBundle\Document\Subscriber;
13
14
use Doctrine\ORM\EntityManagerInterface;
15
use PHPCR\ItemNotFoundException;
16
use PHPCR\SessionInterface;
17
use Sulu\Bundle\ArticleBundle\Document\Behavior\RoutableBehavior;
18
use Sulu\Bundle\ArticleBundle\Document\Behavior\RoutablePageBehavior;
19
use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
20
use Sulu\Bundle\DocumentManagerBundle\Bridge\PropertyEncoder;
21
use Sulu\Bundle\RouteBundle\Entity\RouteRepositoryInterface;
22
use Sulu\Bundle\RouteBundle\Generator\ChainRouteGeneratorInterface;
23
use Sulu\Bundle\RouteBundle\Manager\ConflictResolverInterface;
24
use Sulu\Bundle\RouteBundle\Manager\RouteManagerInterface;
25
use Sulu\Bundle\RouteBundle\Model\RouteInterface;
26
use Sulu\Component\Content\Metadata\Factory\StructureMetadataFactoryInterface;
27
use Sulu\Component\DocumentManager\Behavior\Mapping\ChildrenBehavior;
28
use Sulu\Component\DocumentManager\Behavior\Mapping\ParentBehavior;
29
use Sulu\Component\DocumentManager\DocumentManagerInterface;
30
use Sulu\Component\DocumentManager\Event\AbstractMappingEvent;
31
use Sulu\Component\DocumentManager\Event\CopyEvent;
32
use Sulu\Component\DocumentManager\Event\PublishEvent;
33
use Sulu\Component\DocumentManager\Event\RemoveEvent;
34
use Sulu\Component\DocumentManager\Event\ReorderEvent;
35
use Sulu\Component\DocumentManager\Events;
36
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
37
38
/**
39
 * Handles document-manager events to create/update/remove routes.
40
 */
41
class RoutableSubscriber implements EventSubscriberInterface
42
{
43
    const ROUTE_FIELD = 'routePath';
44
45
    const ROUTES_PROPERTY = 'suluRoutes';
46
47
    const TAG_NAME = 'sulu_article.article_route';
48
49
    /**
50
     * @var ChainRouteGeneratorInterface
51
     */
52
    private $chainRouteGenerator;
53
54
    /**
55
     * @var RouteManagerInterface
56
     */
57
    private $routeManager;
58
59
    /**
60
     * @var RouteRepositoryInterface
61
     */
62
    private $routeRepository;
63
64
    /**
65
     * @var EntityManagerInterface
66
     */
67
    private $entityManager;
68
69
    /**
70
     * @var DocumentManagerInterface
71
     */
72
    private $documentManager;
73
74
    /**
75
     * @var DocumentInspector
76
     */
77
    private $documentInspector;
78
79
    /**
80
     * @var PropertyEncoder
81
     */
82
    private $propertyEncoder;
83
84
    /**
85
     * @var StructureMetadataFactoryInterface
86
     */
87
    private $metadataFactory;
88
89
    /**
90
     * @var ConflictResolverInterface
91
     */
92
    private $conflictResolver;
93
94
    /**
95
     * @param ChainRouteGeneratorInterface $chainRouteGenerator
96
     * @param RouteManagerInterface $routeManager
97
     * @param RouteRepositoryInterface $routeRepository
98
     * @param EntityManagerInterface $entityManager
99
     * @param DocumentManagerInterface $documentManager
100
     * @param DocumentInspector $documentInspector
101
     * @param PropertyEncoder $propertyEncoder
102
     * @param StructureMetadataFactoryInterface $metadataFactory
103
     * @param ConflictResolverInterface $conflictResolver
104
     */
105 62
    public function __construct(
106
        ChainRouteGeneratorInterface $chainRouteGenerator,
107
        RouteManagerInterface $routeManager,
108
        RouteRepositoryInterface $routeRepository,
109
        EntityManagerInterface $entityManager,
110
        DocumentManagerInterface $documentManager,
111
        DocumentInspector $documentInspector,
112
        PropertyEncoder $propertyEncoder,
113
        StructureMetadataFactoryInterface $metadataFactory,
114
        ConflictResolverInterface $conflictResolver
115
    ) {
116 62
        $this->chainRouteGenerator = $chainRouteGenerator;
117 62
        $this->routeManager = $routeManager;
118 62
        $this->routeRepository = $routeRepository;
119 62
        $this->entityManager = $entityManager;
120 62
        $this->documentManager = $documentManager;
121 62
        $this->documentInspector = $documentInspector;
122 62
        $this->propertyEncoder = $propertyEncoder;
123 62
        $this->metadataFactory = $metadataFactory;
124 62
        $this->conflictResolver = $conflictResolver;
125 62
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130 55
    public static function getSubscribedEvents()
131
    {
132
        return [
133 55
            Events::PERSIST => [
134 55
                // low priority because all other subscriber should be finished
135
                ['handlePersist', -2048],
136
            ],
137
            Events::REMOVE => [
138 55
                // high priority to ensure nodes are not deleted until we iterate over children
139
                ['handleRemove', 2048],
140
            ],
141
            Events::PUBLISH => ['handlePublish', -2048],
142 55
            Events::REORDER => ['handleReorder', -1024],
143 55
            Events::COPY => ['handleCopy', -2048],
144 55
        ];
145
    }
146
147
    /**
148
     * Generate route and save route-path.
149
     *
150
     * @param AbstractMappingEvent $event
151
     */
152
    public function handlePersist(AbstractMappingEvent $event)
153 55
    {
154
        $document = $event->getDocument();
155 55
156 55
        if (!$document instanceof RoutablePageBehavior || !$document instanceof ChildrenBehavior) {
0 ignored issues
show
Bug introduced by
The class Sulu\Component\DocumentM...apping\ChildrenBehavior does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
157 52
            return;
158
        }
159
160 54
        $this->updateChildRoutes($document);
161 54
    }
162 54
163
    /**
164 54
     * Regenerate routes for siblings on reorder.
165 54
     *
166 20
     * @param ReorderEvent $event
167
     */
168 54
    public function handleReorder(ReorderEvent $event)
169
    {
170
        $document = $event->getDocument();
171
        if (!$document instanceof RoutablePageBehavior || !$document instanceof ParentBehavior) {
0 ignored issues
show
Bug introduced by
The class Sulu\Component\DocumentM...\Mapping\ParentBehavior does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
172
            return;
173
        }
174
175 56
        $parentDocument = $document->getParent();
176
        if (!$parentDocument instanceof ChildrenBehavior) {
0 ignored issues
show
Bug introduced by
The class Sulu\Component\DocumentM...apping\ChildrenBehavior does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
177 56
            return;
178 56
        }
179 9
180
        $this->updateChildRoutes($parentDocument);
181
    }
182 55
183
    /**
184 55
     * Handle publish event and generate route and the child-routes.
185 55
     *
186
     * @param PublishEvent $event
187 55
     */
188 55
    public function handlePublish(PublishEvent $event)
189
    {
190 55
        $document = $event->getDocument();
191 55
        if (!$document instanceof RoutableBehavior) {
192
            return;
193
        }
194
195
        $propertyName = $this->getPropertyName($event->getLocale(), self::ROUTES_PROPERTY);
196
197
        // check if nodes previous generated routes exists and remove them if not
198 2
        $oldRoutes = $event->getNode()->getPropertyValueWithDefault($propertyName, []);
199
        $this->removeOldChildRoutes($event->getNode()->getSession(), $oldRoutes, $event->getLocale());
200 2
201 2
        $routes = [];
202
        if ($document instanceof ChildrenBehavior) {
0 ignored issues
show
Bug introduced by
The class Sulu\Component\DocumentM...apping\ChildrenBehavior does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
203
            // generate new routes of children
204
            $routes = $this->generateChildRoutes($document, $event->getLocale());
205 2
        }
206 2
207
        // save the newly generated routes of children
208
        $event->getNode()->setProperty($propertyName, $routes);
209
        $this->entityManager->flush();
210 2
    }
211 2
212 2
    /**
213 2
     * Removes route.
214
     *
215 2
     * @param RemoveEvent $event
216 2
     */
217
    public function handleRemove(RemoveEvent $event)
218 2
    {
219
        $document = $event->getDocument();
220 2
        if (!$document instanceof RoutableBehavior || !$document instanceof ChildrenBehavior) {
0 ignored issues
show
Bug introduced by
The class Sulu\Component\DocumentM...apping\ChildrenBehavior does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
221
            return;
222
        }
223
224
        $locales = $this->documentInspector->getLocales($document);
225
        foreach ($locales as $locale) {
226
            $this->removeChildRoutes($document, $locale);
227
        }
228
229 24
        $this->entityManager->flush();
230
    }
231 24
232 24
    /**
233 9
     * Update routes for copied article.
234
     *
235
     * @param CopyEvent $event
236 18
     */
237
    public function handleCopy(CopyEvent $event)
238
    {
239 18
        $document = $event->getDocument();
240
        if (!$document instanceof RoutableBehavior) {
241
            return;
242
        }
243
244 18
        $locales = $this->documentInspector->getLocales($document);
245 18
        foreach ($locales as $locale) {
246
            $localizedDocument = $this->documentManager->find($event->getCopiedPath(), $locale);
247 18
248 18
            if ($localizedDocument instanceof ChildrenBehavior) {
0 ignored issues
show
Bug introduced by
The class Sulu\Component\DocumentM...apping\ChildrenBehavior does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
249 18
                $this->generateChildRoutes($localizedDocument, $locale);
250
            }
251
        }
252 18
    }
253
254
    /**
255 18
     * Create or update for given document.
256 18
     *
257
     * @param RoutablePageBehavior $document
258 18
     * @param string $locale
259 18
     *
260
     * @return RouteInterface
261 18
     */
262
    private function createOrUpdatePageRoute(RoutablePageBehavior $document, $locale)
263
    {
264
        $route = $this->reallocateExistingRoute($document, $locale);
265 18
        if ($route) {
266 18
            return $route;
267 18
        }
268
269
        $route = $document->getRoute();
270
        if (!$route) {
271
            $route = $this->routeRepository->findByEntity($document->getClass(), $document->getUuid(), $locale);
272
        }
273
274
        if ($route && $route->getEntityId() !== $document->getId()) {
275
            // Mismatch of entity-id's happens because doctrine don't check entities which has been changed in the
276
            // current session.
277
278
            $document->removeRoute();
279
            $route = null;
280
        }
281
282
        if ($route) {
283
            $document->setRoute($route);
284
285
            return $this->routeManager->update($document, null, false);
286
        }
287
288
        return $this->routeManager->create($document);
289
    }
290
291
    /**
292
     * Reallocates existing route to given document.
293
     *
294
     * @param RoutablePageBehavior $document
295
     * @param string $locale
296
     *
297
     * @return RouteInterface
298
     */
299
    private function reallocateExistingRoute(RoutablePageBehavior $document, $locale)
300
    {
301
        $newRoute = $this->routeRepository->findByPath($document->getRoutePath(), $locale);
302
        if (!$newRoute) {
303
            return;
304
        }
305
306
        $oldRoute = $this->routeRepository->findByEntity(get_class($document), $document->getUuid(), $locale);
307
        $history = $this->routeRepository->findHistoryByEntity(get_class($document), $document->getUuid(), $locale);
308
309
        /** @var RouteInterface $historyRoute */
310
        foreach (array_filter(array_merge($history, [$oldRoute])) as $historyRoute) {
311
            if ($historyRoute->getId() === $newRoute->getId() || $document->getId() !== $historyRoute->getEntityId()) {
312
                // Mismatch of entity-id's happens because doctrine don't check entities which has been changed in the
313
                // current session. If the old-route was already reused by a page before it will be returned in the
314
                // query of line 329.
315
316
                continue;
317
            }
318
319
            $historyRoute->setTarget($newRoute);
320
            $historyRoute->setHistory(true);
321
            $newRoute->addHistory($historyRoute);
322
        }
323
324
        $newRoute->setEntityClass(get_class($document));
325
        $newRoute->setEntityId($document->getId());
326
        $newRoute->setTarget(null);
327
        $newRoute->setHistory(false);
328
329 18
        return $newRoute;
330
    }
331 18
332
    private function updateRoute(RoutablePageBehavior $document)
333 18
    {
334 18
        $locale = $this->documentInspector->getLocale($document);
335
        $propertyName = $this->getRoutePathPropertyName($document->getStructureType(), $locale);
336
337 18
        $route = $this->chainRouteGenerator->generate($document);
338 1
        $document->setRoutePath($route->getPath());
339
340 1
        $node = $this->documentInspector->getNode($document);
341
        $node->setProperty($propertyName, $route->getPath());
342
    }
343 18
344
    private function updateChildRoutes(ChildrenBehavior $document)
345
    {
346
        foreach ($document->getChildren() as $childDocument) {
347
            if (!$childDocument instanceof RoutablePageBehavior) {
348
                continue;
349
            }
350
351
            $this->updateRoute($childDocument);
352
        }
353 18
    }
354
355 18
    /**
356
     * Generates child routes.
357
     *
358
     * @param ChildrenBehavior $document
359
     * @param string $locale
360
     *
361
     * @return string[]
362 18
     */
363 18
    private function generateChildRoutes(ChildrenBehavior $document, $locale)
364
    {
365
        $routes = [];
366
        foreach ($document->getChildren() as $child) {
367
            if (!$child instanceof RoutablePageBehavior) {
368
                continue;
369
            }
370
371
            $childRoute = $this->createOrUpdatePageRoute($child, $locale);
372
            $this->entityManager->persist($childRoute);
373 18
374
            $child->setRoutePath($childRoute->getPath());
375 18
            $childNode = $this->documentInspector->getNode($child);
376 18
377
            $propertyName = $this->getRoutePathPropertyName($child->getStructureType(), $locale);
378
            $childNode->setProperty($propertyName, $childRoute->getPath());
379
380
            $routes[] = $childRoute->getPath();
381
        }
382
383
        return $routes;
384
    }
385
386
    /**
387
     * Removes old-routes where the node does not exists anymore.
388
     *
389
     * @param SessionInterface $session
390
     * @param array $oldRoutes
391
     * @param string $locale
392
     */
393 18
    private function removeOldChildRoutes(SessionInterface $session, array $oldRoutes, $locale)
394
    {
395
        foreach ($oldRoutes as $oldRoute) {
396
            $oldRouteEntity = $this->routeRepository->findByPath($oldRoute, $locale);
397
            if ($oldRouteEntity && !$this->nodeExists($session, $oldRouteEntity->getEntityId())) {
398
                $this->entityManager->remove($oldRouteEntity);
399
            }
400
        }
401 6
402
        $this->entityManager->flush();
403 6
    }
404 6
405 2
    /**
406
     * Iterate over children and remove routes.
407
     *
408 4
     * @param ChildrenBehavior $document
409 4
     * @param string $locale
410 4
     */
411
    private function removeChildRoutes(ChildrenBehavior $document, $locale)
412 4
    {
413 4
        foreach ($document->getChildren() as $child) {
414 4
            if ($child instanceof RoutablePageBehavior) {
415 4
                $this->removeChildRoute($child, $locale);
416
            }
417 4
418 2
            if ($child instanceof ChildrenBehavior) {
0 ignored issues
show
Bug introduced by
The class Sulu\Component\DocumentM...apping\ChildrenBehavior does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
419
                $this->removeChildRoutes($child, $locale);
420
            }
421 2
        }
422
    }
423 2
424 2
    /**
425
     * Removes route if exists.
426
     *
427
     * @param RoutablePageBehavior $document
428 4
     * @param string $locale
429 4
     */
430
    private function removeChildRoute(RoutablePageBehavior $document, $locale)
431
    {
432
        $route = $this->routeRepository->findByPath($document->getRoutePath(), $locale);
433
        if ($route) {
434
            $this->entityManager->remove($route);
435
        }
436 1
    }
437
438 1
    /**
439 1
     * Returns encoded "routePath" property-name.
440
     *
441
     * @param string $structureType
442
     * @param string $locale
443 1
     *
444 1
     * @return string
445 1
     */
446
    private function getRoutePathPropertyName($structureType, $locale)
447 1
    {
448 1
        $metadata = $this->metadataFactory->getStructureMetadata('article', $structureType);
449
450 1
        if ($metadata->hasTag(self::TAG_NAME)) {
451 1
            return $this->getPropertyName($locale, $metadata->getPropertyByTagName(self::TAG_NAME)->getName());
452 1
        }
453 1
454
        return $this->getPropertyName($locale, self::ROUTE_FIELD);
455
    }
456 1
457 1
    /**
458 1
     * Returns encoded property-name.
459
     *
460 1
     * @param string $locale
461 1
     * @param string $field
462
     *
463
     * @return string
464 1
     */
465
    private function getPropertyName($locale, $field)
466
    {
467
        return $this->propertyEncoder->localizedSystemName($field, $locale);
468
    }
469
470
    /**
471
     * Returns true if given uuid exists.
472 1
     *
473
     * @param SessionInterface $session
474 1
     * @param string $uuid
475 1
     *
476 1
     * @return bool
477
     */
478
    private function nodeExists(SessionInterface $session, $uuid)
479 1
    {
480 1
        try {
481
            $session->getNodeByIdentifier($uuid);
482
483 1
            return true;
484
        } catch (ItemNotFoundException $exception) {
0 ignored issues
show
Bug introduced by
The class PHPCR\ItemNotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
485
            return false;
486
        }
487
    }
488
}
489