Completed
Pull Request — master (#28)
by
unknown
02:17
created

AutoJoin::getJoinAlias()   C

Complexity

Conditions 8
Paths 24

Size

Total Lines 47
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 47
rs 5.7377
cc 8
eloc 23
nc 24
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
62
        // the table is identified as an embeddable
63
        if (array_search($table, $this->embeddables) !== false) {
64
            $embeddable_dimensions = explode('.', $table);
65
66
            if (count($embeddable_dimensions) === 1) {
67
                // the embeddable is not inside an association, so we use the root alias prefix
68
                return $this->queryBuilder->getRootAlias() . '.' . $table;
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated with message: Please use $qb->getRootAliases() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
69
            }
70
            else {
71
                // remove the embeddable's property
72
                array_pop($embeddable_dimensions);
73
                $table_alias = implode('.', $embeddable_dimensions);
74
75
                if (isset($this->knownAliases[$table_alias])) {
76
                    return $table;
77
                } else {
78
                    return self::ALIAS_PREFIX . $table;
79
                }
80
            }
81
        }
82
83
        // the table name is a known alias (already join for instance) so we
84
        // don't need to do anything.
85
        if (isset($this->knownAliases[$table])) {
86
            return $table;
87
        }
88
89
        // otherwise the table should have automatically been joined, so we use our table prefix
90
        if (isset($this->knownAliases[self::ALIAS_PREFIX.$table])) {
91
            return self::ALIAS_PREFIX . $table;
92
        }
93
94
        throw new \RuntimeException(sprintf('Could not automatically join table "%s"', $table));
95
    }
96
97
    private function traverseAssociationsForEmbeddables(EntityManager $entityManager, array $associations, $fieldNamePrefix = false)
98
    {
99
        $associationsEmbeddables = array();
100
101
        foreach ($associations as $association) {
102
            $classMetaData = $entityManager->getClassMetadata($association['targetEntity']);
103
104
            foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) {
105
                $associationsEmbeddables[] = implode('.', array_filter(array($fieldNamePrefix, $association['fieldName'], $embeddedClassKey)));
106
            }
107
108
            $associationMappings = $classMetaData->getAssociationMappings();
109
            $associationMappings = array_filter($associationMappings, function($associationMapping) {
110
                return $associationMapping['isOwningSide'] === false;
111
            });
112
113
            if (count($associationMappings) !== 0) {
114
                $traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $associationMappings, $association['fieldName']);
115
                $associationsEmbeddables = array_merge($associationsEmbeddables, $traversedAssociationsEmbeddables);
116
            }
117
        }
118
119
        return $associationsEmbeddables;
120
    }
121
122
    private function analizeEmbeddables(QueryBuilder $queryBuilder)
123
    {
124
        $embeddables = array();
125
        $entityManager = $queryBuilder->getEntityManager();
126
        $rootEntities = $queryBuilder->getRootEntities();
127
128
        foreach ($rootEntities as $rootEntity) {
129
            $classMetaData = $entityManager->getClassMetadata($rootEntity);
130
131
            foreach ($classMetaData->embeddedClasses as $embeddedClassKey => $embeddedClass) {
132
                $embeddables[] = $embeddedClassKey;
133
            }
134
135
            $traversedAssociationsEmbeddables = $this->traverseAssociationsForEmbeddables($entityManager, $classMetaData->getAssociationMappings());
136
            $embeddables = array_merge($embeddables, $traversedAssociationsEmbeddables);
137
        }
138
139
        return $embeddables;
140
    }
141
142
    /**
143
     * Builds an associative array of already joined tables and their alias.
144
     *
145
     * @param QueryBuilder $queryBuilder
146
     *
147
     * @return array
148
     */
149
    private function analizeJoinedTables(QueryBuilder $queryBuilder)
150
    {
151
        $joinMap = [];
152
        $joins   = $queryBuilder->getDQLPart('join');
153
        foreach (array_keys($joins) as $fromTable) {
154
            foreach ($joins[$fromTable] as $join) {
155
                $joinMap[$join->getJoin()] = $join->getAlias();
156
            }
157
        }
158
        return $joinMap;
159
    }
160
161
    private function autoJoin(QueryBuilder $queryBuilder)
162
    {
163
        foreach ($this->expectedJoinChains as $tablesToJoin) {
164
            // if the table is an embeddable, the property needs to be removed
165
            if (array_search(implode('.', $tablesToJoin), $this->embeddables) !== false) {
166
                array_pop($tablesToJoin);
167
            }
168
169
            // check if the first dimension is a known alias
170
            if (isset($this->knownAliases[$tablesToJoin[0]])) {
171
                $joinTo = $tablesToJoin[0];
172
                array_pop($tablesToJoin);
173
            } else { // if not, it's the root table
174
                $joinTo = $queryBuilder->getRootAliases()[0];
175
            }
176
177
            foreach ($tablesToJoin as $table) {
178
                $joinAlias = self::ALIAS_PREFIX . $table;
179
                $join      = sprintf('%s.%s', $joinTo, $table);
180
181
                if (!isset($this->joinMap[$join])) {
182
                    $this->joinMap[$join]           = $joinAlias;
183
                    $this->knownAliases[$joinAlias] = true;
184
185
                    $queryBuilder->join(sprintf('%s.%s', $joinTo, $table), $joinAlias);
186
                } else {
187
                    $joinAlias = $this->joinMap[$join];
188
                }
189
190
                $joinTo = $joinAlias;
191
            }
192
        }
193
    }
194
}
195