Completed
Pull Request — master (#86)
by
unknown
03:04 queued 01:29
created

buildAccessPathFromElementContext()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 1
1
<?php
2
3
namespace RulerZ\Target\DoctrineORM;
4
5
use Doctrine\ORM\EntityManagerInterface as EntityManager;
6
use Hoa\Ruler\Model as AST;
7
8
class DoctrineAutoJoin
9
{
10
    /**
11
     * @var EntityManager
12
     */
13
    private $em;
14
15
    /**
16
     * @var array
17
     */
18
    private $detectedJoins = [];
19
20
    /**
21
     * @var array<Entity,array<dimmension,alias>
22
     */
23
    private $knownEntities = [];
24
25
    /**
26
     * @var array<alias,Entity>
27
     */
28
    private $aliasMap = [];
29
30
    /**
31
     * @var array
32
     */
33
    private $rootEntities = [];
34
35
    public function __construct(EntityManager $em, array $rootEntities, array $rootAliases, array $existingJoins)
36
    {
37
        $this->em = $em;
38
        $this->rootEntities = array_combine($rootAliases, $rootEntities);
39
40
        $this->aliasMap = array_combine($rootAliases, $rootEntities);
41
42
        foreach ($existingJoins as $joins) {
43
            /** @var \Doctrine\ORM\Query\Expr\Join $join */
44
            foreach ($joins as $join) {
45
                list($fromAlias, $attribute) = explode('.', $join->getJoin());
46
                $relation = $this->getRelation($attribute, $this->getEntity($fromAlias));
47
48
                $this->saveAlias($relation['targetEntity'], $relation['fieldName'], $join->getAlias());
49
            }
50
        }
51
    }
52
53
    public function getDetectedJoins()
54
    {
55
        return $this->detectedJoins;
56
    }
57
58
    public function buildAccessPathFromElementContext(AST\Bag\Context $element)
59
    {
60
        $dimensionNames = array_map(function ($dimension) {
61
            return $dimension[1];
62
        }, $element->getDimensions());
63
        array_unshift($dimensionNames, $element->getId());
64
65
        return $this->buildAccessPath($dimensionNames);
66
    }
67
68
    public function buildAccessPath(array $dimensionNames)
69
    {
70
        $currentEntity = current($this->rootEntities);
71
        $lastAlias = key($this->aliasMap);
72
73
        foreach ($dimensionNames as $i => $dimension) {
74
            if (!$this->em->getClassMetadata($currentEntity)) {
75
                throw new \Exception(sprintf('Metadata not found for entity "%s"', $currentEntity));
76
            }
77
78
            // the current dimension is an alias (a table already joined for instance)
79
            if (isset($this->aliasMap[$dimension])) {
80
                $currentEntity = $this->getEntity($dimension);
81
                $lastAlias = $dimension;
82
83
                continue;
84
            }
85
86
            // the current dimension is a relation of the current entity
87
            if ($this->relationExists($dimension, $currentEntity)) {
88
                /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
89
                $classMetadata = $this->em->getClassMetadata($currentEntity);
90
                $association = $classMetadata->getAssociationMapping($dimension);
91
92
                if (!isset($this->knownEntities[$currentEntity], $this->knownEntities[$currentEntity][$association['fieldName']])) {
93
                    $alias = sprintf('_%d_%s', count($this->knownEntities, COUNT_RECURSIVE), $dimension);
94
95
                    $this->saveAlias($currentEntity, $association['fieldName'], $alias);
96
97
                    $this->detectedJoins[] = [
98
                        'root' => $lastAlias,
99
                        'column' => $dimension,
100
                        'as' => $alias = $this->getAlias($currentEntity, $association['fieldName']),
101
                    ];
102
                } else {
103
                    $alias = $this->getAlias($currentEntity, $association['fieldName']);
104
                }
105
106
                $currentEntity = $association['targetEntity'];
107
                $lastAlias = $alias;
108
109
                continue;
110
            }
111
112
            // the current dimension is an embedded class
113
            if ($this->embeddedExists($dimension, $currentEntity)) {
114
                /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
115
                $classMetadata = $this->em->getClassMetadata($currentEntity);
116
                $embeddableMetadata = $classMetadata->embeddedClasses[$dimension];
117
118
                $currentEntity = $embeddableMetadata['class'];
119
                $lastAlias = $lastAlias.'.'.$dimension;
120
121
                continue;
122
            }
123
124
            // or, at last, it's a column access.
125
            if ($this->columnExists($dimension, $currentEntity)) {
126
                if (($i + 1) === count($dimensionNames)) {
127
                    return sprintf('%s.%s', $lastAlias, $dimension);
128
                }
129
130
                throw new \RuntimeException('Found scalar attribute in the middle of an access path.');
131
            }
132
133
            throw new \Exception(sprintf('"%s" not found for entity "%s"', $dimension, $currentEntity));
134
        }
135
136
        return $lastAlias;
137
    }
138
139
    private function columnExists($name, $rootEntity)
140
    {
141
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
142
        $classMetadata = $this->em->getClassMetadata($rootEntity);
143
144
        return $classMetadata->hasField($name);
145
    }
146
147
    private function relationExists($name, $rootEntity)
148
    {
149
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
150
        $classMetadata = $this->em->getClassMetadata($rootEntity);
151
152
        return $classMetadata->hasAssociation($name);
153
    }
154
155
    private function getRelation($name, $rootEntity)
156
    {
157
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
158
        $classMetadata = $this->em->getClassMetadata($rootEntity);
159
160
        return $classMetadata->getAssociationMapping($name);
161
    }
162
163
    private function embeddedExists($name, $rootEntity)
164
    {
165
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
166
        $classMetadata = $this->em->getClassMetadata($rootEntity);
167
168
        return isset($classMetadata->embeddedClasses) && isset($classMetadata->embeddedClasses[$name]);
169
    }
170
171
    private function saveAlias($entity, $dimension, $alias)
172
    {
173
        if (!isset($this->knownEntities[$entity])) {
174
            $this->knownEntities[$entity] = [];
175
        }
176
177
        $this->knownEntities[$entity][$dimension] = $alias;
178
        $this->aliasMap[$alias] = $entity;
179
    }
180
181
    private function getAlias($entity, $dimension)
182
    {
183
        return $this->knownEntities[$entity][$dimension];
184
    }
185
186
    private function getEntity($entity)
187
    {
188
        return $this->aliasMap[$entity];
189
    }
190
}
191