DoctrineWatcher::watch()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 4
nop 4
dl 0
loc 16
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
namespace BenTools\DoctrineWatcher\Watcher;
4
5
use BenTools\DoctrineWatcher\Changeset\ChangesetFactory;
6
use BenTools\DoctrineWatcher\Changeset\PropertyChangeset;
7
use Doctrine\Common\EventSubscriber;
8
use Doctrine\Common\Util\ClassUtils;
9
use Doctrine\ORM\Event\LifecycleEventArgs;
10
11
final class DoctrineWatcher implements EventSubscriber
12
{
13
    public const DEFAULT_OPTIONS = [
14
        'trigger_on_persist'      => false,
15
        'trigger_when_no_changes' => false,
16
    ];
17
18
    /**
19
     * @var array
20
     */
21
    private $defaulOptions;
22
23
    /**
24
     * @var ChangesetFactory
25
     */
26
    private $changesetFactory;
27
28
    /**
29
     * @var callable[]
30
     */
31
    private $listeners = [];
32
33
    /**
34
     * DoctrineWatcher constructor.
35
     *
36
     * @param array                 $options
37
     * @param ChangesetFactory|null $changesetFactory
38
     */
39
    public function __construct(
40
        array $options = self::DEFAULT_OPTIONS,
41
        ChangesetFactory $changesetFactory = null
42
    ) {
43
        $this->defaulOptions = \array_replace(self::DEFAULT_OPTIONS, $options);
44
        $this->changesetFactory = $changesetFactory ?? new ChangesetFactory();
45
    }
46
47
    /**
48
     * @param string   $entityClass
49
     * @param          $property
50
     * @param callable $callback
51
     * @param array    $options
52
     * @throws \InvalidArgumentException
53
     */
54
    public function watch(string $entityClass, $property, callable $callback, array $options = []): void
55
    {
56
        if (\is_array($property)) {
57
            foreach ($property as $prop) {
58
                $this->watch($entityClass, $prop, $callback, $options);
59
            }
60
            return;
61
        }
62
63
        if (!\is_string($property)) {
64
            throw new \InvalidArgumentException(\sprintf('Expected property, got %s.', \is_object($property) ? \get_class($property) : \gettype($property)));
65
        }
66
67
        $options = \array_replace($this->defaulOptions, $options);
68
        $listener = $this->createPropertyListener($entityClass, $property, $callback, $options);
69
        $this->listeners[$entityClass][$property][] = $listener;
70
    }
71
72
    /**
73
     * @param string   $entityClass
74
     * @param string   $property
75
     * @param callable $callback
76
     * @param array    $options
77
     */
78
    public function watchIterable(string $entityClass, string $property, callable $callback, array $options = []): void
79
    {
80
        \trigger_error(\sprintf('%s is deprecated as it is now an alias of %s.', __METHOD__, strtr(__METHOD__, ['watchIterable' => 'watch'])), \E_USER_DEPRECATED);
81
        $this->watch($entityClass, $property, $callback, $options);
82
    }
83
84
    /**
85
     * @param string   $entityClass
86
     * @param string   $property
87
     * @param callable $callback
88
     * @param array    $options
89
     * @return callable
90
     */
91
    private function createPropertyListener(string $entityClass, string $property, callable $callback, array $options = []): callable
92
    {
93
        return function (LifecycleEventArgs $eventArgs, string $operationType) use ($entityClass, $property, $callback, $options) {
94
            $em = $eventArgs->getEntityManager();
95
            $unitOfWork = $em->getUnitOfWork();
96
            $entity = $eventArgs->getEntity();
97
98
            // Do not trigger on the wrong entity
99
            if (!$entity instanceof $entityClass) {
100
                return;
101
            }
102
103
            // Do not trigger if entity was not managed
104
            if (false === $options['trigger_on_persist'] && PropertyChangeset::INSERT === $operationType) {
105
                return;
106
            }
107
108
            // Do not trigger if field has no changes
109
            $className = ClassUtils::getClass($entity);
110
            $classMetadata = $em->getClassMetadata($className);
111
            $changedProperties = $this->changesetFactory->getChangedProperties($entity, $unitOfWork, $classMetadata);
112
            if (!\in_array($property, $changedProperties) && false === $options['trigger_when_no_changes']) {
113
                return;
114
            }
115
116
            $changeset = $this->changesetFactory->getChangeset($entity, $property, $unitOfWork, $classMetadata);
117
            $callback(
118
                $changeset,
119
                $operationType,
120
                $entity,
121
                $property
122
            );
123
        };
124
    }
125
126
    /**
127
     * @param LifecycleEventArgs $eventArgs
128
     * @param string             $operationType
129
     */
130
    private function trigger(LifecycleEventArgs $eventArgs, string $operationType): void
131
    {
132
        $entity = $eventArgs->getEntity();
133
        $class = ClassUtils::getClass($entity);
134
        foreach ($this->listeners[$class] ?? [] as $property => $listeners) {
135
            foreach ($listeners as $listener) {
136
                $listener($eventArgs, $operationType);
137
            }
138
        }
139
    }
140
141
    /**
142
     * @param LifecycleEventArgs $eventArgs
143
     */
144
    public function postPersist(LifecycleEventArgs $eventArgs): void
145
    {
146
        $this->trigger($eventArgs, PropertyChangeset::INSERT);
147
    }
148
149
    /**
150
     * @param LifecycleEventArgs $eventArgs
151
     */
152
    public function postUpdate(LifecycleEventArgs $eventArgs): void
153
    {
154
        $this->trigger($eventArgs, PropertyChangeset::UPDATE);
155
    }
156
157
    /**
158
     * @inheritDoc
159
     */
160
    public function getSubscribedEvents()
161
    {
162
        return [
163
            'postPersist',
164
            'postUpdate',
165
        ];
166
    }
167
}
168