1
|
|
|
<?php |
2
|
|
|
namespace Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\QueryBuilder; |
3
|
|
|
|
4
|
|
|
use Doctrine\ORM\Query\Expr\Select; |
5
|
|
|
use Netdudes\DataSourceryBundle\DataSource\Configuration\Field; |
6
|
|
|
use Netdudes\DataSourceryBundle\Query\Query; |
7
|
|
|
|
8
|
|
|
class SelectGenerator |
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* @var JoinGenerator |
12
|
|
|
*/ |
13
|
|
|
private $joinsGenerator; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* @var RequiredFieldsExtractor |
17
|
|
|
*/ |
18
|
|
|
private $requiredFieldsExtractor; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var Field[] |
22
|
|
|
*/ |
23
|
|
|
private $queryBuilderDataSourceFields; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var string |
27
|
|
|
*/ |
28
|
|
|
private $fromAlias; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var Select[][] |
32
|
|
|
*/ |
33
|
|
|
private $selectFieldMapCache = []; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var Select[] |
37
|
|
|
*/ |
38
|
|
|
private $selectCache = []; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @param array $queryBuilderDataSourceFields |
42
|
|
|
* @param $fromAlias |
43
|
|
|
* @param JoinGenerator $joinsGenerator |
44
|
|
|
* @param RequiredFieldsExtractor $requiredFieldsExtractor |
45
|
|
|
*/ |
46
|
|
|
public function __construct(array $queryBuilderDataSourceFields, $fromAlias, JoinGenerator $joinsGenerator, RequiredFieldsExtractor $requiredFieldsExtractor) |
47
|
|
|
{ |
48
|
|
|
$this->joinsGenerator = $joinsGenerator; |
49
|
|
|
$this->requiredFieldsExtractor = $requiredFieldsExtractor; |
50
|
|
|
$this->queryBuilderDataSourceFields = $queryBuilderDataSourceFields; |
51
|
|
|
$this->fromAlias = $fromAlias; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Generates the SELECT part of the DQL (Select object) given a Query. |
56
|
|
|
* |
57
|
|
|
* @param Query $query |
58
|
|
|
* |
59
|
|
|
* @return Select|null |
60
|
|
|
*/ |
61
|
|
View Code Duplication |
public function generate(Query $query) |
|
|
|
|
62
|
|
|
{ |
63
|
|
|
$uniqueId = spl_object_hash($query); |
64
|
|
|
if (!isset($this->selectCache[$uniqueId])) { |
65
|
|
|
$this->selectCache[$uniqueId] = $this->build($query); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
return $this->selectCache[$uniqueId]; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
protected function build(Query $query) |
72
|
|
|
{ |
73
|
|
|
$selectFieldMap = $this->getSelectFieldMap($query); |
74
|
|
|
|
75
|
|
|
$selectStatements = []; |
76
|
|
|
foreach ($selectFieldMap as $identifier => $selectField) { |
77
|
|
|
$selectStatements[] = $selectField . ' ' . $identifier; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
if (empty($selectStatements)) { |
81
|
|
|
return new Select(); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return new Select($selectStatements); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @param Query $query |
89
|
|
|
* |
90
|
|
|
* @return string[] |
91
|
|
|
*/ |
92
|
|
View Code Duplication |
public function getSelectFieldMap(Query $query) |
|
|
|
|
93
|
|
|
{ |
94
|
|
|
$uniqueId = spl_object_hash($query); |
95
|
|
|
if (!isset($this->selectFieldMapCache[$uniqueId])) { |
96
|
|
|
$this->selectFieldMapCache[$uniqueId] = $this->buildSelectFieldMap($query); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $this->selectFieldMapCache[$uniqueId]; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @param Query $query |
104
|
|
|
* |
105
|
|
|
* @return array |
106
|
|
|
*/ |
107
|
|
|
public function getUniqueNameToSelectFieldMap(Query $query) |
108
|
|
|
{ |
109
|
|
|
$selectFieldMap = $this->getSelectFieldMap($query); |
110
|
|
|
$uniqueNameToSelectFieldMap = []; |
111
|
|
|
foreach ($this->queryBuilderDataSourceFields as $field) { |
112
|
|
|
$alias = $field->getDatabaseSelectAlias(); |
113
|
|
|
if (is_array($alias)) { |
114
|
|
|
$alias = str_replace('.', '_', $field->getDatabaseFilterQueryField()); |
115
|
|
|
} |
116
|
|
|
if (array_key_exists($alias, $selectFieldMap)) { |
117
|
|
|
$uniqueNameToSelectFieldMap[$field->getUniqueName()] = $selectFieldMap[$alias]; |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
return $uniqueNameToSelectFieldMap; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Builds a map relating the database field alias (the alias in the SELECT statement) and the |
126
|
|
|
* actual entity field (as in JOINED_ENTITY.field or FROM_ENTITY.field) in order to use them |
127
|
|
|
* within the WHERE statement. |
128
|
|
|
* |
129
|
|
|
* @param Query $query |
130
|
|
|
* |
131
|
|
|
* @return array |
132
|
|
|
*/ |
133
|
|
|
protected function buildSelectFieldMap(Query $query) |
134
|
|
|
{ |
135
|
|
|
$selectFieldsMap = []; |
136
|
|
|
$requiredFields = $this->requiredFieldsExtractor->extractRequiredFields($query); |
137
|
|
|
$joins = $this->joinsGenerator->generate($query); |
138
|
|
|
foreach ($this->queryBuilderDataSourceFields as $element) { |
139
|
|
|
if (!in_array($element->getUniqueName(), $requiredFields, true)) { |
140
|
|
|
continue; |
141
|
|
|
} |
142
|
|
|
if (is_null($element->getDatabaseFilterQueryField())) { |
143
|
|
|
continue; |
144
|
|
|
} |
145
|
|
|
$fieldIdentifier = $element->getDatabaseSelectAlias(); |
146
|
|
|
if (is_array($fieldIdentifier)) { |
147
|
|
|
$fieldIdentifier = str_replace('.', '_', $element->getDatabaseFilterQueryField()); |
148
|
|
|
} |
149
|
|
|
$fieldParts = explode('.', $element->getDatabaseFilterQueryField()); |
150
|
|
|
if (count($fieldParts) === 1) { |
151
|
|
|
$selectFieldsMap[$fieldIdentifier] = $this->fromAlias . '.' . array_slice($fieldParts, -1, 1)[0]; |
152
|
|
|
} else { |
153
|
|
|
$joinField = implode('.', array_slice($fieldParts, 0, -1)); |
154
|
|
|
foreach ($joins as $path => $join) { |
155
|
|
|
if ($path == $joinField) { |
156
|
|
|
$selectFieldsMap[$fieldIdentifier] = $join->getAlias() . '.' . array_slice($fieldParts, -1, 1)[0]; |
157
|
|
|
break; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
return $selectFieldsMap; |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.