@@ -10,214 +10,214 @@ |
||
10 | 10 | |
11 | 11 | abstract class AbstractQueryFactory implements QueryFactory |
12 | 12 | { |
13 | - /** |
|
14 | - * @var TDBMService |
|
15 | - */ |
|
16 | - protected $tdbmService; |
|
17 | - |
|
18 | - /** |
|
19 | - * @var Schema |
|
20 | - */ |
|
21 | - protected $schema; |
|
22 | - |
|
23 | - /** |
|
24 | - * @var OrderByAnalyzer |
|
25 | - */ |
|
26 | - protected $orderByAnalyzer; |
|
27 | - |
|
28 | - /** |
|
29 | - * @var string|UncheckedOrderBy|null |
|
30 | - */ |
|
31 | - protected $orderBy; |
|
32 | - |
|
33 | - protected $magicSql; |
|
34 | - protected $magicSqlCount; |
|
35 | - protected $columnDescList; |
|
36 | - |
|
37 | - /** |
|
38 | - * @param TDBMService $tdbmService |
|
39 | - */ |
|
40 | - public function __construct(TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, $orderBy) |
|
41 | - { |
|
42 | - $this->tdbmService = $tdbmService; |
|
43 | - $this->schema = $schema; |
|
44 | - $this->orderByAnalyzer = $orderByAnalyzer; |
|
45 | - $this->orderBy = $orderBy; |
|
46 | - } |
|
47 | - |
|
48 | - /** |
|
49 | - * Returns the column list that must be fetched for the SQL request. |
|
50 | - * |
|
51 | - * Note: MySQL dictates that ORDER BYed columns should appear in the SELECT clause. |
|
52 | - * |
|
53 | - * @param string $mainTable |
|
54 | - * @param array $additionalTablesFetch |
|
55 | - * @param string|UncheckedOrderBy|null $orderBy |
|
56 | - * |
|
57 | - * @return array |
|
58 | - * |
|
59 | - * @throws \Doctrine\DBAL\Schema\SchemaException |
|
60 | - */ |
|
61 | - protected function getColumnsList(string $mainTable, array $additionalTablesFetch = array(), $orderBy = null) |
|
62 | - { |
|
63 | - // From the table name and the additional tables we want to fetch, let's build a list of all tables |
|
64 | - // that must be part of the select columns. |
|
65 | - |
|
66 | - $connection = $this->tdbmService->getConnection(); |
|
67 | - |
|
68 | - $tableGroups = []; |
|
69 | - $allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($mainTable); |
|
70 | - $tableGroupName = $this->getTableGroupName($allFetchedTables); |
|
71 | - foreach ($allFetchedTables as $table) { |
|
72 | - $tableGroups[$table] = $tableGroupName; |
|
73 | - } |
|
74 | - |
|
75 | - $columnsList = []; |
|
76 | - $columnDescList = []; |
|
77 | - $sortColumn = 0; |
|
78 | - $reconstructedOrderBy = null; |
|
79 | - |
|
80 | - if (is_string($orderBy)) { |
|
81 | - $orderBy = trim($orderBy); |
|
82 | - if ($orderBy === '') { |
|
83 | - $orderBy = null; |
|
84 | - } |
|
85 | - } |
|
86 | - |
|
87 | - // Now, let's deal with "order by columns" |
|
88 | - if ($orderBy !== null) { |
|
89 | - if ($orderBy instanceof UncheckedOrderBy) { |
|
90 | - $securedOrderBy = false; |
|
91 | - $orderBy = $orderBy->getOrderBy(); |
|
92 | - $reconstructedOrderBy = $orderBy; |
|
93 | - } else { |
|
94 | - $securedOrderBy = true; |
|
95 | - $reconstructedOrderBys = []; |
|
96 | - } |
|
97 | - $orderByColumns = $this->orderByAnalyzer->analyzeOrderBy($orderBy); |
|
98 | - |
|
99 | - // If we sort by a column, there is a high chance we will fetch the bean containing this column. |
|
100 | - // Hence, we should add the table to the $additionalTablesFetch |
|
101 | - foreach ($orderByColumns as $orderByColumn) { |
|
102 | - if ($orderByColumn['type'] === 'colref') { |
|
103 | - if ($orderByColumn['table'] !== null) { |
|
104 | - $additionalTablesFetch[] = $orderByColumn['table']; |
|
105 | - } |
|
106 | - if ($securedOrderBy) { |
|
107 | - $reconstructedOrderBys[] = ($orderByColumn['table'] !== null ? $connection->quoteIdentifier($orderByColumn['table']).'.' : '').$connection->quoteIdentifier($orderByColumn['column']).' '.$orderByColumn['direction']; |
|
108 | - } |
|
109 | - } elseif ($orderByColumn['type'] === 'expr') { |
|
110 | - $sortColumnName = 'sort_column_'.$sortColumn; |
|
111 | - $columnsList[] = $orderByColumn['expr'].' as '.$sortColumnName; |
|
112 | - $columnDescList[] = [ |
|
113 | - 'tableGroup' => null, |
|
114 | - ]; |
|
115 | - ++$sortColumn; |
|
116 | - |
|
117 | - if ($securedOrderBy) { |
|
118 | - throw new TDBMInvalidArgumentException('Invalid ORDER BY column: "'.$orderByColumn['expr'].'". If you want to use expression in your ORDER BY clause, you must wrap them in a UncheckedOrderBy object. For instance: new UncheckedOrderBy("col1 + col2 DESC")'); |
|
119 | - } |
|
120 | - } |
|
121 | - } |
|
122 | - |
|
123 | - if ($reconstructedOrderBy === null) { |
|
124 | - $reconstructedOrderBy = implode(', ', $reconstructedOrderBys); |
|
125 | - } |
|
126 | - } |
|
127 | - |
|
128 | - foreach ($additionalTablesFetch as $additionalTable) { |
|
129 | - $relatedTables = $this->tdbmService->_getRelatedTablesByInheritance($additionalTable); |
|
130 | - $tableGroupName = $this->getTableGroupName($relatedTables); |
|
131 | - foreach ($relatedTables as $table) { |
|
132 | - $tableGroups[$table] = $tableGroupName; |
|
133 | - } |
|
134 | - $allFetchedTables = array_merge($allFetchedTables, $relatedTables); |
|
135 | - } |
|
136 | - |
|
137 | - // Let's remove any duplicate |
|
138 | - $allFetchedTables = array_flip(array_flip($allFetchedTables)); |
|
139 | - |
|
140 | - // Now, let's build the column list |
|
141 | - foreach ($allFetchedTables as $table) { |
|
142 | - foreach ($this->schema->getTable($table)->getColumns() as $column) { |
|
143 | - $columnName = $column->getName(); |
|
144 | - $columnDescList[] = [ |
|
145 | - 'as' => $table.'____'.$columnName, |
|
146 | - 'table' => $table, |
|
147 | - 'column' => $columnName, |
|
148 | - 'type' => $column->getType(), |
|
149 | - 'tableGroup' => $tableGroups[$table], |
|
150 | - ]; |
|
151 | - $columnsList[] = $connection->quoteIdentifier($table).'.'.$connection->quoteIdentifier($columnName).' as '. |
|
152 | - $connection->quoteIdentifier($table.'____'.$columnName); |
|
153 | - } |
|
154 | - } |
|
155 | - |
|
156 | - return [$columnDescList, $columnsList, $reconstructedOrderBy]; |
|
157 | - } |
|
158 | - |
|
159 | - abstract protected function compute(); |
|
160 | - |
|
161 | - /** |
|
162 | - * Returns an identifier for the group of tables passed in parameter. |
|
163 | - * |
|
164 | - * @param string[] $relatedTables |
|
165 | - * |
|
166 | - * @return string |
|
167 | - */ |
|
168 | - protected function getTableGroupName(array $relatedTables) |
|
169 | - { |
|
170 | - sort($relatedTables); |
|
171 | - |
|
172 | - return implode('_``_', $relatedTables); |
|
173 | - } |
|
174 | - |
|
175 | - public function getMagicSql() : string |
|
176 | - { |
|
177 | - if ($this->magicSql === null) { |
|
178 | - $this->compute(); |
|
179 | - } |
|
180 | - |
|
181 | - return $this->magicSql; |
|
182 | - } |
|
183 | - |
|
184 | - public function getMagicSqlCount() : string |
|
185 | - { |
|
186 | - if ($this->magicSqlCount === null) { |
|
187 | - $this->compute(); |
|
188 | - } |
|
189 | - |
|
190 | - return $this->magicSqlCount; |
|
191 | - } |
|
192 | - |
|
193 | - public function getColumnDescriptors() : array |
|
194 | - { |
|
195 | - if ($this->columnDescList === null) { |
|
196 | - $this->compute(); |
|
197 | - } |
|
198 | - |
|
199 | - return $this->columnDescList; |
|
200 | - } |
|
201 | - |
|
202 | - /** |
|
203 | - * Sets the ORDER BY directive executed in SQL. |
|
204 | - * |
|
205 | - * For instance: |
|
206 | - * |
|
207 | - * $queryFactory->sort('label ASC, status DESC'); |
|
208 | - * |
|
209 | - * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter. |
|
210 | - * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter: |
|
211 | - * |
|
212 | - * $queryFactory->sort(new UncheckedOrderBy('RAND()')) |
|
213 | - * |
|
214 | - * @param string|UncheckedOrderBy|null $orderBy |
|
215 | - */ |
|
216 | - public function sort($orderBy) |
|
217 | - { |
|
218 | - $this->orderBy = $orderBy; |
|
219 | - $this->magicSql = null; |
|
220 | - $this->magicSqlCount = null; |
|
221 | - $this->columnDescList = null; |
|
222 | - } |
|
13 | + /** |
|
14 | + * @var TDBMService |
|
15 | + */ |
|
16 | + protected $tdbmService; |
|
17 | + |
|
18 | + /** |
|
19 | + * @var Schema |
|
20 | + */ |
|
21 | + protected $schema; |
|
22 | + |
|
23 | + /** |
|
24 | + * @var OrderByAnalyzer |
|
25 | + */ |
|
26 | + protected $orderByAnalyzer; |
|
27 | + |
|
28 | + /** |
|
29 | + * @var string|UncheckedOrderBy|null |
|
30 | + */ |
|
31 | + protected $orderBy; |
|
32 | + |
|
33 | + protected $magicSql; |
|
34 | + protected $magicSqlCount; |
|
35 | + protected $columnDescList; |
|
36 | + |
|
37 | + /** |
|
38 | + * @param TDBMService $tdbmService |
|
39 | + */ |
|
40 | + public function __construct(TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, $orderBy) |
|
41 | + { |
|
42 | + $this->tdbmService = $tdbmService; |
|
43 | + $this->schema = $schema; |
|
44 | + $this->orderByAnalyzer = $orderByAnalyzer; |
|
45 | + $this->orderBy = $orderBy; |
|
46 | + } |
|
47 | + |
|
48 | + /** |
|
49 | + * Returns the column list that must be fetched for the SQL request. |
|
50 | + * |
|
51 | + * Note: MySQL dictates that ORDER BYed columns should appear in the SELECT clause. |
|
52 | + * |
|
53 | + * @param string $mainTable |
|
54 | + * @param array $additionalTablesFetch |
|
55 | + * @param string|UncheckedOrderBy|null $orderBy |
|
56 | + * |
|
57 | + * @return array |
|
58 | + * |
|
59 | + * @throws \Doctrine\DBAL\Schema\SchemaException |
|
60 | + */ |
|
61 | + protected function getColumnsList(string $mainTable, array $additionalTablesFetch = array(), $orderBy = null) |
|
62 | + { |
|
63 | + // From the table name and the additional tables we want to fetch, let's build a list of all tables |
|
64 | + // that must be part of the select columns. |
|
65 | + |
|
66 | + $connection = $this->tdbmService->getConnection(); |
|
67 | + |
|
68 | + $tableGroups = []; |
|
69 | + $allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($mainTable); |
|
70 | + $tableGroupName = $this->getTableGroupName($allFetchedTables); |
|
71 | + foreach ($allFetchedTables as $table) { |
|
72 | + $tableGroups[$table] = $tableGroupName; |
|
73 | + } |
|
74 | + |
|
75 | + $columnsList = []; |
|
76 | + $columnDescList = []; |
|
77 | + $sortColumn = 0; |
|
78 | + $reconstructedOrderBy = null; |
|
79 | + |
|
80 | + if (is_string($orderBy)) { |
|
81 | + $orderBy = trim($orderBy); |
|
82 | + if ($orderBy === '') { |
|
83 | + $orderBy = null; |
|
84 | + } |
|
85 | + } |
|
86 | + |
|
87 | + // Now, let's deal with "order by columns" |
|
88 | + if ($orderBy !== null) { |
|
89 | + if ($orderBy instanceof UncheckedOrderBy) { |
|
90 | + $securedOrderBy = false; |
|
91 | + $orderBy = $orderBy->getOrderBy(); |
|
92 | + $reconstructedOrderBy = $orderBy; |
|
93 | + } else { |
|
94 | + $securedOrderBy = true; |
|
95 | + $reconstructedOrderBys = []; |
|
96 | + } |
|
97 | + $orderByColumns = $this->orderByAnalyzer->analyzeOrderBy($orderBy); |
|
98 | + |
|
99 | + // If we sort by a column, there is a high chance we will fetch the bean containing this column. |
|
100 | + // Hence, we should add the table to the $additionalTablesFetch |
|
101 | + foreach ($orderByColumns as $orderByColumn) { |
|
102 | + if ($orderByColumn['type'] === 'colref') { |
|
103 | + if ($orderByColumn['table'] !== null) { |
|
104 | + $additionalTablesFetch[] = $orderByColumn['table']; |
|
105 | + } |
|
106 | + if ($securedOrderBy) { |
|
107 | + $reconstructedOrderBys[] = ($orderByColumn['table'] !== null ? $connection->quoteIdentifier($orderByColumn['table']).'.' : '').$connection->quoteIdentifier($orderByColumn['column']).' '.$orderByColumn['direction']; |
|
108 | + } |
|
109 | + } elseif ($orderByColumn['type'] === 'expr') { |
|
110 | + $sortColumnName = 'sort_column_'.$sortColumn; |
|
111 | + $columnsList[] = $orderByColumn['expr'].' as '.$sortColumnName; |
|
112 | + $columnDescList[] = [ |
|
113 | + 'tableGroup' => null, |
|
114 | + ]; |
|
115 | + ++$sortColumn; |
|
116 | + |
|
117 | + if ($securedOrderBy) { |
|
118 | + throw new TDBMInvalidArgumentException('Invalid ORDER BY column: "'.$orderByColumn['expr'].'". If you want to use expression in your ORDER BY clause, you must wrap them in a UncheckedOrderBy object. For instance: new UncheckedOrderBy("col1 + col2 DESC")'); |
|
119 | + } |
|
120 | + } |
|
121 | + } |
|
122 | + |
|
123 | + if ($reconstructedOrderBy === null) { |
|
124 | + $reconstructedOrderBy = implode(', ', $reconstructedOrderBys); |
|
125 | + } |
|
126 | + } |
|
127 | + |
|
128 | + foreach ($additionalTablesFetch as $additionalTable) { |
|
129 | + $relatedTables = $this->tdbmService->_getRelatedTablesByInheritance($additionalTable); |
|
130 | + $tableGroupName = $this->getTableGroupName($relatedTables); |
|
131 | + foreach ($relatedTables as $table) { |
|
132 | + $tableGroups[$table] = $tableGroupName; |
|
133 | + } |
|
134 | + $allFetchedTables = array_merge($allFetchedTables, $relatedTables); |
|
135 | + } |
|
136 | + |
|
137 | + // Let's remove any duplicate |
|
138 | + $allFetchedTables = array_flip(array_flip($allFetchedTables)); |
|
139 | + |
|
140 | + // Now, let's build the column list |
|
141 | + foreach ($allFetchedTables as $table) { |
|
142 | + foreach ($this->schema->getTable($table)->getColumns() as $column) { |
|
143 | + $columnName = $column->getName(); |
|
144 | + $columnDescList[] = [ |
|
145 | + 'as' => $table.'____'.$columnName, |
|
146 | + 'table' => $table, |
|
147 | + 'column' => $columnName, |
|
148 | + 'type' => $column->getType(), |
|
149 | + 'tableGroup' => $tableGroups[$table], |
|
150 | + ]; |
|
151 | + $columnsList[] = $connection->quoteIdentifier($table).'.'.$connection->quoteIdentifier($columnName).' as '. |
|
152 | + $connection->quoteIdentifier($table.'____'.$columnName); |
|
153 | + } |
|
154 | + } |
|
155 | + |
|
156 | + return [$columnDescList, $columnsList, $reconstructedOrderBy]; |
|
157 | + } |
|
158 | + |
|
159 | + abstract protected function compute(); |
|
160 | + |
|
161 | + /** |
|
162 | + * Returns an identifier for the group of tables passed in parameter. |
|
163 | + * |
|
164 | + * @param string[] $relatedTables |
|
165 | + * |
|
166 | + * @return string |
|
167 | + */ |
|
168 | + protected function getTableGroupName(array $relatedTables) |
|
169 | + { |
|
170 | + sort($relatedTables); |
|
171 | + |
|
172 | + return implode('_``_', $relatedTables); |
|
173 | + } |
|
174 | + |
|
175 | + public function getMagicSql() : string |
|
176 | + { |
|
177 | + if ($this->magicSql === null) { |
|
178 | + $this->compute(); |
|
179 | + } |
|
180 | + |
|
181 | + return $this->magicSql; |
|
182 | + } |
|
183 | + |
|
184 | + public function getMagicSqlCount() : string |
|
185 | + { |
|
186 | + if ($this->magicSqlCount === null) { |
|
187 | + $this->compute(); |
|
188 | + } |
|
189 | + |
|
190 | + return $this->magicSqlCount; |
|
191 | + } |
|
192 | + |
|
193 | + public function getColumnDescriptors() : array |
|
194 | + { |
|
195 | + if ($this->columnDescList === null) { |
|
196 | + $this->compute(); |
|
197 | + } |
|
198 | + |
|
199 | + return $this->columnDescList; |
|
200 | + } |
|
201 | + |
|
202 | + /** |
|
203 | + * Sets the ORDER BY directive executed in SQL. |
|
204 | + * |
|
205 | + * For instance: |
|
206 | + * |
|
207 | + * $queryFactory->sort('label ASC, status DESC'); |
|
208 | + * |
|
209 | + * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter. |
|
210 | + * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter: |
|
211 | + * |
|
212 | + * $queryFactory->sort(new UncheckedOrderBy('RAND()')) |
|
213 | + * |
|
214 | + * @param string|UncheckedOrderBy|null $orderBy |
|
215 | + */ |
|
216 | + public function sort($orderBy) |
|
217 | + { |
|
218 | + $this->orderBy = $orderBy; |
|
219 | + $this->magicSql = null; |
|
220 | + $this->magicSqlCount = null; |
|
221 | + $this->columnDescList = null; |
|
222 | + } |
|
223 | 223 | } |