Completed
Push — master ( 10d093...24820b )
by Kévin
03:15
created

DoctrineAutoJoin::saveAlias()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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