Completed
Pull Request — master (#328)
by Nikola
02:58
created

MetadataLoadInterceptor::getSubscribedEvents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 2
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 2
1
<?php
2
/*
3
 * Go! AOP framework
4
 *
5
 * @copyright Copyright 2012, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
namespace Go\Bridge\Doctrine;
11
12
use Doctrine\Common\EventSubscriber;
13
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
14
use Doctrine\ORM\Events;
15
use Doctrine\ORM\Mapping\ClassMetadata;
16
use Go\Core\AspectContainer;
17
18
/**
19
 * Class MetadataLoadInterceptor
20
 *
21
 * Support for weaving Doctrine entities.
22
 */
23
final class MetadataLoadInterceptor implements EventSubscriber
24
{
25
    /**
26
     * {@inheritdoc}
27
     */
28
    public function getSubscribedEvents()
29
    {
30
        return [
31
            Events::loadClassMetadata
32
        ];
33
    }
34
35
    /**
36
     * Handles \Doctrine\ORM\Events::loadClassMetadata event by modifying metadata of Go! AOP proxied classes.
37
     *
38
     * This method intercepts loaded metadata of Doctrine's entities which are weaved by Go! AOP,
39
     * and denotes them as mapped superclass. If weaved entities uses mappings from traits
40
     * (such as Timestampable, Blameable, etc... from https://github.com/Atlantic18/DoctrineExtensions),
41
     * it will remove all mappings from proxied class for fields inherited from traits in order to prevent
42
     * collision with concrete subclass of weaved entity. Fields from trait will be present in concrete subclass
43
     * of weaved entitites.
44
     *
45
     * @see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses
46
     * @see https://github.com/Atlantic18/DoctrineExtensions
47
     *
48
     * @param LoadClassMetadataEventArgs $args
49
     */
50 2
    public function loadClassMetadata(LoadClassMetadataEventArgs $args)
51
    {
52
        /**
53
         * @var ClassMetadata $metadata
54
         */
55 2
        $metadata = $args->getClassMetadata();
56
57 2
        if (1 === preg_match(sprintf('/.+(%s)$/', AspectContainer::AOP_PROXIED_SUFFIX), $metadata->name)) {
58 1
            $metadata->isMappedSuperclass           = true;
59 1
            $metadata->isEmbeddedClass              = false;
60 1
            $metadata->table                        = [];
61 1
            $metadata->customRepositoryClassName    = null;
62
63 1
            $this->removeMappingsFromTraits($metadata);
64
        }
65 2
    }
66
67
    /**
68
     * Remove fields in Go! AOP proxied class metadata that are inherited
69
     * from traits.
70
     *
71
     * @param ClassMetadata $metadata
72
     */
73 1
    private function removeMappingsFromTraits(ClassMetadata $metadata)
74
    {
75 1
        $traits = $this->getTraits($metadata->name);
76
77 1
        foreach ($traits as $trait) {
78 1
            $trait = new \ReflectionClass($trait);
79
80
            /**
81
             * @var \ReflectionProperty $property
82
             */
83 1
            foreach ($trait->getProperties() as $property) {
84 1
                $name = $property->getName();
85
86 1
                if (isset($metadata->fieldMappings[$name])) {
87 1
                    $mapping = $metadata->fieldMappings[$name];
88
89
                    unset(
90 1
                        $metadata->fieldMappings[$name],
91 1
                        $metadata->fieldNames[$mapping['columnName']],
92 1
                        $metadata->columnNames[$name]
93
                    );
94
                }
95
            }
96
        }
97 1
    }
98
99
    /**
100
     * Get ALL traits used by one class.
101
     *
102
     * This method is copied from https://github.com/RunOpenCode/traitor-bundle/blob/master/src/RunOpenCode/Bundle/Traitor/Utils/ClassUtils.php
103
     *
104
     * @param object|string $objectOrClass Instance of class or FQCN
105
     * @param bool $autoload Weather to autoload class.
106
     *
107
     * @throws \InvalidArgumentException
108
     * @throws \RuntimeException
109
     *
110
     * @return array Used traits.
111
     */
112 1
    private function getTraits($objectOrClass, $autoload = true)
113
    {
114 1
        if (is_object($objectOrClass)) {
115
            $objectOrClass = get_class($objectOrClass);
116
        }
117
118 1
        if (!is_string($objectOrClass)) {
119
            throw new \InvalidArgumentException(sprintf('Full qualified class name expected, got: "%s".', gettype($objectOrClass)));
120
        }
121
122 1
        if (!class_exists($objectOrClass)) {
123
            throw new \RuntimeException(sprintf('Class "%s" does not exists or it can not be autoloaded.', $objectOrClass));
124
        }
125
126 1
        $traits = [];
127
        // Get traits of all parent classes
128
        do {
129 1
            $traits = array_merge(class_uses($objectOrClass, $autoload), $traits);
130 1
        } while ($objectOrClass = get_parent_class($objectOrClass));
131
132 1
        $traitsToSearch = $traits;
133
134 1
        while (count($traitsToSearch) > 0) {
135 1
            $newTraits = class_uses(array_pop($traitsToSearch), $autoload);
136 1
            $traits = array_merge($newTraits, $traits);
137 1
            $traitsToSearch = array_merge($newTraits, $traitsToSearch);
138
        }
139
140 1
        foreach ($traits as $trait => $same) {
141 1
            $traits = array_merge(class_uses($trait, $autoload), $traits);
142
        }
143
144 1
        return array_unique(array_map(function ($fqcn) {
145 1
            return ltrim($fqcn, '\\');
146 1
        }, $traits));
147
    }
148
}
149