Completed
Pull Request — master (#328)
by Nikola
03:49
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
 * @package Go\Bridge\Doctrine
24
 */
25
final class MetadataLoadInterceptor implements EventSubscriber
26
{
27
    /**
28
     * {@inheritdoc}
29
     */
30
    public function getSubscribedEvents()
31
    {
32
        return [
33
            Events::loadClassMetadata
34
        ];
35
    }
36
37
    /**
38
     * Handles \Doctrine\ORM\Events::loadClassMetadata event by modifying metadata of Go! AOP proxied classes.
39
     *
40
     * This method intercepts loaded metadata of Doctrine's entities which are weaved by Go! AOP,
41
     * and denotes them as mapped superclass. If weaved entities uses mappings from traits
42
     * (such as Timestampable, Blameable, etc... from https://github.com/Atlantic18/DoctrineExtensions),
43
     * it will remove all mappings from proxied class for fields inherited from traits in order to prevent
44
     * collision with concrete subclass of weaved entity. Fields from trait will be present in concrete subclass
45
     * of weaved entitites.
46
     *
47
     * @see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses
48
     * @see https://github.com/Atlantic18/DoctrineExtensions
49
     *
50
     * @param LoadClassMetadataEventArgs $args
51
     */
52 2
    public function loadClassMetadata(LoadClassMetadataEventArgs $args)
53
    {
54
        /**
55
         * @var ClassMetadata $metadata
56
         */
57 2
        $metadata = $args->getClassMetadata();
58
59 2
        if (1 === preg_match(sprintf('/.+(%s)$/', AspectContainer::AOP_PROXIED_SUFFIX), $metadata->name)) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
60
61 1
            $metadata->isMappedSuperclass = true;
62 1
            $metadata->isEmbeddedClass = false;
63 1
            $metadata->table = [];
64 1
            $metadata->customRepositoryClassName = null;
65
66 1
            $this->removeMappingsFromTraits($metadata);
67
        }
68 2
    }
69
70
    /**
71
     * Remove fields in Go! AOP proxied class metadata that are inherited
72
     * from traits.
73
     *
74
     * @param ClassMetadata $metadata
75
     */
76 1
    private function removeMappingsFromTraits(ClassMetadata $metadata)
77
    {
78 1
        $traits = $this->getTraits($metadata->name);
79
80 1
        foreach ($traits as $trait) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
81
82 1
            $trait = new \ReflectionClass($trait);
83
84
            /**
85
             * @var \ReflectionProperty $property
86
             */
87 1
            foreach ($trait->getProperties() as $property) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
88
89 1
                $name = $property->getName();
90
91 1
                if (isset($metadata->fieldMappings[$name])) {
92 1
                    $mapping = $metadata->fieldMappings[$name];
93
94
                    unset(
95 1
                        $metadata->fieldMappings[$name],
96 1
                        $metadata->fieldNames[$mapping['columnName']],
97 1
                        $metadata->columnNames[$name]
98
                    );
99
                }
100
            }
101
        }
102 1
    }
103
104
    /**
105
     * Get ALL traits used by one class.
106
     *
107
     * This method is copied from https://github.com/RunOpenCode/traitor-bundle/blob/master/src/RunOpenCode/Bundle/Traitor/Utils/ClassUtils.php
108
     *
109
     * @param object|string $objectOrClass Instance of class or FQCN
110
     * @param bool $autoload Weather to autoload class.
111
     *
112
     * @throws \InvalidArgumentException
113
     * @throws \RuntimeException
114
     *
115
     * @return array Used traits.
116
     */
117 1
    private function getTraits($objectOrClass, $autoload = true)
118
    {
119 1
        if (is_object($objectOrClass)) {
120
            $objectOrClass = get_class($objectOrClass);
121
        }
122
123 1
        if (!is_string($objectOrClass)) {
124
            throw new \InvalidArgumentException(sprintf('Full qualified class name expected, got: "%s".', gettype($objectOrClass)));
125
        }
126
127 1
        if (!class_exists($objectOrClass)) {
128
            throw new \RuntimeException(sprintf('Class "%s" does not exists or it can not be autoloaded.', $objectOrClass));
129
        }
130
131 1
        $traits = [];
132
        // Get traits of all parent classes
133
        do {
134 1
            $traits = array_merge(class_uses($objectOrClass, $autoload), $traits);
135 1
        } while ($objectOrClass = get_parent_class($objectOrClass));
136
137 1
        $traitsToSearch = $traits;
138
139 1
        while (count($traitsToSearch) > 0) {
140 1
            $newTraits = class_uses(array_pop($traitsToSearch), $autoload);
141 1
            $traits = array_merge($newTraits, $traits);
142 1
            $traitsToSearch = array_merge($newTraits, $traitsToSearch);
143
        }
144
145 1
        foreach ($traits as $trait => $same) {
146 1
            $traits = array_merge(class_uses($trait, $autoload), $traits);
147
        }
148
149 1
        return array_unique(array_map(function ($fqcn) {
150 1
            return ltrim($fqcn, '\\');
151 1
        }, $traits));
152
    }
153
}
154