Completed
Pull Request — master (#28)
by
unknown
13:06
created

AutoJoin::autoJoin()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 33
rs 8.439
cc 6
eloc 19
nc 13
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, $full_property_path = null)
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
62
        // the table is identified as an embeddable
63
        if (in_array($full_property_path, $this->embeddables))
64
        {
65
            return $this->getEmbeddableAlias($full_property_path);
66
        }
67
68
        // the table name is a known alias (already join for instance) so we
69
        // don't need to do anything.
70
        if (isset($this->knownAliases[$table])) {
71
            return $table;
72
        }
73
74
        // otherwise the table should have automatically been joined, so we use our table prefix
75
        if (isset($this->knownAliases[self::ALIAS_PREFIX.$table])) {
76
            return self::ALIAS_PREFIX . $table;
77
        }
78
79
        throw new \RuntimeException(sprintf('Could not automatically join table "%s"', $table));
80
    }
81
82
    private function getEmbeddableAlias($full_property_path)
83
    {
84
        $embeddable_dimensions = explode('.', $full_property_path);
85
86
        $embeddable_name = array_pop($embeddable_dimensions);
87
        $embeddable_table = array_pop($embeddable_dimensions);
88
89
        if ($embeddable_table === null)
90
        {
91
            // the embeddable is not inside an association, so we use the root alias prefix.
92
            $embeddable_table = $this->queryBuilder->getRootAliases()[0];
93
        }
94
        elseif (array_key_exists($embeddable_table, $this->knownAliases))
95
        {
96
            // the table name is a known alias (already join for instance) so we
97
            // don't need to do anything.
98
            $embeddable_table = $embeddable_table;
0 ignored issues
show
Bug introduced by
Why assign $embeddable_table to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
99
        }
100
        elseif (array_key_exists(self::ALIAS_PREFIX . $embeddable_table, $this->knownAliases))
101
        {
102
            // otherwise the table should have automatically been joined, so we use our table prefix.
103
            $embeddable_table = self::ALIAS_PREFIX . $embeddable_table;
104
        }
105
106
        return $embeddable_table . '.' . $embeddable_name;
107
    }
108
109
    private function traverseAssociationsForEmbeddables(EntityManager $entityManager, array $associations, $fieldNamePrefix = false)
110
    {
111
        $associationsEmbeddables = array();
112
113
        foreach ($associations as $association) {
114
            $classMetaData = $entityManager->getClassMetadata($association['targetEntity']);
115
116
            foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) {
117
                $associationsEmbeddables[] = implode('.', array_filter(array($fieldNamePrefix, $association['fieldName'], $embeddedClassKey)));
118
            }
119
120
            $associationMappings = $classMetaData->getAssociationMappings();
121
            $associationMappings = array_filter($associationMappings, function($associationMapping) {
122
                return $associationMapping['isOwningSide'] === true;
123
            });
124
125
            if (count($associationMappings) !== 0) {
126
                $traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $associationMappings, $association['fieldName']);
127
                $associationsEmbeddables = array_merge($associationsEmbeddables, $traversedAssociationsEmbeddables);
128
            }
129
        }
130
131
        return $associationsEmbeddables;
132
    }
133
134
    private function analizeEmbeddables(QueryBuilder $queryBuilder)
135
    {
136
        $embeddables = array();
137
        $entityManager = $queryBuilder->getEntityManager();
138
        $rootEntities = $queryBuilder->getRootEntities();
139
140
        foreach ($rootEntities as $rootEntity) {
141
            $classMetaData = $entityManager->getClassMetadata($rootEntity);
142
143
            foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) {
144
                $embeddables[] = $embeddedClassKey;
145
            }
146
147
            // Since this is a root entity embeddable, there is no need to join.
148
            foreach ($this->expectedJoinChains as $tablesToJoinKey => $tablesToJoin) {
149
                if (in_array(implode('.', $tablesToJoin), $embeddables)) {
150
                    unset($this->expectedJoinChains[$tablesToJoinKey]);
151
                }
152
            }
153
154
            $traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $classMetaData->getAssociationMappings());
155
            $embeddables = array_merge($embeddables, $traversedAssociationsEmbeddables);
156
        }
157
158
        return $embeddables;
159
    }
160
161
    /**
162
     * Builds an associative array of already joined tables and their alias.
163
     *
164
     * @param QueryBuilder $queryBuilder
165
     *
166
     * @return array
167
     */
168
    private function analizeJoinedTables(QueryBuilder $queryBuilder)
169
    {
170
        $joinMap = [];
171
        $joins   = $queryBuilder->getDQLPart('join');
172
        foreach (array_keys($joins) as $fromTable) {
173
            foreach ($joins[$fromTable] as $join) {
174
                $joinMap[$join->getJoin()] = $join->getAlias();
175
            }
176
        }
177
        return $joinMap;
178
    }
179
180
    private function autoJoin(QueryBuilder $queryBuilder)
181
    {
182
        foreach ($this->expectedJoinChains as $tablesToJoin) {
183
            // if the table is an embeddable, the property needs to be removed
184
            if (array_search(implode('.', $tablesToJoin), $this->embeddables) !== false) {
185
                array_pop($tablesToJoin);
186
            }
187
188
            // check if the first dimension is a known alias
189
            if (isset($this->knownAliases[$tablesToJoin[0]])) {
190
                $joinTo = $tablesToJoin[0];
191
                array_shift($tablesToJoin);
192
            } else { // if not, it's the root table
193
                $joinTo = $queryBuilder->getRootAliases()[0];
194
            }
195
196
            foreach ($tablesToJoin as $table) {
197
                $joinAlias = self::ALIAS_PREFIX . $table;
198
                $join      = sprintf('%s.%s', $joinTo, $table);
199
200
                if (!isset($this->joinMap[$join])) {
201
                    $this->joinMap[$join]           = $joinAlias;
202
                    $this->knownAliases[$joinAlias] = true;
203
204
                    $queryBuilder->join(sprintf('%s.%s', $joinTo, $table), $joinAlias);
205
                } else {
206
                    $joinAlias = $this->joinMap[$join];
207
                }
208
209
                $joinTo = $joinAlias;
210
            }
211
        }
212
    }
213
}
214