Completed
Pull Request — master (#28)
by
unknown
09:12
created

AutoJoin::getEmbeddableAlias()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 25
rs 8.8571
cc 3
eloc 12
nc 3
nop 1
1
<?php
2
3
namespace RulerZ\Executor\DoctrineQueryBuilder;
4
5
use Doctrine\ORM\QueryBuilder;
6
use Doctrine\ORM\EntityManager;
7
8
class AutoJoin
9
{
10
    const ALIAS_PREFIX = 'rulerz_';
11
12
    /**
13
     * List of root and association entity embeddables
14
     *
15
     * @var array
16
     */
17
    private $embeddables = null;
18
19
    /**
20
     * Associative list of known aliases (selected or joined tables).
21
     *
22
     * @var array
23
     */
24
    private $knownAliases = [];
25
26
    /**
27
     * Associative list of joined tables and their alias.
28
     *
29
     * @var array
30
     */
31
    private $joinMap = null;
32
33
    /**
34
     * @var QueryBuilder
35
     */
36
    private $queryBuilder;
37
38
    /**
39
     * @var array
40
     */
41
    private $expectedJoinChains = [];
42
43
    public function __construct(QueryBuilder $queryBuilder, array $expectedJoinChains)
44
    {
45
        $this->queryBuilder       = $queryBuilder;
46
        $this->expectedJoinChains = $expectedJoinChains;
47
    }
48
49
    public function getJoinAlias($table)
50
    {
51
        if ($this->embeddables === null) {
52
            $this->embeddables = $this->analizeEmbeddables($this->queryBuilder);
53
        }
54
55
        if ($this->joinMap === null) {
56
            $this->joinMap      = $this->analizeJoinedTables($this->queryBuilder);
57
            $this->knownAliases = array_flip($this->queryBuilder->getRootAliases()) + array_flip($this->joinMap);
58
59
            $this->autoJoin($this->queryBuilder);
60
        }
61
        // the table is identified as an embeddable
62
        if (array_search($table, $this->embeddables) !== false) {
63
            return $this->getEmbeddableAlias($table);
64
        }
65
66
        // the table name is a known alias (already join for instance) so we
67
        // don't need to do anything.
68
        if (isset($this->knownAliases[$table])) {
69
            return $table;
70
        }
71
72
        // otherwise the table should have automatically been joined, so we use our table prefix
73
        if (isset($this->knownAliases[self::ALIAS_PREFIX.$table])) {
74
            return self::ALIAS_PREFIX . $table;
75
        }
76
77
        throw new \RuntimeException(sprintf('Could not automatically join table "%s"', $table));
78
    }
79
80
    private function getEmbeddableAlias($table)
81
    {
82
        $embeddable_dimensions = explode('.', $table);
83
84
        if (count($embeddable_dimensions) === 1) {
85
            // the embeddable is not inside an association, so we use the root alias prefix
86
            $embeddable_alias = $this->queryBuilder->getRootAliases()[0] . '.' . $table;
87
        }
88
        else {
89
            // remove the embeddable's property
90
            array_pop($embeddable_dimensions);
91
            $table_alias = implode('.', $embeddable_dimensions);
92
93
            if (isset($this->knownAliases[$table_alias])) {
94
                // the table name is a known alias (already join for instance) so we
95
                // don't need to do anything.
96
                $embeddable_alias = $table;
97
            } else {
98
                // otherwise the table should have automatically been joined, so we use our table prefix
99
                $embeddable_alias = self::ALIAS_PREFIX . $table;
100
            }
101
        }
102
103
        return $embeddable_alias;
104
    }
105
106
    private function traverseAssociationsForEmbeddables(EntityManager $entityManager, array $associations, $fieldNamePrefix = false)
107
    {
108
        $associationsEmbeddables = array();
109
110
        foreach ($associations as $association) {
111
            $classMetaData = $entityManager->getClassMetadata($association['targetEntity']);
112
113
            foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) {
114
                $associationsEmbeddables[] = implode('.', array_filter(array($fieldNamePrefix, $association['fieldName'], $embeddedClassKey)));
115
            }
116
117
            $associationMappings = $classMetaData->getAssociationMappings();
118
            $associationMappings = array_filter($associationMappings, function($associationMapping) {
119
                return $associationMapping['isOwningSide'] === false;
120
            });
121
122
            if (count($associationMappings) !== 0) {
123
                $traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $associationMappings, $association['fieldName']);
124
                $associationsEmbeddables = array_merge($associationsEmbeddables, $traversedAssociationsEmbeddables);
125
            }
126
        }
127
128
        return $associationsEmbeddables;
129
    }
130
131
    private function analizeEmbeddables(QueryBuilder $queryBuilder)
132
    {
133
        $embeddables = array();
134
        $entityManager = $queryBuilder->getEntityManager();
135
        $rootEntities = $queryBuilder->getRootEntities();
136
137
        foreach ($rootEntities as $rootEntity) {
138
            $classMetaData = $entityManager->getClassMetadata($rootEntity);
139
140
            foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) {
141
                $embeddables[] = $embeddedClassKey;
142
            }
143
144
            // Since this is a root entity embeddable, there is no need to join.
145
            foreach ($this->expectedJoinChains as $tablesToJoinKey => $tablesToJoin) {
146
                if (in_array(implode('.', $tablesToJoin), $embeddables)) {
147
                    unset($this->expectedJoinChains[$tablesToJoinKey]);
148
                }
149
            }
150
151
            $traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $classMetaData->getAssociationMappings());
152
            $embeddables = array_merge($embeddables, $traversedAssociationsEmbeddables);
153
        }
154
155
        return $embeddables;
156
    }
157
158
    /**
159
     * Builds an associative array of already joined tables and their alias.
160
     *
161
     * @param QueryBuilder $queryBuilder
162
     *
163
     * @return array
164
     */
165
    private function analizeJoinedTables(QueryBuilder $queryBuilder)
166
    {
167
        $joinMap = [];
168
        $joins   = $queryBuilder->getDQLPart('join');
169
        foreach (array_keys($joins) as $fromTable) {
170
            foreach ($joins[$fromTable] as $join) {
171
                $joinMap[$join->getJoin()] = $join->getAlias();
172
            }
173
        }
174
        return $joinMap;
175
    }
176
177
    private function autoJoin(QueryBuilder $queryBuilder)
178
    {
179
        foreach ($this->expectedJoinChains as $tablesToJoin) {
180
            // if the table is an embeddable, the property needs to be removed
181
            if (array_search(implode('.', $tablesToJoin), $this->embeddables) !== false) {
182
                array_pop($tablesToJoin);
183
            }
184
185
            // check if the first dimension is a known alias
186
            if (isset($this->knownAliases[$tablesToJoin[0]])) {
187
                $joinTo = $tablesToJoin[0];
188
                array_pop($tablesToJoin);
189
            } else { // if not, it's the root table
190
                $joinTo = $queryBuilder->getRootAliases()[0];
191
            }
192
193
            foreach ($tablesToJoin as $table) {
194
                $joinAlias = self::ALIAS_PREFIX . $table;
195
                $join      = sprintf('%s.%s', $joinTo, $table);
196
197
                if (!isset($this->joinMap[$join])) {
198
                    $this->joinMap[$join]           = $joinAlias;
199
                    $this->knownAliases[$joinAlias] = true;
200
201
                    $queryBuilder->join(sprintf('%s.%s', $joinTo, $table), $joinAlias);
202
                } else {
203
                    $joinAlias = $this->joinMap[$join];
204
                }
205
206
                $joinTo = $joinAlias;
207
            }
208
        }
209
    }
210
}
211