ReferenceSubscriber   C
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Test Coverage

Coverage 96.9%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 120
dl 0
loc 270
ccs 125
cts 129
cp 0.969
rs 6.4799
c 1
b 0
f 0
wmc 54

12 Methods

Rating   Name   Duplication   Size   Complexity  
A updatePersist() 0 7 3
B postFlush() 0 36 10
A isParentClass() 0 9 3
A getMetadata() 0 8 2
B updateEntity() 0 42 8
A postLoad() 0 12 5
A __construct() 0 4 1
A loadEntity() 0 10 4
A getAllMetadata() 0 10 4
B preFlush() 0 21 8
A executeChanges() 0 27 5
A getSubscribedEvents() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like ReferenceSubscriber often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ReferenceSubscriber, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * DoctrineExtendListener.php
5
 *
6
 * @since 06/03/18
7
 * @author gseidel
8
 */
9
10
namespace Enhavo\Bundle\DoctrineExtensionBundle\EventListener;
11
12
use Doctrine\Common\EventSubscriber;
13
use Doctrine\ORM\Event\LifecycleEventArgs;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Doctrine\ORM\Event\PostFlushEventArgs;
16
use Doctrine\ORM\Event\PreFlushEventArgs;
17
use Doctrine\ORM\Events;
18
use Doctrine\ORM\Proxy\Proxy;
19
use Enhavo\Bundle\DoctrineExtensionBundle\EntityResolver\EntityResolverInterface;
20
use Enhavo\Bundle\DoctrineExtensionBundle\Metadata\Metadata;
21
use Enhavo\Bundle\DoctrineExtensionBundle\Metadata\Reference;
22
use Enhavo\Component\Metadata\MetadataRepository;
23
use Symfony\Component\PropertyAccess\PropertyAccess;
24
use Doctrine\ORM\UnitOfWork;
25
26
/**
27
 * Class ReferenceSubscriber
28
 *
29
 * A reference extension for doctrine, to reference multiple entities within one property.
30
 */
