Completed
Pull Request — master (#86)
by
unknown
08:57
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
                continue;
83
            }
84
85
            // the current dimension is a relation of the current entity
86
            if ($this->relationExists($dimension, $currentEntity)) {
87
                /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
88
                $classMetadata = $this->em->getClassMetadata($currentEntity);
89
                $association = $classMetadata->getAssociationMapping($dimension);
90
91
                if (!isset($this->knownEntities[$currentEntity], $this->knownEntities[$currentEntity][$association['fieldName']])) {
92
                    $alias = sprintf('_%d_%s', count($this->knownEntities), $dimension);
93
94
                    $this->saveAlias($currentEntity, $association['fieldName'], $alias);
95
96
                    $this->detectedJoins[] = [
97
                        'root' => $lastAlias,
98
                        'column' => $dimension,
99
                        'as' => $alias = $this->getAlias($currentEntity, $association['fieldName']),
100
                    ];
101
                } else {
102
                    $alias = $this->getAlias($currentEntity, $association['fieldName']);
103
                }
104
105
                $currentEntity = $association['targetEntity'];
106
                $lastAlias = $alias;
107
                continue;
108
            }
109
110
            // the current dimension is an embedded class
111
            if ($this->embeddedExists($dimension, $currentEntity)) {
112
                /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
113
                $classMetadata = $this->em->getClassMetadata($currentEntity);
114
                $embeddableMetadata = $classMetadata->embeddedClasses[$dimension];
115
116
                $currentEntity = $embeddableMetadata['class'];
117
                $lastAlias = $lastAlias.'.'.$dimension;
118
                continue;
119
            }
120
121
            // or, at last, it's a column access.
122
            if ($this->columnExists($dimension, $currentEntity)) {
123
                if (($i + 1) === count($dimensionNames)) {
124
                    return sprintf('%s.%s', $lastAlias, $dimension);
125
                }
126
127
                throw new \RuntimeException('Found scalar attribute in the middle of an access path.');
128
            }
129
130
            throw new \Exception(sprintf('"%s" not found for entity "%s"', $dimension, $currentEntity));
131
        }
132
133
        return $lastAlias;
134
    }
135
136
    private function columnExists($name, $rootEntity)
137
    {
138
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
139
        $classMetadata = $this->em->getClassMetadata($rootEntity);
140
141
        return $classMetadata->hasField($name);
142
    }
143
144
    private function relationExists($name, $rootEntity)
145
    {
146
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
147
        $classMetadata = $this->em->getClassMetadata($rootEntity);
148
149
        return $classMetadata->hasAssociation($name);
150
    }
151
152
    private function getRelation($name, $rootEntity)
153
    {
154
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
155
        $classMetadata = $this->em->getClassMetadata($rootEntity);
156
157
        return $classMetadata->getAssociationMapping($name);
158
    }
159
160
    private function embeddedExists($name, $rootEntity)
161
    {
162
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata */
163
        $classMetadata = $this->em->getClassMetadata($rootEntity);
164
165
        return isset($classMetadata->embeddedClasses) && isset($classMetadata->embeddedClasses[$name]);
166
    }
167
168
    private function saveAlias($entity, $dimension, $alias)
169
    {
170
        if (!isset($this->knownEntities[$entity])) {
171
            $this->knownEntities[$entity] = [];
172
        }
173
174
        $this->knownEntities[$entity][$dimension] = $alias;
175
        $this->aliasMap[$alias] = $entity;
176
    }
177
178
    private function getAlias($entity, $dimension)
179
    {
180
        return $this->knownEntities[$entity][$dimension];
181
    }
182
183
    private function getEntity($entity)
184
    {
185
        return $this->aliasMap[$entity];
186
    }
187
}
188