TagSubscriber::getSubscribedEvents()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Beelab\TagBundle\Listener;
4
5
use Beelab\TagBundle\Tag\TaggableInterface;
6
use Beelab\TagBundle\Tag\TagInterface;
7
use Doctrine\Common\EventSubscriber;
8
use Doctrine\Common\Persistence\Mapping\MappingException as LegacyMappingException;
0 ignored issues
show
Bug introduced by
The type Doctrine\Common\Persiste...apping\MappingException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Doctrine\ORM\Event\OnFlushEventArgs;
10
use Doctrine\Persistence\Mapping\MappingException;
11
12
/**
13
 * Add tags to entities that implements TaggableInterface.
14
 */
15
final class TagSubscriber implements EventSubscriber
16
{
17
    /**
18
     * @var \Doctrine\ORM\EntityManager
19
     */
20
    private $manager;
21
22
    /**
23
     * @var \Doctrine\ORM\UnitOfWork
24
     */
25
    private $uow;
26
27
    /**
28
     * @var TagInterface
29
     */
30
    private $tag;
31
32
    /**
33
     * @var bool
34
     */
35
    private $purge;
36
37
    /**
38
     * @param bool $purge whether to delete tags when entity is deleted
39
     *
40
     * @throws MappingException
41
     * @throws \InvalidArgumentException
42
     */
43
    public function __construct(string $tagClassName, bool $purge = false)
44
    {
45
        if (!\class_exists($tagClassName)) {
46
            if (\class_exists('Doctrine\Common\Persistence\Mapping\MappingException')) {
47
                throw LegacyMappingException::nonExistingClass($tagClassName);
48
            }
49
            throw MappingException::nonExistingClass($tagClassName);
50
        }
51
        $this->tag = new $tagClassName();
52
        if (!$this->tag instanceof TagInterface) {
53
            throw new \InvalidArgumentException(\sprintf('Class "%s" must implement TagInterface.', $tagClassName));
54
        }
55
        $this->purge = $purge;
56
    }
57
58
    public function getSubscribedEvents(): array
59
    {
60
        return ['onFlush'];
61
    }
62
63
    /**
64
     * Main method: call setTags() on entities scheduled to be inserted or updated, and
65
     * possibly call purgeTags() on entities scheduled to be deleted.
66
     */
67
    public function onFlush(OnFlushEventArgs $args): void
68
    {
69
        $this->manager = $args->getEntityManager();
70
        $this->uow = $this->manager->getUnitOfWork();
71
        foreach ($this->uow->getScheduledEntityInsertions() as $key => $entity) {
72
            if ($entity instanceof TaggableInterface) {
73
                $this->setTags($entity, false);
74
            }
75
        }
76
        foreach ($this->uow->getScheduledEntityUpdates() as $key => $entity) {
77
            if ($entity instanceof TaggableInterface) {
78
                $this->setTags($entity, true);
79
            }
80
        }
81
        if ($this->purge) {
82
            foreach ($this->uow->getScheduledEntityDeletions() as $key => $entity) {
83
                if ($entity instanceof TaggableInterface) {
84
                    $this->purgeTags($entity);
85
                }
86
            }
87
        }
88
    }
89
90
    /**
91
     * Do the stuff.
92
     *
93
     * @param bool $update true if entity is being updated, false otherwise
94
     */
95
    private function setTags(TaggableInterface $entity, bool $update = false): void
96
    {
97
        $tagNames = $entity->getTagNames();
98
        if (empty($tagNames) && !$update) {
99
            return;
100
        }
101
        // need to clone here, to avoid getting new tags
102
        $oldTags = \is_object($entityTags = $entity->getTags()) ? clone $entityTags : $entityTags;
103
        $tagClassMetadata = $this->manager->getClassMetadata(\get_class($this->tag));
104
        $repository = $this->manager->getRepository(\get_class($this->tag));
105
        foreach ($tagNames as $tagName) {
106
            $tag = $repository->findOneByName($tagName);
0 ignored issues
show
Bug introduced by
The method findOneByName() does not exist on Doctrine\Persistence\ObjectRepository. Did you maybe mean findOneBy()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

106
            /** @scrutinizer ignore-call */ 
107
            $tag = $repository->findOneByName($tagName);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
107
            if (empty($tag)) {
108
                // if tag doesn't exist, create it
109
                $tag = clone $this->tag;
110
                $tag->setName($tagName);
111
                $this->manager->persist($tag);
112
                // see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#onflush
113
                $this->uow->computeChangeSet($tagClassMetadata, $tag);
114
            }
115
            if (!$entity->hasTag($tag)) {
116
                // add tag only if not already added
117
                $entity->addTag($tag);
118
            }
119
        }
120
        // if updating, need to check if some tags were removed
121
        if ($update) {
122
            foreach ($oldTags as $oldTag) {
123
                if (!\in_array($oldTag->getName(), $tagNames)) {
124
                    $entity->removeTag($oldTag);
125
                }
126
            }
127
        }
128
        // see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#onflush
129
        $entityClassMetadata = $this->manager->getClassMetadata(\get_class($entity));
130
        $this->uow->computeChangeSets($entityClassMetadata, $entity);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\ORM\UnitOfWork::computeChangeSets() has too many arguments starting with $entityClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

130
        $this->uow->/** @scrutinizer ignore-call */ 
131
                    computeChangeSets($entityClassMetadata, $entity);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
131
    }
132
133
    /**
134
     * Purge oprhan tags
135
     * Warning: DO NOT purge tags if you have more than one entity
136
     * with tags, since this could lead to costraint violations.
137
     */
138
    private function purgeTags(TaggableInterface $entity): void
139
    {
140
        foreach ($entity->getTags() as $oldTag) {
141
            $this->manager->remove($oldTag);
142
        }
143
    }
144
}
145