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