TagSubscriber   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 24
eloc 47
dl 0
loc 124
rs 10
c 0
b 0
f 0

5 Methods

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

102
            /** @scrutinizer ignore-call */ 
103
            $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...
103
            if (empty($tag)) {
104
                // if tag doesn't exist, create it
105
                $tag = clone $this->tag;
106
                $tag->setName($tagName);
107
                $this->manager->persist($tag);
108
                // see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#onflush
109
                $this->uow->computeChangeSet($tagClassMetadata, $tag);
110
            }
111
            if (!$entity->hasTag($tag)) {
112
                // add tag only if not already added
113
                $entity->addTag($tag);
114
            }
115
        }
116
        // if updating, need to check if some tags were removed
117
        if ($update) {
118
            foreach ($oldTags as $oldTag) {
119
                if (!\in_array($oldTag->getName(), $tagNames)) {
120
                    $entity->removeTag($oldTag);
121
                }
122
            }
123
        }
124
        // see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#onflush
125
        $entityClassMetadata = $this->manager->getClassMetadata(\get_class($entity));
126
        $this->uow->computeChangeSets($entityClassMetadata, $entity);
0 ignored issues
show
Unused Code introduced by Massimiliano Arione
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

126
        $this->uow->/** @scrutinizer ignore-call */ 
127
                    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...
127
    }
128
129
    /**
130
     * Purge oprhan tags
131
     * Warning: DO NOT purge tags if you have more than one entity
132
     * with tags, since this could lead to costraint violations.
133
     */
134
    protected function purgeTags(TaggableInterface $entity): void
135
    {
136
        foreach ($entity->getTags() as $oldTag) {
137
            $this->manager->remove($oldTag);
138
        }
139
    }
140
}
141