31
class ReferenceSubscriber implements EventSubscriber
32
{
33
    /** @var MetadataRepository */
34
    private $metadataRepository;
35
36
    /** @var EntityResolverInterface */
37
    private $entityResolver;
38
39 3
    public function __construct(MetadataRepository $metadataRepository, EntityResolverInterface $entityResolver)
40
    {
41 3
        $this->metadataRepository = $metadataRepository;
42 3
        $this->entityResolver = $entityResolver;
43
    }
44
45
    /**
46
     * @param $object
47
     * @return Metadata|null
48
     */
49 3
    private function getMetadata($object): ?Metadata
50
    {
51
        /** @var Metadata $metadata */
52 3
        $metadata = $this->metadataRepository->getMetadata($object);
53 3
        if ($metadata === null) {
54 3
            return null;
55
        }
56 3
        return $metadata;
57
    }
58
59
    /**
60
     * @return Metadata[]
61
     */
62 3
    private function getAllMetadata(): array
63
    {
64 3
        $data = [];
65
        /** @var Metadata $metadata */
66 3
        foreach ($this->metadataRepository->getAllMetadata() as $metadata) {
67 3
            if (count($metadata->getReferences()) > 0 && !in_array($metadata, $data)) {
0 ignored issues
show
Bug introduced by
The method getReferences() does not exist on Enhavo\Component\Metadata\Metadata. It seems like you code against a sub-type of Enhavo\Component\Metadata\Metadata such as Enhavo\Bundle\DoctrineEx...undle\Metadata\Metadata. ( Ignorable by Annotation )

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

67
            if (count($metadata->/** @scrutinizer ignore-call */ getReferences()) > 0 && !in_array($metadata, $data)) {
Loading history...
68 3
                $data[] = $metadata;
69
            }
70
        }
71 3
        return $data;
72
    }
73
74
    /**
75
     * {@inheritDoc}
76
     */
77 3
    public function getSubscribedEvents()
78
    {
79 3
        return [
80 3
            Events::postLoad,
81 3
            Events::preFlush,
82 3
            Events::postFlush,
83 3
        ];
84
    }
85
86
    /**
87
     * Insert target class on load
88
     *
89
     * @param $args LifecycleEventArgs
90
     */
91 3
    public function postLoad(LifecycleEventArgs $args)
92
    {
93 3
        $object = $args->getObject();
94
95 3
        $metadata = $this->getMetadata($object);
96 3
        if ($metadata === null) {
97 3
            return;
98
        }
99
100 3
        if (get_class($object) === $metadata->getClassName() || $this->isParentClass($object, $metadata->getClassName())) {
101 3
            foreach ($metadata->getReferences() as $reference) {
102 3
                $this->loadEntity($reference, $object);
103
            }
104
        }
105
    }
106
107 2
    private function isParentClass($object, $class)
108
    {
109 2
        $parentClass = get_parent_class($object);
110 2
        if ($parentClass === false) {
111
            return false;
112 2
        } elseif ($parentClass === $class) {
113 2
            return true;
114
        } else {
115
            return $this->isParentClass($parentClass, $class);
116
        }
117
    }
118
119 3
    private function loadEntity(Reference $reference, $entity)
120
    {
121 3
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
122 3
        $id = $propertyAccessor->getValue($entity, $reference->getIdField());
123 3
        $class = $propertyAccessor->getValue($entity, $reference->getNameField());
124
125 3
        if ($id && $class) {
126 3
            $targetEntity = $this->entityResolver->getEntity($id, $class);
127 3
            if ($targetEntity !== null) {
128 3
                $propertyAccessor->setValue($entity, $reference->getProperty(), $targetEntity);
129
            }
130
        }
131
    }
132
133
    /**
134
     * Update entity before flush to prevent possible changes after flush
135
     *
136
     * @param PreFlushEventArgs $args
137
     */
138 3
    public function preFlush(PreFlushEventArgs $args)
139
    {
140 3
        $uow = $args->getEntityManager()->getUnitOfWork();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Event\PreFl...rgs::getEntityManager() has been deprecated: 2.13. Use {@see getObjectManager} instead. ( Ignorable by Annotation )

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

140
        $uow = /** @scrutinizer ignore-deprecated */ $args->getEntityManager()->getUnitOfWork();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
141 3
        $em = $args->getEntityManager();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Event\PreFl...rgs::getEntityManager() has been deprecated: 2.13. Use {@see getObjectManager} instead. ( Ignorable by Annotation )

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

141
        $em = /** @scrutinizer ignore-deprecated */ $args->getEntityManager();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
142 3
        $identityMap = $uow->getIdentityMap();
143
144 3
        foreach ($this->getAllMetadata() as $metadata) {
145 3
            if (isset($identityMap[$metadata->getClassName()])) {
146 2
                foreach ($identityMap[$metadata->getClassName()] as $entity) {
147 2
                    foreach ($metadata->getReferences() as $reference) {
148 2
                        $this->updateEntity($reference, $entity);
149 2
                        $this->updatePersist($reference, $entity, $em);
150
                    }
151
                }
152
            }
153
154 3
            foreach ($uow->getScheduledEntityInsertions() as $entity) {
155 3
                if (get_class($entity) === $metadata->getClassName()) {
156 3
                    foreach ($metadata->getReferences() as $reference) {
157 3
                        $this->updateEntity($reference, $entity);
158 3
                        $this->updatePersist($reference, $entity, $em);
159
                    }
160
                }
161
            }
162
        }
163
    }
164
165 3
    private function updatePersist(Reference $reference, $entity, EntityManagerInterface $em)
166
    {
167 3
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
168 3
        $targetProperty = $propertyAccessor->getValue($entity, $reference->getProperty());
169
170 3
        if ($targetProperty && $em->getUnitOfWork()->getEntityState($targetProperty) !== UnitOfWork::STATE_MANAGED) {
171 3
            $em->persist($targetProperty);
172
        }
173
    }
174
175
    /**
176
     * Check if entity is not up to date and execute changes
177
     *
178
     * @param PostFlushEventArgs $args
179
     */
180 3
    public function postFlush(PostFlushEventArgs $args)
181
    {
182 3
        $persist = false;
183 3
        $changes = [];
184
185 3
        $em = $args->getEntityManager();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Event\PostF...rgs::getEntityManager() has been deprecated: 2.13. Use {@see getObjectManager} instead. ( Ignorable by Annotation )

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

185
        $em = /** @scrutinizer ignore-deprecated */ $args->getEntityManager();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
186 3
        $uow = $args->getEntityManager()->getUnitOfWork();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Event\PostF...rgs::getEntityManager() has been deprecated: 2.13. Use {@see getObjectManager} instead. ( Ignorable by Annotation )

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

186
        $uow = /** @scrutinizer ignore-deprecated */ $args->getEntityManager()->getUnitOfWork();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
187 3
        $result = $uow->getIdentityMap();
188
189 3
        foreach ($this->getAllMetadata() as $metadata) {
190 3
            if (isset($result[$metadata->getClassName()])) {
191 3
                foreach ($result[$metadata->getClassName()] as $entity) {
192 3
                    foreach ($metadata->getReferences() as $reference) {
193 3
                        $entityChanges = $this->updateEntity($reference, $entity);
194 3
                        foreach ($entityChanges as $entityChange) {
195 3
                            $changes[] = $entityChange;
196
                        }
197
198 3
                        $propertyAccessor = PropertyAccess::createPropertyAccessor();
199 3
                        $targetProperty = $propertyAccessor->getValue($entity, $reference->getProperty());
200
201 3
                        if ($targetProperty && $uow->getEntityState($targetProperty) !== UnitOfWork::STATE_MANAGED) {
202 1
                            $em->persist($targetProperty);
203 1
                            $persist = true;
204
                        }
205
                    }
206
                }
207
            }
208
        }
209
210 3
        if (count($changes)) {
211 3
            $this->executeChanges($changes, $args->getEntityManager());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Event\PostF...rgs::getEntityManager() has been deprecated: 2.13. Use {@see getObjectManager} instead. ( Ignorable by Annotation )

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

211
            $this->executeChanges($changes, /** @scrutinizer ignore-deprecated */ $args->getEntityManager());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
212
        }
213
214 3
        if ($persist) {
215 1
            $em->flush();
216
        }
217
    }
218
219
    /**
220
     * Update entity values
221
     *
222
     * @param Reference $reference
223
     * @param object $entity
224
     * @return ReferenceChange[]
225
     */
226 3
    private function updateEntity(Reference $reference, $entity)
227
    {
228 3
        $changes = [];
229
230 3
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
231 3
        $targetProperty = $propertyAccessor->getValue($entity, $reference->getProperty());
232
233
        // if entity state is still proxy, we try to load its entity to make sure that target property is correct
234 3
        if ($entity instanceof Proxy && $targetProperty === null) {
235
            $this->loadEntity($reference, $entity);
236
            $targetProperty = $propertyAccessor->getValue($entity, $reference->getProperty());
237
        }
238
239 3
        if ($targetProperty) {
240
            // update id property
241 3
            $idProperty = $propertyAccessor->getValue($entity, $reference->getIdField());
242 3
            $id = $propertyAccessor->getValue($targetProperty, 'id');
243 3
            if ($idProperty != $id) {
244 3
                $propertyAccessor->setValue($entity, $reference->getIdField(), $id);
245 3
                $changes[] = new ReferenceChange($entity, $reference->getIdField(), $id);
246
            }
247
248
            // update class property
249 3
            $classProperty = $propertyAccessor->getValue($entity, $reference->getNameField());
250 3
            $class = $this->entityResolver->getName($targetProperty);
251 3
            if ($classProperty != $class) {
252 3
                $propertyAccessor->setValue($entity, $reference->getNameField(), $class);
253 3
                $changes[] = new ReferenceChange($entity, $reference->getNameField(), $class);
254
            }
255
        } else {
256 1
            if (null !== $propertyAccessor->getValue($entity, $reference->getIdField())) {
257 1
                $propertyAccessor->setValue($entity, $reference->getIdField(), null);
258 1
                $changes[] = new ReferenceChange($entity, $reference->getIdField(), null);
259
            }
260
261 1
            if (null !== $propertyAccessor->getValue($entity, $reference->getNameField())) {
262 1
                $propertyAccessor->setValue($entity, $reference->getNameField(), null);
263 1
                $changes[] = new ReferenceChange($entity, $reference->getNameField(), null);
264
            }
265
        }
266
267 3
        return $changes;
268
    }
269
270
    /**
271
     * @param ReferenceChange[] $changes
272
     * @param EntityManagerInterface $em
273
     */
274 3
    private function executeChanges($changes, EntityManagerInterface $em)
275
    {
276 3
        $queries = [];
277 3
        $entities = [];
278 3
        foreach ($changes as $change) {
279 3
            $query = $em->createQueryBuilder()
280 3
                ->update(get_class($change->getEntity()), 'e')
281 3
                ->set(sprintf('e.%s', $change->getProperty()), ':value')
282 3
                ->where('e.id = :id')
283 3
                ->setParameter('value', $change->getValue())
284 3
                ->setParameter('id', $change->getEntity()->getId())
285 3
                ->getQuery()
286 3
            ;
287
288 3
            $queries[] = $query;
289
290 3
            if (!in_array($change->getEntity(), $entities)) {
291 3
                $entities[] = $change->getEntity();
292
            }
293
        }
294
295 3
        foreach ($queries as $query) {
296 3
            $query->execute();
297
        }
298
299 3
        foreach ($entities as $entity) {
300 3
            $em->refresh($entity);
301
        }
302
    }
303
}
304