These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /* |
||
3 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
4 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
5 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||
6 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||
7 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||
8 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||
9 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||
10 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||
11 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
12 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||
13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
14 | * |
||
15 | * This software consists of voluntary contributions made by many individuals |
||
16 | * and is licensed under the MIT license. For more information, see |
||
17 | * <http://www.doctrine-project.org>. |
||
18 | */ |
||
19 | |||
20 | namespace Doctrine\ORM\Persisters\Entity; |
||
21 | |||
22 | use Doctrine\Common\Collections\Criteria; |
||
23 | use Doctrine\Common\Collections\Expr\Comparison; |
||
24 | use Doctrine\Common\Util\ClassUtils; |
||
25 | use Doctrine\DBAL\Connection; |
||
26 | use Doctrine\DBAL\LockMode; |
||
27 | use Doctrine\DBAL\Types\Type; |
||
28 | use Doctrine\ORM\EntityManagerInterface; |
||
29 | use Doctrine\ORM\Mapping\ClassMetadata; |
||
30 | use Doctrine\ORM\Mapping\MappingException; |
||
31 | use Doctrine\ORM\OptimisticLockException; |
||
32 | use Doctrine\ORM\ORMException; |
||
33 | use Doctrine\ORM\PersistentCollection; |
||
34 | use Doctrine\ORM\Persisters\SqlExpressionVisitor; |
||
35 | use Doctrine\ORM\Persisters\SqlValueVisitor; |
||
36 | use Doctrine\ORM\Query; |
||
37 | use Doctrine\ORM\UnitOfWork; |
||
38 | use Doctrine\ORM\Utility\IdentifierFlattener; |
||
39 | use Doctrine\ORM\Utility\PersisterHelper; |
||
40 | |||
41 | /** |
||
42 | * A BasicEntityPersister maps an entity to a single table in a relational database. |
||
43 | * |
||
44 | * A persister is always responsible for a single entity type. |
||
45 | * |
||
46 | * EntityPersisters are used during a UnitOfWork to apply any changes to the persistent |
||
47 | * state of entities onto a relational database when the UnitOfWork is committed, |
||
48 | * as well as for basic querying of entities and their associations (not DQL). |
||
49 | * |
||
50 | * The persisting operations that are invoked during a commit of a UnitOfWork to |
||
51 | * persist the persistent entity state are: |
||
52 | * |
||
53 | * - {@link addInsert} : To schedule an entity for insertion. |
||
54 | * - {@link executeInserts} : To execute all scheduled insertions. |
||
55 | * - {@link update} : To update the persistent state of an entity. |
||
56 | * - {@link delete} : To delete the persistent state of an entity. |
||
57 | * |
||
58 | * As can be seen from the above list, insertions are batched and executed all at once |
||
59 | * for increased efficiency. |
||
60 | * |
||
61 | * The querying operations invoked during a UnitOfWork, either through direct find |
||
62 | * requests or lazy-loading, are the following: |
||
63 | * |
||
64 | * - {@link load} : Loads (the state of) a single, managed entity. |
||
65 | * - {@link loadAll} : Loads multiple, managed entities. |
||
66 | * - {@link loadOneToOneEntity} : Loads a one/many-to-one entity association (lazy-loading). |
||
67 | * - {@link loadOneToManyCollection} : Loads a one-to-many entity association (lazy-loading). |
||
68 | * - {@link loadManyToManyCollection} : Loads a many-to-many entity association (lazy-loading). |
||
69 | * |
||
70 | * The BasicEntityPersister implementation provides the default behavior for |
||
71 | * persisting and querying entities that are mapped to a single database table. |
||
72 | * |
||
73 | * Subclasses can be created to provide custom persisting and querying strategies, |
||
74 | * i.e. spanning multiple tables. |
||
75 | * |
||
76 | * @author Roman Borschel <[email protected]> |
||
77 | * @author Giorgio Sironi <[email protected]> |
||
78 | * @author Benjamin Eberlei <[email protected]> |
||
79 | * @author Alexander <[email protected]> |
||
80 | * @author Fabio B. Silva <[email protected]> |
||
81 | * @author Rob Caiger <[email protected]> |
||
82 | * @since 2.0 |
||
83 | */ |
||
84 | class BasicEntityPersister implements EntityPersister |
||
85 | { |
||
86 | /** |
||
87 | * @var array |
||
88 | */ |
||
89 | static private $comparisonMap = [ |
||
90 | Comparison::EQ => '= %s', |
||
91 | Comparison::IS => '= %s', |
||
92 | Comparison::NEQ => '!= %s', |
||
93 | Comparison::GT => '> %s', |
||
94 | Comparison::GTE => '>= %s', |
||
95 | Comparison::LT => '< %s', |
||
96 | Comparison::LTE => '<= %s', |
||
97 | Comparison::IN => 'IN (%s)', |
||
98 | Comparison::NIN => 'NOT IN (%s)', |
||
99 | Comparison::CONTAINS => 'LIKE %s', |
||
100 | Comparison::STARTS_WITH => 'LIKE %s', |
||
101 | Comparison::ENDS_WITH => 'LIKE %s', |
||
102 | ]; |
||
103 | |||
104 | /** |
||
105 | * Metadata object that describes the mapping of the mapped entity class. |
||
106 | * |
||
107 | * @var \Doctrine\ORM\Mapping\ClassMetadata |
||
108 | */ |
||
109 | protected $class; |
||
110 | |||
111 | /** |
||
112 | * The underlying DBAL Connection of the used EntityManager. |
||
113 | * |
||
114 | * @var \Doctrine\DBAL\Connection $conn |
||
115 | */ |
||
116 | protected $conn; |
||
117 | |||
118 | /** |
||
119 | * The database platform. |
||
120 | * |
||
121 | * @var \Doctrine\DBAL\Platforms\AbstractPlatform |
||
122 | */ |
||
123 | protected $platform; |
||
124 | |||
125 | /** |
||
126 | * The EntityManager instance. |
||
127 | * |
||
128 | * @var EntityManagerInterface |
||
129 | */ |
||
130 | protected $em; |
||
131 | |||
132 | /** |
||
133 | * Queued inserts. |
||
134 | * |
||
135 | * @var array |
||
136 | */ |
||
137 | protected $queuedInserts = []; |
||
138 | |||
139 | /** |
||
140 | * The map of column names to DBAL mapping types of all prepared columns used |
||
141 | * when INSERTing or UPDATEing an entity. |
||
142 | * |
||
143 | * @var array |
||
144 | * |
||
145 | * @see prepareInsertData($entity) |
||
146 | * @see prepareUpdateData($entity) |
||
147 | */ |
||
148 | protected $columnTypes = []; |
||
149 | |||
150 | /** |
||
151 | * The map of quoted column names. |
||
152 | * |
||
153 | * @var array |
||
154 | * |
||
155 | * @see prepareInsertData($entity) |
||
156 | * @see prepareUpdateData($entity) |
||
157 | */ |
||
158 | protected $quotedColumns = []; |
||
159 | |||
160 | /** |
||
161 | * The INSERT SQL statement used for entities handled by this persister. |
||
162 | * This SQL is only generated once per request, if at all. |
||
163 | * |
||
164 | * @var string |
||
165 | */ |
||
166 | private $insertSql; |
||
167 | |||
168 | /** |
||
169 | * The quote strategy. |
||
170 | * |
||
171 | * @var \Doctrine\ORM\Mapping\QuoteStrategy |
||
172 | */ |
||
173 | protected $quoteStrategy; |
||
174 | |||
175 | /** |
||
176 | * The IdentifierFlattener used for manipulating identifiers |
||
177 | * |
||
178 | * @var \Doctrine\ORM\Utility\IdentifierFlattener |
||
179 | */ |
||
180 | private $identifierFlattener; |
||
181 | |||
182 | /** |
||
183 | * @var CachedPersisterContext |
||
184 | */ |
||
185 | protected $currentPersisterContext; |
||
186 | |||
187 | /** |
||
188 | * @var CachedPersisterContext |
||
189 | */ |
||
190 | private $limitsHandlingContext; |
||
191 | |||
192 | /** |
||
193 | * @var CachedPersisterContext |
||
194 | */ |
||
195 | private $noLimitsContext; |
||
196 | |||
197 | /** |
||
198 | * Initializes a new <tt>BasicEntityPersister</tt> that uses the given EntityManager |
||
199 | * and persists instances of the class described by the given ClassMetadata descriptor. |
||
200 | * |
||
201 | * @param EntityManagerInterface $em |
||
202 | * @param ClassMetadata $class |
||
203 | */ |
||
204 | 1150 | public function __construct(EntityManagerInterface $em, ClassMetadata $class) |
|
205 | { |
||
206 | 1150 | $this->em = $em; |
|
207 | 1150 | $this->class = $class; |
|
208 | 1150 | $this->conn = $em->getConnection(); |
|
209 | 1150 | $this->platform = $this->conn->getDatabasePlatform(); |
|
210 | 1150 | $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); |
|
211 | 1150 | $this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory()); |
|
212 | 1150 | $this->noLimitsContext = $this->currentPersisterContext = new CachedPersisterContext( |
|
213 | 1150 | $class, |
|
214 | 1150 | new Query\ResultSetMapping(), |
|
215 | 1150 | false |
|
216 | ); |
||
217 | 1150 | $this->limitsHandlingContext = new CachedPersisterContext( |
|
218 | 1150 | $class, |
|
219 | 1150 | new Query\ResultSetMapping(), |
|
220 | 1150 | true |
|
221 | ); |
||
222 | 1150 | } |
|
223 | |||
224 | /** |
||
225 | * {@inheritdoc} |
||
226 | */ |
||
227 | 18 | public function getClassMetadata() |
|
228 | { |
||
229 | 18 | return $this->class; |
|
230 | } |
||
231 | |||
232 | /** |
||
233 | * {@inheritdoc} |
||
234 | */ |
||
235 | 11 | public function getResultSetMapping() |
|
236 | { |
||
237 | 11 | return $this->currentPersisterContext->rsm; |
|
238 | } |
||
239 | |||
240 | /** |
||
241 | * {@inheritdoc} |
||
242 | */ |
||
243 | 1033 | public function addInsert($entity) |
|
244 | { |
||
245 | 1033 | $this->queuedInserts[spl_object_hash($entity)] = $entity; |
|
246 | 1033 | } |
|
247 | |||
248 | /** |
||
249 | * {@inheritdoc} |
||
250 | */ |
||
251 | 94 | public function getInserts() |
|
252 | { |
||
253 | 94 | return $this->queuedInserts; |
|
254 | } |
||
255 | |||
256 | /** |
||
257 | * {@inheritdoc} |
||
258 | */ |
||
259 | 1007 | public function executeInserts() |
|
260 | { |
||
261 | 1007 | if ( ! $this->queuedInserts) { |
|
262 | 640 | return []; |
|
263 | } |
||
264 | |||
265 | 958 | $postInsertIds = []; |
|
266 | 958 | $idGenerator = $this->class->idGenerator; |
|
267 | 958 | $isPostInsertId = $idGenerator->isPostInsertGenerator(); |
|
268 | |||
269 | 958 | $stmt = $this->conn->prepare($this->getInsertSQL()); |
|
270 | 958 | $tableName = $this->class->getTableName(); |
|
271 | |||
272 | 958 | foreach ($this->queuedInserts as $entity) { |
|
273 | 958 | $insertData = $this->prepareInsertData($entity); |
|
274 | |||
275 | 958 | if (isset($insertData[$tableName])) { |
|
276 | 931 | $paramIndex = 1; |
|
277 | |||
278 | 931 | foreach ($insertData[$tableName] as $column => $value) { |
|
279 | 931 | $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$column]); |
|
280 | } |
||
281 | } |
||
282 | |||
283 | 958 | $stmt->execute(); |
|
284 | |||
285 | 957 | View Code Duplication | if ($isPostInsertId) { |
286 | 868 | $generatedId = $idGenerator->generate($this->em, $entity); |
|
287 | $id = [ |
||
288 | 868 | $this->class->identifier[0] => $generatedId |
|
289 | ]; |
||
290 | 868 | $postInsertIds[] = [ |
|
291 | 868 | 'generatedId' => $generatedId, |
|
292 | 868 | 'entity' => $entity, |
|
293 | ]; |
||
294 | } else { |
||
295 | 262 | $id = $this->class->getIdentifierValues($entity); |
|
296 | } |
||
297 | |||
298 | 957 | if ($this->class->isVersioned) { |
|
299 | 957 | $this->assignDefaultVersionValue($entity, $id); |
|
300 | } |
||
301 | } |
||
302 | |||
303 | 957 | $stmt->closeCursor(); |
|
304 | 957 | $this->queuedInserts = []; |
|
305 | |||
306 | 957 | return $postInsertIds; |
|
307 | } |
||
308 | |||
309 | /** |
||
310 | * Retrieves the default version value which was created |
||
311 | * by the preceding INSERT statement and assigns it back in to the |
||
312 | * entities version field. |
||
313 | * |
||
314 | * @param object $entity |
||
315 | * @param array $id |
||
316 | * |
||
317 | * @return void |
||
318 | */ |
||
319 | 200 | protected function assignDefaultVersionValue($entity, array $id) |
|
320 | { |
||
321 | 200 | $value = $this->fetchVersionValue($this->class, $id); |
|
322 | |||
323 | 200 | $this->class->setFieldValue($entity, $this->class->versionField, $value); |
|
324 | 200 | } |
|
325 | |||
326 | /** |
||
327 | * Fetches the current version value of a versioned entity. |
||
328 | * |
||
329 | * @param \Doctrine\ORM\Mapping\ClassMetadata $versionedClass |
||
330 | * @param array $id |
||
331 | * |
||
332 | * @return mixed |
||
333 | */ |
||
334 | 209 | protected function fetchVersionValue($versionedClass, array $id) |
|
335 | { |
||
336 | 209 | $versionField = $versionedClass->versionField; |
|
337 | 209 | $fieldMapping = $versionedClass->fieldMappings[$versionField]; |
|
338 | 209 | $tableName = $this->quoteStrategy->getTableName($versionedClass, $this->platform); |
|
339 | 209 | $identifier = $this->quoteStrategy->getIdentifierColumnNames($versionedClass, $this->platform); |
|
340 | 209 | $columnName = $this->quoteStrategy->getColumnName($versionField, $versionedClass, $this->platform); |
|
341 | |||
342 | // FIXME: Order with composite keys might not be correct |
||
343 | 209 | $sql = 'SELECT ' . $columnName |
|
344 | 209 | . ' FROM ' . $tableName |
|
345 | 209 | . ' WHERE ' . implode(' = ? AND ', $identifier) . ' = ?'; |
|
346 | |||
347 | |||
348 | 209 | $flatId = $this->identifierFlattener->flattenIdentifier($versionedClass, $id); |
|
349 | |||
350 | 209 | $value = $this->conn->fetchColumn( |
|
351 | 209 | $sql, |
|
352 | 209 | array_values($flatId), |
|
353 | 209 | 0, |
|
354 | 209 | $this->extractIdentifierTypes($id, $versionedClass) |
|
355 | ); |
||
356 | |||
357 | 209 | return Type::getType($fieldMapping['type'])->convertToPHPValue($value, $this->platform); |
|
358 | } |
||
359 | |||
360 | 209 | private function extractIdentifierTypes(array $id, ClassMetadata $versionedClass) : array |
|
361 | { |
||
362 | 209 | $types = []; |
|
363 | |||
364 | 209 | foreach ($id as $field => $value) { |
|
365 | 209 | $types = array_merge($types, $this->getTypes($field, $value, $versionedClass)); |
|
366 | } |
||
367 | |||
368 | 209 | return $types; |
|
369 | } |
||
370 | |||
371 | /** |
||
372 | * {@inheritdoc} |
||
373 | */ |
||
374 | 97 | public function update($entity) |
|
375 | { |
||
376 | 97 | $tableName = $this->class->getTableName(); |
|
377 | 97 | $updateData = $this->prepareUpdateData($entity); |
|
378 | |||
379 | 97 | if ( ! isset($updateData[$tableName]) || ! ($data = $updateData[$tableName])) { |
|
380 | 8 | return; |
|
381 | } |
||
382 | |||
383 | 89 | $isVersioned = $this->class->isVersioned; |
|
384 | 89 | $quotedTableName = $this->quoteStrategy->getTableName($this->class, $this->platform); |
|
385 | |||
386 | 89 | $this->updateTable($entity, $quotedTableName, $data, $isVersioned); |
|
387 | |||
388 | 87 | if ($isVersioned) { |
|
389 | 12 | $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity); |
|
390 | |||
391 | 12 | $this->assignDefaultVersionValue($entity, $id); |
|
392 | } |
||
393 | 87 | } |
|
394 | |||
395 | /** |
||
396 | * Performs an UPDATE statement for an entity on a specific table. |
||
397 | * The UPDATE can optionally be versioned, which requires the entity to have a version field. |
||
398 | * |
||
399 | * @param object $entity The entity object being updated. |
||
400 | * @param string $quotedTableName The quoted name of the table to apply the UPDATE on. |
||
401 | * @param array $updateData The map of columns to update (column => value). |
||
402 | * @param boolean $versioned Whether the UPDATE should be versioned. |
||
403 | * |
||
404 | * @return void |
||
405 | * |
||
406 | * @throws \Doctrine\ORM\ORMException |
||
407 | * @throws \Doctrine\ORM\OptimisticLockException |
||
408 | */ |
||
409 | 120 | protected final function updateTable($entity, $quotedTableName, array $updateData, $versioned = false) |
|
410 | { |
||
411 | 120 | $set = []; |
|
412 | 120 | $types = []; |
|
413 | 120 | $params = []; |
|
414 | |||
415 | 120 | foreach ($updateData as $columnName => $value) { |
|
416 | 120 | $placeholder = '?'; |
|
417 | 120 | $column = $columnName; |
|
418 | |||
419 | switch (true) { |
||
420 | 120 | case isset($this->class->fieldNames[$columnName]): |
|
421 | 60 | $fieldName = $this->class->fieldNames[$columnName]; |
|
422 | 60 | $column = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); |
|
423 | |||
424 | 60 | View Code Duplication | if (isset($this->class->fieldMappings[$fieldName]['requireSQLConversion'])) { |
425 | 3 | $type = Type::getType($this->columnTypes[$columnName]); |
|
426 | 3 | $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); |
|
427 | } |
||
428 | |||
429 | 60 | break; |
|
430 | |||
431 | 62 | case isset($this->quotedColumns[$columnName]): |
|
432 | 62 | $column = $this->quotedColumns[$columnName]; |
|
433 | |||
434 | 62 | break; |
|
435 | } |
||
436 | |||
437 | 120 | $params[] = $value; |
|
438 | 120 | $set[] = $column . ' = ' . $placeholder; |
|
439 | 120 | $types[] = $this->columnTypes[$columnName]; |
|
440 | } |
||
441 | |||
442 | 120 | $where = []; |
|
443 | 120 | $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); |
|
444 | |||
445 | 120 | foreach ($this->class->identifier as $idField) { |
|
446 | 120 | if ( ! isset($this->class->associationMappings[$idField])) { |
|
447 | 117 | $params[] = $identifier[$idField]; |
|
448 | 117 | $types[] = $this->class->fieldMappings[$idField]['type']; |
|
449 | 117 | $where[] = $this->quoteStrategy->getColumnName($idField, $this->class, $this->platform); |
|
450 | |||
451 | 117 | continue; |
|
452 | } |
||
453 | |||
454 | 4 | $params[] = $identifier[$idField]; |
|
455 | 4 | $where[] = $this->class->associationMappings[$idField]['joinColumns'][0]['name']; |
|
456 | 4 | $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']); |
|
457 | |||
458 | switch (true) { |
||
459 | 4 | View Code Duplication | case (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])): |
460 | 3 | $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; |
|
461 | 3 | break; |
|
462 | |||
463 | 1 | View Code Duplication | case (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])): |
464 | 1 | $types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type']; |
|
465 | 1 | break; |
|
466 | |||
467 | default: |
||
468 | 4 | throw ORMException::unrecognizedField($targetMapping->identifier[0]); |
|
469 | } |
||
470 | |||
471 | } |
||
472 | |||
473 | 120 | if ($versioned) { |
|
474 | 18 | $versionField = $this->class->versionField; |
|
475 | 18 | $versionFieldType = $this->class->fieldMappings[$versionField]['type']; |
|
476 | 18 | $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->class, $this->platform); |
|
477 | |||
478 | 18 | $where[] = $versionColumn; |
|
479 | 18 | $types[] = $this->class->fieldMappings[$versionField]['type']; |
|
480 | 18 | $params[] = $this->class->reflFields[$versionField]->getValue($entity); |
|
481 | |||
482 | switch ($versionFieldType) { |
||
483 | 18 | case Type::SMALLINT: |
|
484 | 18 | case Type::INTEGER: |
|
485 | 1 | case Type::BIGINT: |
|
486 | 17 | $set[] = $versionColumn . ' = ' . $versionColumn . ' + 1'; |
|
487 | 17 | break; |
|
488 | |||
489 | 1 | case Type::DATETIME: |
|
490 | 1 | $set[] = $versionColumn . ' = CURRENT_TIMESTAMP'; |
|
491 | 1 | break; |
|
492 | } |
||
493 | } |
||
494 | |||
495 | 120 | $sql = 'UPDATE ' . $quotedTableName |
|
496 | 120 | . ' SET ' . implode(', ', $set) |
|
497 | 120 | . ' WHERE ' . implode(' = ? AND ', $where) . ' = ?'; |
|
498 | |||
499 | 120 | $result = $this->conn->executeUpdate($sql, $params, $types); |
|
500 | |||
501 | 120 | if ($versioned && ! $result) { |
|
502 | 4 | throw OptimisticLockException::lockFailed($entity); |
|
503 | } |
||
504 | 117 | } |
|
505 | |||
506 | /** |
||
507 | * @todo Add check for platform if it supports foreign keys/cascading. |
||
508 | * |
||
509 | * @param array $identifier |
||
510 | * |
||
511 | * @return void |
||
512 | */ |
||
513 | 65 | protected function deleteJoinTableRecords($identifier) |
|
514 | { |
||
515 | 65 | foreach ($this->class->associationMappings as $mapping) { |
|
516 | 49 | if ($mapping['type'] !== ClassMetadata::MANY_TO_MANY) { |
|
517 | 48 | continue; |
|
518 | } |
||
519 | |||
520 | // @Todo this only covers scenarios with no inheritance or of the same level. Is there something |
||
521 | // like self-referential relationship between different levels of an inheritance hierarchy? I hope not! |
||
522 | 24 | $selfReferential = ($mapping['targetEntity'] == $mapping['sourceEntity']); |
|
523 | 24 | $class = $this->class; |
|
524 | 24 | $association = $mapping; |
|
525 | 24 | $otherColumns = []; |
|
526 | 24 | $otherKeys = []; |
|
527 | 24 | $keys = []; |
|
528 | |||
529 | 24 | View Code Duplication | if ( ! $mapping['isOwningSide']) { |
530 | 6 | $class = $this->em->getClassMetadata($mapping['targetEntity']); |
|
531 | 6 | $association = $class->associationMappings[$mapping['mappedBy']]; |
|
532 | } |
||
533 | |||
534 | 24 | $joinColumns = $mapping['isOwningSide'] |
|
535 | 20 | ? $association['joinTable']['joinColumns'] |
|
536 | 24 | : $association['joinTable']['inverseJoinColumns']; |
|
537 | |||
538 | |||
539 | 24 | if ($selfReferential) { |
|
540 | 1 | $otherColumns = (! $mapping['isOwningSide']) |
|
541 | ? $association['joinTable']['joinColumns'] |
||
542 | 1 | : $association['joinTable']['inverseJoinColumns']; |
|
543 | } |
||
544 | |||
545 | 24 | foreach ($joinColumns as $joinColumn) { |
|
546 | 24 | $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
547 | } |
||
548 | |||
549 | 24 | foreach ($otherColumns as $joinColumn) { |
|
550 | 1 | $otherKeys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
551 | } |
||
552 | |||
553 | 24 | if (isset($mapping['isOnDeleteCascade'])) { |
|
554 | 5 | continue; |
|
555 | } |
||
556 | |||
557 | 20 | $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); |
|
558 | |||
559 | 20 | $this->conn->delete($joinTableName, array_combine($keys, $identifier)); |
|
560 | |||
561 | 20 | if ($selfReferential) { |
|
562 | 20 | $this->conn->delete($joinTableName, array_combine($otherKeys, $identifier)); |
|
563 | } |
||
564 | } |
||
565 | 65 | } |
|
566 | |||
567 | /** |
||
568 | * {@inheritdoc} |
||
569 | */ |
||
570 | 61 | public function delete($entity) |
|
571 | { |
||
572 | 61 | $self = $this; |
|
573 | 61 | $class = $this->class; |
|
574 | 61 | $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); |
|
575 | 61 | $tableName = $this->quoteStrategy->getTableName($class, $this->platform); |
|
576 | 61 | $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); |
|
577 | 61 | $id = array_combine($idColumns, $identifier); |
|
578 | 61 | $types = array_map(function ($identifier) use ($class, $self) { |
|
579 | 61 | if (isset($class->fieldMappings[$identifier])) { |
|
580 | 59 | return $class->fieldMappings[$identifier]['type']; |
|
581 | } |
||
582 | |||
583 | 5 | $targetMapping = $self->em->getClassMetadata($class->associationMappings[$identifier]['targetEntity']); |
|
584 | |||
585 | 5 | if (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])) { |
|
586 | 4 | return $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; |
|
587 | } |
||
588 | |||
589 | 1 | if (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])) { |
|
590 | 1 | return $targetMapping->associationMappings[$targetMapping->identifier[0]]['type']; |
|
591 | } |
||
592 | |||
593 | throw ORMException::unrecognizedField($targetMapping->identifier[0]); |
||
594 | 61 | }, $class->identifier); |
|
595 | |||
596 | 61 | $this->deleteJoinTableRecords($identifier); |
|
597 | |||
598 | 61 | return (bool) $this->conn->delete($tableName, $id, $types); |
|
599 | } |
||
600 | |||
601 | /** |
||
602 | * Prepares the changeset of an entity for database insertion (UPDATE). |
||
603 | * |
||
604 | * The changeset is obtained from the currently running UnitOfWork. |
||
605 | * |
||
606 | * During this preparation the array that is passed as the second parameter is filled with |
||
607 | * <columnName> => <value> pairs, grouped by table name. |
||
608 | * |
||
609 | * Example: |
||
610 | * <code> |
||
611 | * array( |
||
612 | * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), |
||
613 | * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), |
||
614 | * ... |
||
615 | * ) |
||
616 | * </code> |
||
617 | * |
||
618 | * @param object $entity The entity for which to prepare the data. |
||
619 | * |
||
620 | * @return array The prepared data. |
||
621 | */ |
||
622 | 1038 | protected function prepareUpdateData($entity) |
|
623 | { |
||
624 | 1038 | $versionField = null; |
|
625 | 1038 | $result = []; |
|
626 | 1038 | $uow = $this->em->getUnitOfWork(); |
|
627 | |||
628 | 1038 | if (($versioned = $this->class->isVersioned) != false) { |
|
629 | 213 | $versionField = $this->class->versionField; |
|
630 | } |
||
631 | |||
632 | 1038 | foreach ($uow->getEntityChangeSet($entity) as $field => $change) { |
|
633 | 1002 | if (isset($versionField) && $versionField == $field) { |
|
634 | continue; |
||
635 | } |
||
636 | |||
637 | 1002 | if (isset($this->class->embeddedClasses[$field])) { |
|
638 | 9 | continue; |
|
639 | } |
||
640 | |||
641 | 1002 | $newVal = $change[1]; |
|
642 | |||
643 | 1002 | if ( ! isset($this->class->associationMappings[$field])) { |
|
644 | 964 | $fieldMapping = $this->class->fieldMappings[$field]; |
|
645 | 964 | $columnName = $fieldMapping['columnName']; |
|
646 | |||
647 | 964 | $this->columnTypes[$columnName] = $fieldMapping['type']; |
|
648 | |||
649 | 964 | $result[$this->getOwningTable($field)][$columnName] = $newVal; |
|
650 | |||
651 | 964 | continue; |
|
652 | } |
||
653 | |||
654 | 853 | $assoc = $this->class->associationMappings[$field]; |
|
655 | |||
656 | // Only owning side of x-1 associations can have a FK column. |
||
657 | 853 | if ( ! $assoc['isOwningSide'] || ! ($assoc['type'] & ClassMetadata::TO_ONE)) { |
|
658 | 8 | continue; |
|
659 | } |
||
660 | |||
661 | 853 | if ($newVal !== null) { |
|
662 | 631 | $oid = spl_object_hash($newVal); |
|
663 | |||
664 | 631 | if (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { |
|
665 | // The associated entity $newVal is not yet persisted, so we must |
||
666 | // set $newVal = null, in order to insert a null value and schedule an |
||
667 | // extra update on the UnitOfWork. |
||
668 | 43 | $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]); |
|
669 | |||
670 | 43 | $newVal = null; |
|
671 | } |
||
672 | } |
||
673 | |||
674 | 853 | $newValId = null; |
|
675 | |||
676 | 853 | if ($newVal !== null) { |
|
677 | 631 | $newValId = $uow->getEntityIdentifier($newVal); |
|
678 | } |
||
679 | |||
680 | 853 | $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); |
|
681 | 853 | $owningTable = $this->getOwningTable($field); |
|
682 | |||
683 | 853 | foreach ($assoc['joinColumns'] as $joinColumn) { |
|
684 | 853 | $sourceColumn = $joinColumn['name']; |
|
685 | 853 | $targetColumn = $joinColumn['referencedColumnName']; |
|
686 | 853 | $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
687 | |||
688 | 853 | $this->quotedColumns[$sourceColumn] = $quotedColumn; |
|
689 | 853 | $this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em); |
|
690 | 853 | $result[$owningTable][$sourceColumn] = $newValId |
|
691 | 631 | ? $newValId[$targetClass->getFieldForColumn($targetColumn)] |
|
692 | 853 | : null; |
|
693 | } |
||
694 | } |
||
695 | |||
696 | 1038 | return $result; |
|
697 | } |
||
698 | |||
699 | /** |
||
700 | * Prepares the data changeset of a managed entity for database insertion (initial INSERT). |
||
701 | * The changeset of the entity is obtained from the currently running UnitOfWork. |
||
702 | * |
||
703 | * The default insert data preparation is the same as for updates. |
||
704 | * |
||
705 | * @param object $entity The entity for which to prepare the data. |
||
706 | * |
||
707 | * @return array The prepared data for the tables to update. |
||
708 | * |
||
709 | * @see prepareUpdateData |
||
710 | */ |
||
711 | 1033 | protected function prepareInsertData($entity) |
|
712 | { |
||
713 | 1033 | return $this->prepareUpdateData($entity); |
|
714 | } |
||
715 | |||
716 | /** |
||
717 | * {@inheritdoc} |
||
718 | */ |
||
719 | 930 | public function getOwningTable($fieldName) |
|
720 | { |
||
721 | 930 | return $this->class->getTableName(); |
|
722 | } |
||
723 | |||
724 | /** |
||
725 | * {@inheritdoc} |
||
726 | */ |
||
727 | 509 | public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, array $orderBy = null) |
|
728 | { |
||
729 | 509 | $this->switchPersisterContext(null, $limit); |
|
730 | |||
731 | 509 | $sql = $this->getSelectSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); |
|
732 | 508 | list($params, $types) = $this->expandParameters($criteria); |
|
733 | 508 | $stmt = $this->conn->executeQuery($sql, $params, $types); |
|
734 | |||
735 | 508 | View Code Duplication | if ($entity !== null) { |
736 | 72 | $hints[Query::HINT_REFRESH] = true; |
|
737 | 72 | $hints[Query::HINT_REFRESH_ENTITY] = $entity; |
|
738 | } |
||
739 | |||
740 | 508 | $hydrator = $this->em->newHydrator($this->currentPersisterContext->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); |
|
741 | 508 | $entities = $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, $hints); |
|
742 | |||
743 | 508 | return $entities ? $entities[0] : null; |
|
744 | } |
||
745 | |||
746 | /** |
||
747 | * {@inheritdoc} |
||
748 | */ |
||
749 | 433 | public function loadById(array $identifier, $entity = null) |
|
750 | { |
||
751 | 433 | return $this->load($identifier, $entity); |
|
752 | } |
||
753 | |||
754 | /** |
||
755 | * {@inheritdoc} |
||
756 | */ |
||
757 | 96 | public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = []) |
|
758 | { |
||
759 | 96 | if (($foundEntity = $this->em->getUnitOfWork()->tryGetById($identifier, $assoc['targetEntity'])) != false) { |
|
760 | return $foundEntity; |
||
761 | } |
||
762 | |||
763 | 96 | $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); |
|
764 | |||
765 | 96 | if ($assoc['isOwningSide']) { |
|
766 | 32 | $isInverseSingleValued = $assoc['inversedBy'] && ! $targetClass->isCollectionValuedAssociation($assoc['inversedBy']); |
|
767 | |||
768 | // Mark inverse side as fetched in the hints, otherwise the UoW would |
||
769 | // try to load it in a separate query (remember: to-one inverse sides can not be lazy). |
||
770 | 32 | $hints = []; |
|
771 | |||
772 | 32 | if ($isInverseSingleValued) { |
|
773 | $hints['fetched']["r"][$assoc['inversedBy']] = true; |
||
774 | } |
||
775 | |||
776 | /* cascade read-only status |
||
0 ignored issues
–
show
|
|||
777 | if ($this->em->getUnitOfWork()->isReadOnly($sourceEntity)) { |
||
778 | $hints[Query::HINT_READ_ONLY] = true; |
||
779 | } |
||
780 | */ |
||
781 | |||
782 | 32 | $targetEntity = $this->load($identifier, null, $assoc, $hints); |
|
783 | |||
784 | // Complete bidirectional association, if necessary |
||
785 | 32 | if ($targetEntity !== null && $isInverseSingleValued) { |
|
786 | $targetClass->reflFields[$assoc['inversedBy']]->setValue($targetEntity, $sourceEntity); |
||
0 ignored issues
–
show
Accessing
reflFields on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
Loading history...
|
|||
787 | } |
||
788 | |||
789 | 32 | return $targetEntity; |
|
790 | } |
||
791 | |||
792 | 64 | $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); |
|
793 | 64 | $owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']); |
|
794 | |||
795 | // TRICKY: since the association is specular source and target are flipped |
||
796 | 64 | foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { |
|
797 | 64 | if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) { |
|
798 | throw MappingException::joinColumnMustPointToMappedField( |
||
799 | $sourceClass->name, $sourceKeyColumn |
||
800 | ); |
||
801 | } |
||
802 | |||
803 | // unset the old value and set the new sql aliased value here. By definition |
||
804 | // unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method. |
||
805 | 64 | $identifier[$targetClass->getFieldForColumn($targetKeyColumn)] = |
|
806 | 64 | $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity); |
|
807 | |||
808 | 64 | unset($identifier[$targetKeyColumn]); |
|
809 | } |
||
810 | |||
811 | 64 | $targetEntity = $this->load($identifier, null, $assoc); |
|
812 | |||
813 | 64 | if ($targetEntity !== null) { |
|
814 | 16 | $targetClass->setFieldValue($targetEntity, $assoc['mappedBy'], $sourceEntity); |
|
815 | } |
||
816 | |||
817 | 64 | return $targetEntity; |
|
818 | } |
||
819 | |||
820 | /** |
||
821 | * {@inheritdoc} |
||
822 | */ |
||
823 | 17 | public function refresh(array $id, $entity, $lockMode = null) |
|
824 | { |
||
825 | 17 | $sql = $this->getSelectSQL($id, null, $lockMode); |
|
826 | 17 | list($params, $types) = $this->expandParameters($id); |
|
827 | 17 | $stmt = $this->conn->executeQuery($sql, $params, $types); |
|
828 | |||
829 | 17 | $hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT); |
|
830 | 17 | $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [Query::HINT_REFRESH => true]); |
|
831 | 17 | } |
|
832 | |||
833 | /** |
||
834 | * {@inheritDoc} |
||
835 | */ |
||
836 | 46 | public function count($criteria = []) |
|
837 | { |
||
838 | 46 | $sql = $this->getCountSQL($criteria); |
|
839 | |||
840 | 46 | list($params, $types) = ($criteria instanceof Criteria) |
|
841 | 43 | ? $this->expandCriteriaParameters($criteria) |
|
842 | 46 | : $this->expandParameters($criteria); |
|
843 | |||
844 | 46 | return (int) $this->conn->executeQuery($sql, $params, $types)->fetchColumn(); |
|
845 | } |
||
846 | |||
847 | /** |
||
848 | * {@inheritdoc} |
||
849 | */ |
||
850 | 9 | public function loadCriteria(Criteria $criteria) |
|
851 | { |
||
852 | 9 | $orderBy = $criteria->getOrderings(); |
|
853 | 9 | $limit = $criteria->getMaxResults(); |
|
854 | 9 | $offset = $criteria->getFirstResult(); |
|
855 | 9 | $query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); |
|
856 | |||
857 | 7 | list($params, $types) = $this->expandCriteriaParameters($criteria); |
|
858 | |||
859 | 7 | $stmt = $this->conn->executeQuery($query, $params, $types); |
|
860 | 7 | $hydrator = $this->em->newHydrator(($this->currentPersisterContext->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); |
|
861 | |||
862 | 7 | return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true] |
|
863 | ); |
||
864 | } |
||
865 | |||
866 | /** |
||
867 | * {@inheritdoc} |
||
868 | */ |
||
869 | 56 | public function expandCriteriaParameters(Criteria $criteria) |
|
870 | { |
||
871 | 56 | $expression = $criteria->getWhereExpression(); |
|
872 | 56 | $sqlParams = []; |
|
873 | 56 | $sqlTypes = []; |
|
874 | |||
875 | 56 | if ($expression === null) { |
|
876 | 2 | return [$sqlParams, $sqlTypes]; |
|
877 | } |
||
878 | |||
879 | 55 | $valueVisitor = new SqlValueVisitor(); |
|
880 | |||
881 | 55 | $valueVisitor->dispatch($expression); |
|
882 | |||
883 | 55 | list($params, $types) = $valueVisitor->getParamsAndTypes(); |
|
884 | |||
885 | 55 | foreach ($params as $param) { |
|
886 | 51 | $sqlParams = array_merge($sqlParams, $this->getValues($param)); |
|
887 | } |
||
888 | |||
889 | 55 | foreach ($types as $type) { |
|
890 | 51 | list ($field, $value) = $type; |
|
891 | 51 | $sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class)); |
|
892 | } |
||
893 | |||
894 | 55 | return [$sqlParams, $sqlTypes]; |
|
895 | } |
||
896 | |||
897 | /** |
||
898 | * {@inheritdoc} |
||
899 | */ |
||
900 | 71 | public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null) |
|
901 | { |
||
902 | 71 | $this->switchPersisterContext($offset, $limit); |
|
903 | |||
904 | 71 | $sql = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); |
|
905 | 67 | list($params, $types) = $this->expandParameters($criteria); |
|
906 | 67 | $stmt = $this->conn->executeQuery($sql, $params, $types); |
|
907 | |||
908 | 67 | $hydrator = $this->em->newHydrator(($this->currentPersisterContext->selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); |
|
909 | |||
910 | 67 | return $hydrator->hydrateAll($stmt, $this->currentPersisterContext->rsm, [UnitOfWork::HINT_DEFEREAGERLOAD => true] |
|
911 | ); |
||
912 | } |
||
913 | |||
914 | /** |
||
915 | * {@inheritdoc} |
||
916 | */ |
||
917 | 8 | View Code Duplication | public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) |
918 | { |
||
919 | 8 | $this->switchPersisterContext($offset, $limit); |
|
920 | |||
921 | 8 | $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); |
|
922 | |||
923 | 8 | return $this->loadArrayFromStatement($assoc, $stmt); |
|
924 | } |
||
925 | |||
926 | /** |
||
927 | * Loads an array of entities from a given DBAL statement. |
||
928 | * |
||
929 | * @param array $assoc |
||
930 | * @param \Doctrine\DBAL\Statement $stmt |
||
931 | * |
||
932 | * @return array |
||
933 | */ |
||
934 | 13 | View Code Duplication | private function loadArrayFromStatement($assoc, $stmt) |
935 | { |
||
936 | 13 | $rsm = $this->currentPersisterContext->rsm; |
|
937 | 13 | $hints = [UnitOfWork::HINT_DEFEREAGERLOAD => true]; |
|
938 | |||
939 | 13 | if (isset($assoc['indexBy'])) { |
|
940 | 7 | $rsm = clone ($this->currentPersisterContext->rsm); // this is necessary because the "default rsm" should be changed. |
|
941 | 7 | $rsm->addIndexBy('r', $assoc['indexBy']); |
|
942 | } |
||
943 | |||
944 | 13 | return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); |
|
945 | } |
||
946 | |||
947 | /** |
||
948 | * Hydrates a collection from a given DBAL statement. |
||
949 | * |
||
950 | * @param array $assoc |
||
951 | * @param \Doctrine\DBAL\Statement $stmt |
||
952 | * @param PersistentCollection $coll |
||
953 | * |
||
954 | * @return array |
||
955 | */ |
||
956 | 144 | View Code Duplication | private function loadCollectionFromStatement($assoc, $stmt, $coll) |
957 | { |
||
958 | 144 | $rsm = $this->currentPersisterContext->rsm; |
|
959 | $hints = [ |
||
960 | 144 | UnitOfWork::HINT_DEFEREAGERLOAD => true, |
|
961 | 144 | 'collection' => $coll |
|
962 | ]; |
||
963 | |||
964 | 144 | if (isset($assoc['indexBy'])) { |
|
965 | 10 | $rsm = clone ($this->currentPersisterContext->rsm); // this is necessary because the "default rsm" should be changed. |
|
966 | 10 | $rsm->addIndexBy('r', $assoc['indexBy']); |
|
967 | } |
||
968 | |||
969 | 144 | return $this->em->newHydrator(Query::HYDRATE_OBJECT)->hydrateAll($stmt, $rsm, $hints); |
|
970 | } |
||
971 | |||
972 | /** |
||
973 | * {@inheritdoc} |
||
974 | */ |
||
975 | 82 | public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) |
|
976 | { |
||
977 | 82 | $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); |
|
978 | |||
979 | 82 | return $this->loadCollectionFromStatement($assoc, $stmt, $coll); |
|
980 | } |
||
981 | |||
982 | /** |
||
983 | * @param array $assoc |
||
984 | * @param object $sourceEntity |
||
985 | * @param int|null $offset |
||
986 | * @param int|null $limit |
||
987 | * |
||
988 | * @return \Doctrine\DBAL\Driver\Statement |
||
989 | * |
||
990 | * @throws \Doctrine\ORM\Mapping\MappingException |
||
991 | */ |
||
992 | 89 | private function getManyToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) |
|
993 | { |
||
994 | 89 | $this->switchPersisterContext($offset, $limit); |
|
995 | |||
996 | 89 | $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); |
|
997 | 89 | $class = $sourceClass; |
|
998 | 89 | $association = $assoc; |
|
999 | 89 | $criteria = []; |
|
1000 | 89 | $parameters = []; |
|
1001 | |||
1002 | 89 | View Code Duplication | if ( ! $assoc['isOwningSide']) { |
1003 | 12 | $class = $this->em->getClassMetadata($assoc['targetEntity']); |
|
1004 | 12 | $association = $class->associationMappings[$assoc['mappedBy']]; |
|
1005 | } |
||
1006 | |||
1007 | 89 | $joinColumns = $assoc['isOwningSide'] |
|
1008 | 82 | ? $association['joinTable']['joinColumns'] |
|
1009 | 89 | : $association['joinTable']['inverseJoinColumns']; |
|
1010 | |||
1011 | 89 | $quotedJoinTable = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); |
|
1012 | |||
1013 | 89 | foreach ($joinColumns as $joinColumn) { |
|
1014 | 89 | $sourceKeyColumn = $joinColumn['referencedColumnName']; |
|
1015 | 89 | $quotedKeyColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
1016 | |||
1017 | switch (true) { |
||
1018 | 89 | case $sourceClass->containsForeignIdentifier: |
|
1019 | 4 | $field = $sourceClass->getFieldForColumn($sourceKeyColumn); |
|
1020 | 4 | $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); |
|
1021 | |||
1022 | 4 | View Code Duplication | if (isset($sourceClass->associationMappings[$field])) { |
1023 | 4 | $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); |
|
1024 | 4 | $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; |
|
1025 | } |
||
1026 | |||
1027 | 4 | break; |
|
1028 | |||
1029 | 87 | case isset($sourceClass->fieldNames[$sourceKeyColumn]): |
|
1030 | 87 | $field = $sourceClass->fieldNames[$sourceKeyColumn]; |
|
1031 | 87 | $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); |
|
1032 | |||
1033 | 87 | break; |
|
1034 | |||
1035 | default: |
||
1036 | throw MappingException::joinColumnMustPointToMappedField( |
||
1037 | $sourceClass->name, $sourceKeyColumn |
||
0 ignored issues
–
show
Accessing
name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
Loading history...
|
|||
1038 | ); |
||
1039 | } |
||
1040 | |||
1041 | 89 | $criteria[$quotedJoinTable . '.' . $quotedKeyColumn] = $value; |
|
1042 | 89 | $parameters[] = [ |
|
1043 | 89 | 'value' => $value, |
|
1044 | 89 | 'field' => $field, |
|
1045 | 89 | 'class' => $sourceClass, |
|
1046 | ]; |
||
1047 | } |
||
1048 | |||
1049 | 89 | $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); |
|
1050 | 89 | list($params, $types) = $this->expandToManyParameters($parameters); |
|
1051 | |||
1052 | 89 | return $this->conn->executeQuery($sql, $params, $types); |
|
1053 | } |
||
1054 | |||
1055 | /** |
||
1056 | * {@inheritdoc} |
||
1057 | */ |
||
1058 | 562 | public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, array $orderBy = null) |
|
1059 | { |
||
1060 | 562 | $this->switchPersisterContext($offset, $limit); |
|
1061 | |||
1062 | 562 | $lockSql = ''; |
|
1063 | 562 | $joinSql = ''; |
|
1064 | 562 | $orderBySql = ''; |
|
1065 | |||
1066 | 562 | View Code Duplication | if ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) { |
1067 | 90 | $joinSql = $this->getSelectManyToManyJoinSQL($assoc); |
|
1068 | } |
||
1069 | |||
1070 | 562 | if (isset($assoc['orderBy'])) { |
|
1071 | 5 | $orderBy = $assoc['orderBy']; |
|
1072 | } |
||
1073 | |||
1074 | 562 | if ($orderBy) { |
|
1075 | 11 | $orderBySql = $this->getOrderBySQL($orderBy, $this->getSQLTableAlias($this->class->name)); |
|
1076 | } |
||
1077 | |||
1078 | 560 | $conditionSql = ($criteria instanceof Criteria) |
|
1079 | 9 | ? $this->getSelectConditionCriteriaSQL($criteria) |
|
1080 | 558 | : $this->getSelectConditionSQL($criteria, $assoc); |
|
1081 | |||
1082 | View Code Duplication | switch ($lockMode) { |
|
1083 | 555 | case LockMode::PESSIMISTIC_READ: |
|
1084 | $lockSql = ' ' . $this->platform->getReadLockSQL(); |
||
1085 | break; |
||
1086 | |||
1087 | 555 | case LockMode::PESSIMISTIC_WRITE: |
|
1088 | $lockSql = ' ' . $this->platform->getWriteLockSQL(); |
||
1089 | break; |
||
1090 | } |
||
1091 | |||
1092 | 555 | $columnList = $this->getSelectColumnsSQL(); |
|
1093 | 555 | $tableAlias = $this->getSQLTableAlias($this->class->name); |
|
1094 | 555 | $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); |
|
1095 | 555 | $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); |
|
1096 | |||
1097 | 555 | if ('' !== $filterSql) { |
|
1098 | 12 | $conditionSql = $conditionSql |
|
1099 | 11 | ? $conditionSql . ' AND ' . $filterSql |
|
1100 | 12 | : $filterSql; |
|
1101 | } |
||
1102 | |||
1103 | 555 | $select = 'SELECT ' . $columnList; |
|
1104 | 555 | $from = ' FROM ' . $tableName . ' '. $tableAlias; |
|
1105 | 555 | $join = $this->currentPersisterContext->selectJoinSql . $joinSql; |
|
1106 | 555 | $where = ($conditionSql ? ' WHERE ' . $conditionSql : ''); |
|
1107 | 555 | $lock = $this->platform->appendLockHint($from, $lockMode); |
|
1108 | $query = $select |
||
1109 | 555 | . $lock |
|
1110 | 555 | . $join |
|
1111 | 555 | . $where |
|
1112 | 555 | . $orderBySql; |
|
1113 | |||
1114 | 555 | return $this->platform->modifyLimitQuery($query, $limit, $offset) . $lockSql; |
|
1115 | } |
||
1116 | |||
1117 | /** |
||
1118 | * {@inheritDoc} |
||
1119 | */ |
||
1120 | 41 | public function getCountSQL($criteria = []) |
|
1121 | { |
||
1122 | 41 | $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); |
|
1123 | 41 | $tableAlias = $this->getSQLTableAlias($this->class->name); |
|
1124 | |||
1125 | 41 | $conditionSql = ($criteria instanceof Criteria) |
|
1126 | 38 | ? $this->getSelectConditionCriteriaSQL($criteria) |
|
1127 | 41 | : $this->getSelectConditionSQL($criteria); |
|
1128 | |||
1129 | 41 | $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); |
|
1130 | |||
1131 | 41 | if ('' !== $filterSql) { |
|
1132 | 2 | $conditionSql = $conditionSql |
|
1133 | 2 | ? $conditionSql . ' AND ' . $filterSql |
|
1134 | 2 | : $filterSql; |
|
1135 | } |
||
1136 | |||
1137 | $sql = 'SELECT COUNT(*) ' |
||
1138 | 41 | . 'FROM ' . $tableName . ' ' . $tableAlias |
|
1139 | 41 | . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); |
|
1140 | |||
1141 | 41 | return $sql; |
|
1142 | } |
||
1143 | |||
1144 | /** |
||
1145 | * Gets the ORDER BY SQL snippet for ordered collections. |
||
1146 | * |
||
1147 | * @param array $orderBy |
||
1148 | * @param string $baseTableAlias |
||
1149 | * |
||
1150 | * @return string |
||
1151 | * |
||
1152 | * @throws \Doctrine\ORM\ORMException |
||
1153 | */ |
||
1154 | 12 | protected final function getOrderBySQL(array $orderBy, $baseTableAlias) |
|
1155 | { |
||
1156 | 12 | $orderByList = []; |
|
1157 | |||
1158 | 12 | foreach ($orderBy as $fieldName => $orientation) { |
|
1159 | |||
1160 | 12 | $orientation = strtoupper(trim($orientation)); |
|
1161 | |||
1162 | 12 | if ($orientation != 'ASC' && $orientation != 'DESC') { |
|
1163 | 1 | throw ORMException::invalidOrientation($this->class->name, $fieldName); |
|
1164 | } |
||
1165 | |||
1166 | 11 | if (isset($this->class->fieldMappings[$fieldName])) { |
|
1167 | 9 | $tableAlias = isset($this->class->fieldMappings[$fieldName]['inherited']) |
|
1168 | ? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]['inherited']) |
||
1169 | 9 | : $baseTableAlias; |
|
1170 | |||
1171 | 9 | $columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform); |
|
1172 | 9 | $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; |
|
1173 | |||
1174 | 9 | continue; |
|
1175 | } |
||
1176 | |||
1177 | 2 | if (isset($this->class->associationMappings[$fieldName])) { |
|
1178 | |||
1179 | 2 | if ( ! $this->class->associationMappings[$fieldName]['isOwningSide']) { |
|
1180 | 1 | throw ORMException::invalidFindByInverseAssociation($this->class->name, $fieldName); |
|
1181 | } |
||
1182 | |||
1183 | 1 | $tableAlias = isset($this->class->associationMappings[$fieldName]['inherited']) |
|
1184 | ? $this->getSQLTableAlias($this->class->associationMappings[$fieldName]['inherited']) |
||
1185 | 1 | : $baseTableAlias; |
|
1186 | |||
1187 | 1 | foreach ($this->class->associationMappings[$fieldName]['joinColumns'] as $joinColumn) { |
|
1188 | 1 | $columnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1189 | 1 | $orderByList[] = $tableAlias . '.' . $columnName . ' ' . $orientation; |
|
1190 | } |
||
1191 | |||
1192 | 1 | continue; |
|
1193 | } |
||
1194 | |||
1195 | throw ORMException::unrecognizedField($fieldName); |
||
1196 | } |
||
1197 | |||
1198 | 10 | return ' ORDER BY ' . implode(', ', $orderByList); |
|
1199 | } |
||
1200 | |||
1201 | /** |
||
1202 | * Gets the SQL fragment with the list of columns to select when querying for |
||
1203 | * an entity in this persister. |
||
1204 | * |
||
1205 | * Subclasses should override this method to alter or change the select column |
||
1206 | * list SQL fragment. Note that in the implementation of BasicEntityPersister |
||
1207 | * the resulting SQL fragment is generated only once and cached in {@link selectColumnListSql}. |
||
1208 | * Subclasses may or may not do the same. |
||
1209 | * |
||
1210 | * @return string The SQL fragment. |
||
1211 | */ |
||
1212 | 556 | protected function getSelectColumnsSQL() |
|
1213 | { |
||
1214 | 556 | if ($this->currentPersisterContext->selectColumnListSql !== null) { |
|
1215 | 146 | return $this->currentPersisterContext->selectColumnListSql; |
|
1216 | } |
||
1217 | |||
1218 | 556 | $columnList = []; |
|
1219 | 556 | $this->currentPersisterContext->rsm->addEntityResult($this->class->name, 'r'); // r for root |
|
1220 | |||
1221 | // Add regular columns to select list |
||
1222 | 556 | foreach ($this->class->fieldNames as $field) { |
|
1223 | 554 | $columnList[] = $this->getSelectColumnSQL($field, $this->class); |
|
1224 | } |
||
1225 | |||
1226 | 556 | $this->currentPersisterContext->selectJoinSql = ''; |
|
1227 | 556 | $eagerAliasCounter = 0; |
|
1228 | |||
1229 | 556 | foreach ($this->class->associationMappings as $assocField => $assoc) { |
|
1230 | 493 | $assocColumnSQL = $this->getSelectColumnAssociationSQL($assocField, $assoc, $this->class); |
|
1231 | |||
1232 | 493 | if ($assocColumnSQL) { |
|
1233 | 415 | $columnList[] = $assocColumnSQL; |
|
1234 | } |
||
1235 | |||
1236 | 493 | $isAssocToOneInverseSide = $assoc['type'] & ClassMetadata::TO_ONE && ! $assoc['isOwningSide']; |
|
1237 | 493 | $isAssocFromOneEager = $assoc['type'] !== ClassMetadata::MANY_TO_MANY && $assoc['fetch'] === ClassMetadata::FETCH_EAGER; |
|
1238 | |||
1239 | 493 | if ( ! ($isAssocFromOneEager || $isAssocToOneInverseSide)) { |
|
1240 | 471 | continue; |
|
1241 | } |
||
1242 | |||
1243 | 191 | if ((($assoc['type'] & ClassMetadata::TO_MANY) > 0) && $this->currentPersisterContext->handlesLimits) { |
|
1244 | 3 | continue; |
|
1245 | } |
||
1246 | |||
1247 | 188 | $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); |
|
1248 | |||
1249 | 188 | if ($eagerEntity->inheritanceType != ClassMetadata::INHERITANCE_TYPE_NONE) { |
|
1250 | 5 | continue; // now this is why you shouldn't use inheritance |
|
1251 | } |
||
1252 | |||
1253 | 183 | $assocAlias = 'e' . ($eagerAliasCounter++); |
|
1254 | 183 | $this->currentPersisterContext->rsm->addJoinedEntityResult($assoc['targetEntity'], $assocAlias, 'r', $assocField); |
|
1255 | |||
1256 | 183 | foreach ($eagerEntity->fieldNames as $field) { |
|
1257 | 181 | $columnList[] = $this->getSelectColumnSQL($field, $eagerEntity, $assocAlias); |
|
1258 | } |
||
1259 | |||
1260 | 183 | foreach ($eagerEntity->associationMappings as $eagerAssocField => $eagerAssoc) { |
|
1261 | 180 | $eagerAssocColumnSQL = $this->getSelectColumnAssociationSQL( |
|
1262 | 180 | $eagerAssocField, $eagerAssoc, $eagerEntity, $assocAlias |
|
1263 | ); |
||
1264 | |||
1265 | 180 | if ($eagerAssocColumnSQL) { |
|
1266 | 180 | $columnList[] = $eagerAssocColumnSQL; |
|
1267 | } |
||
1268 | } |
||
1269 | |||
1270 | 183 | $association = $assoc; |
|
1271 | 183 | $joinCondition = []; |
|
1272 | |||
1273 | 183 | if (isset($assoc['indexBy'])) { |
|
1274 | 1 | $this->currentPersisterContext->rsm->addIndexBy($assocAlias, $assoc['indexBy']); |
|
1275 | } |
||
1276 | |||
1277 | 183 | View Code Duplication | if ( ! $assoc['isOwningSide']) { |
1278 | 176 | $eagerEntity = $this->em->getClassMetadata($assoc['targetEntity']); |
|
1279 | 176 | $association = $eagerEntity->getAssociationMapping($assoc['mappedBy']); |
|
1280 | } |
||
1281 | |||
1282 | 183 | $joinTableAlias = $this->getSQLTableAlias($eagerEntity->name, $assocAlias); |
|
1283 | 183 | $joinTableName = $this->quoteStrategy->getTableName($eagerEntity, $this->platform); |
|
1284 | |||
1285 | 183 | if ($assoc['isOwningSide']) { |
|
1286 | 13 | $tableAlias = $this->getSQLTableAlias($association['targetEntity'], $assocAlias); |
|
1287 | 13 | $this->currentPersisterContext->selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($association['joinColumns']); |
|
1288 | |||
1289 | 13 | View Code Duplication | foreach ($association['joinColumns'] as $joinColumn) { |
1290 | 13 | $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1291 | 13 | $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1292 | 13 | $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity']) |
|
1293 | 13 | . '.' . $sourceCol . ' = ' . $tableAlias . '.' . $targetCol; |
|
1294 | } |
||
1295 | |||
1296 | // Add filter SQL |
||
1297 | 13 | if ($filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias)) { |
|
1298 | 13 | $joinCondition[] = $filterSql; |
|
1299 | } |
||
1300 | |||
1301 | } else { |
||
1302 | |||
1303 | 176 | $this->currentPersisterContext->selectJoinSql .= ' LEFT JOIN'; |
|
1304 | |||
1305 | 176 | View Code Duplication | foreach ($association['joinColumns'] as $joinColumn) { |
1306 | 176 | $sourceCol = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1307 | 176 | $targetCol = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1308 | |||
1309 | 176 | $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = ' |
|
1310 | 176 | . $this->getSQLTableAlias($association['targetEntity']) . '.' . $targetCol; |
|
1311 | } |
||
1312 | } |
||
1313 | |||
1314 | 183 | $this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON '; |
|
1315 | 183 | $this->currentPersisterContext->selectJoinSql .= implode(' AND ', $joinCondition); |
|
1316 | } |
||
1317 | |||
1318 | 556 | $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList); |
|
1319 | |||
1320 | 556 | return $this->currentPersisterContext->selectColumnListSql; |
|
1321 | } |
||
1322 | |||
1323 | /** |
||
1324 | * Gets the SQL join fragment used when selecting entities from an association. |
||
1325 | * |
||
1326 | * @param string $field |
||
1327 | * @param array $assoc |
||
1328 | * @param ClassMetadata $class |
||
1329 | * @param string $alias |
||
1330 | * |
||
1331 | * @return string |
||
1332 | */ |
||
1333 | 493 | protected function getSelectColumnAssociationSQL($field, $assoc, ClassMetadata $class, $alias = 'r') |
|
1334 | { |
||
1335 | 493 | if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) ) { |
|
1336 | 394 | return ''; |
|
1337 | } |
||
1338 | |||
1339 | 432 | $columnList = []; |
|
1340 | 432 | $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); |
|
1341 | 432 | $isIdentifier = isset($assoc['id']) && $assoc['id'] === true; |
|
1342 | 432 | $sqlTableAlias = $this->getSQLTableAlias($class->name, ($alias == 'r' ? '' : $alias)); |
|
1343 | |||
1344 | 432 | foreach ($assoc['joinColumns'] as $joinColumn) { |
|
1345 | 432 | $quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1346 | 432 | $resultColumnName = $this->getSQLColumnAlias($joinColumn['name']); |
|
1347 | 432 | $type = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em); |
|
1348 | |||
1349 | 432 | $this->currentPersisterContext->rsm->addMetaResult($alias, $resultColumnName, $joinColumn['name'], $isIdentifier, $type); |
|
1350 | |||
1351 | 432 | $columnList[] = sprintf('%s.%s AS %s', $sqlTableAlias, $quotedColumn, $resultColumnName); |
|
1352 | } |
||
1353 | |||
1354 | 432 | return implode(', ', $columnList); |
|
1355 | } |
||
1356 | |||
1357 | /** |
||
1358 | * Gets the SQL join fragment used when selecting entities from a |
||
1359 | * many-to-many association. |
||
1360 | * |
||
1361 | * @param array $manyToMany |
||
1362 | * |
||
1363 | * @return string |
||
1364 | */ |
||
1365 | 92 | protected function getSelectManyToManyJoinSQL(array $manyToMany) |
|
1366 | { |
||
1367 | 92 | $conditions = []; |
|
1368 | 92 | $association = $manyToMany; |
|
1369 | 92 | $sourceTableAlias = $this->getSQLTableAlias($this->class->name); |
|
1370 | |||
1371 | 92 | View Code Duplication | if ( ! $manyToMany['isOwningSide']) { |
1372 | 13 | $targetEntity = $this->em->getClassMetadata($manyToMany['targetEntity']); |
|
1373 | 13 | $association = $targetEntity->associationMappings[$manyToMany['mappedBy']]; |
|
1374 | } |
||
1375 | |||
1376 | 92 | $joinTableName = $this->quoteStrategy->getJoinTableName($association, $this->class, $this->platform); |
|
1377 | 92 | $joinColumns = ($manyToMany['isOwningSide']) |
|
1378 | 84 | ? $association['joinTable']['inverseJoinColumns'] |
|
1379 | 92 | : $association['joinTable']['joinColumns']; |
|
1380 | |||
1381 | 92 | foreach ($joinColumns as $joinColumn) { |
|
1382 | 92 | $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1383 | 92 | $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1384 | 92 | $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableName . '.' . $quotedSourceColumn; |
|
1385 | } |
||
1386 | |||
1387 | 92 | return ' INNER JOIN ' . $joinTableName . ' ON ' . implode(' AND ', $conditions); |
|
1388 | } |
||
1389 | |||
1390 | /** |
||
1391 | * {@inheritdoc} |
||
1392 | */ |
||
1393 | 1034 | public function getInsertSQL() |
|
1394 | { |
||
1395 | 1034 | if ($this->insertSql !== null) { |
|
1396 | 89 | return $this->insertSql; |
|
1397 | } |
||
1398 | |||
1399 | 1034 | $columns = $this->getInsertColumnList(); |
|
1400 | 1034 | $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); |
|
1401 | |||
1402 | 1034 | if (empty($columns)) { |
|
1403 | 112 | $identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform); |
|
1404 | 112 | $this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn); |
|
1405 | |||
1406 | 112 | return $this->insertSql; |
|
1407 | } |
||
1408 | |||
1409 | 1009 | $values = []; |
|
1410 | 1009 | $columns = array_unique($columns); |
|
1411 | |||
1412 | 1009 | foreach ($columns as $column) { |
|
1413 | 1009 | $placeholder = '?'; |
|
1414 | |||
1415 | 1009 | if (isset($this->class->fieldNames[$column]) |
|
1416 | 1009 | && isset($this->columnTypes[$this->class->fieldNames[$column]]) |
|
1417 | 1009 | && isset($this->class->fieldMappings[$this->class->fieldNames[$column]]['requireSQLConversion'])) { |
|
1418 | 6 | $type = Type::getType($this->columnTypes[$this->class->fieldNames[$column]]); |
|
1419 | 6 | $placeholder = $type->convertToDatabaseValueSQL('?', $this->platform); |
|
1420 | } |
||
1421 | |||
1422 | 1009 | $values[] = $placeholder; |
|
1423 | } |
||
1424 | |||
1425 | 1009 | $columns = implode(', ', $columns); |
|
1426 | 1009 | $values = implode(', ', $values); |
|
1427 | |||
1428 | 1009 | $this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values); |
|
1429 | |||
1430 | 1009 | return $this->insertSql; |
|
1431 | } |
||
1432 | |||
1433 | /** |
||
1434 | * Gets the list of columns to put in the INSERT SQL statement. |
||
1435 | * |
||
1436 | * Subclasses should override this method to alter or change the list of |
||
1437 | * columns placed in the INSERT statements used by the persister. |
||
1438 | * |
||
1439 | * @return array The list of columns. |
||
1440 | */ |
||
1441 | 959 | protected function getInsertColumnList() |
|
1442 | { |
||
1443 | 959 | $columns = []; |
|
1444 | |||
1445 | 959 | foreach ($this->class->reflFields as $name => $field) { |
|
1446 | 959 | if ($this->class->isVersioned && $this->class->versionField == $name) { |
|
1447 | 200 | continue; |
|
1448 | } |
||
1449 | |||
1450 | 959 | if (isset($this->class->embeddedClasses[$name])) { |
|
1451 | 8 | continue; |
|
1452 | } |
||
1453 | |||
1454 | 959 | if (isset($this->class->associationMappings[$name])) { |
|
1455 | 840 | $assoc = $this->class->associationMappings[$name]; |
|
1456 | |||
1457 | 840 | if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { |
|
1458 | 790 | View Code Duplication | foreach ($assoc['joinColumns'] as $joinColumn) { |
1459 | 790 | $columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1460 | } |
||
1461 | } |
||
1462 | |||
1463 | 840 | continue; |
|
1464 | } |
||
1465 | |||
1466 | 959 | if (! $this->class->isIdGeneratorIdentity() || $this->class->identifier[0] != $name) { |
|
1467 | 889 | $columns[] = $this->quoteStrategy->getColumnName($name, $this->class, $this->platform); |
|
1468 | 959 | $this->columnTypes[$name] = $this->class->fieldMappings[$name]['type']; |
|
1469 | } |
||
1470 | } |
||
1471 | |||
1472 | 959 | return $columns; |
|
1473 | } |
||
1474 | |||
1475 | /** |
||
1476 | * Gets the SQL snippet of a qualified column name for the given field name. |
||
1477 | * |
||
1478 | * @param string $field The field name. |
||
1479 | * @param ClassMetadata $class The class that declares this field. The table this class is |
||
1480 | * mapped to must own the column for the given field. |
||
1481 | * @param string $alias |
||
1482 | * |
||
1483 | * @return string |
||
1484 | */ |
||
1485 | 519 | View Code Duplication | protected function getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r') |
1486 | { |
||
1487 | 519 | $root = $alias == 'r' ? '' : $alias ; |
|
1488 | 519 | $tableAlias = $this->getSQLTableAlias($class->name, $root); |
|
1489 | 519 | $fieldMapping = $class->fieldMappings[$field]; |
|
1490 | 519 | $sql = sprintf('%s.%s', $tableAlias, $this->quoteStrategy->getColumnName($field, $class, $this->platform)); |
|
1491 | 519 | $columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']); |
|
1492 | |||
1493 | 519 | $this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field); |
|
1494 | |||
1495 | 519 | if (isset($fieldMapping['requireSQLConversion'])) { |
|
1496 | 3 | $type = Type::getType($fieldMapping['type']); |
|
1497 | 3 | $sql = $type->convertToPHPValueSQL($sql, $this->platform); |
|
1498 | } |
||
1499 | |||
1500 | 519 | return $sql . ' AS ' . $columnAlias; |
|
1501 | } |
||
1502 | |||
1503 | /** |
||
1504 | * Gets the SQL table alias for the given class name. |
||
1505 | * |
||
1506 | * @param string $className |
||
1507 | * @param string $assocName |
||
1508 | * |
||
1509 | * @return string The SQL table alias. |
||
1510 | * |
||
1511 | * @todo Reconsider. Binding table aliases to class names is not such a good idea. |
||
1512 | */ |
||
1513 | 625 | protected function getSQLTableAlias($className, $assocName = '') |
|
1514 | { |
||
1515 | 625 | if ($assocName) { |
|
1516 | 183 | $className .= '#' . $assocName; |
|
1517 | } |
||
1518 | |||
1519 | 625 | if (isset($this->currentPersisterContext->sqlTableAliases[$className])) { |
|
1520 | 621 | return $this->currentPersisterContext->sqlTableAliases[$className]; |
|
1521 | } |
||
1522 | |||
1523 | 625 | $tableAlias = 't' . $this->currentPersisterContext->sqlAliasCounter++; |
|
1524 | |||
1525 | 625 | $this->currentPersisterContext->sqlTableAliases[$className] = $tableAlias; |
|
1526 | |||
1527 | 625 | return $tableAlias; |
|
1528 | } |
||
1529 | |||
1530 | /** |
||
1531 | * {@inheritdoc} |
||
1532 | */ |
||
1533 | public function lock(array $criteria, $lockMode) |
||
1534 | { |
||
1535 | $lockSql = ''; |
||
1536 | $conditionSql = $this->getSelectConditionSQL($criteria); |
||
1537 | |||
1538 | View Code Duplication | switch ($lockMode) { |
|
1539 | case LockMode::PESSIMISTIC_READ: |
||
1540 | $lockSql = $this->platform->getReadLockSQL(); |
||
1541 | |||
1542 | break; |
||
1543 | case LockMode::PESSIMISTIC_WRITE: |
||
1544 | |||
1545 | $lockSql = $this->platform->getWriteLockSQL(); |
||
1546 | break; |
||
1547 | } |
||
1548 | |||
1549 | $lock = $this->getLockTablesSql($lockMode); |
||
1550 | $where = ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' '; |
||
1551 | $sql = 'SELECT 1 ' |
||
1552 | . $lock |
||
1553 | . $where |
||
1554 | . $lockSql; |
||
1555 | |||
1556 | list($params, $types) = $this->expandParameters($criteria); |
||
1557 | |||
1558 | $this->conn->executeQuery($sql, $params, $types); |
||
1559 | } |
||
1560 | |||
1561 | /** |
||
1562 | * Gets the FROM and optionally JOIN conditions to lock the entity managed by this persister. |
||
1563 | * |
||
1564 | * @param integer $lockMode One of the Doctrine\DBAL\LockMode::* constants. |
||
1565 | * |
||
1566 | * @return string |
||
1567 | */ |
||
1568 | 13 | protected function getLockTablesSql($lockMode) |
|
1569 | { |
||
1570 | 13 | return $this->platform->appendLockHint( |
|
1571 | 'FROM ' |
||
1572 | 13 | . $this->quoteStrategy->getTableName($this->class, $this->platform) . ' ' |
|
1573 | 13 | . $this->getSQLTableAlias($this->class->name), |
|
1574 | 13 | $lockMode |
|
1575 | ); |
||
1576 | } |
||
1577 | |||
1578 | /** |
||
1579 | * Gets the Select Where Condition from a Criteria object. |
||
1580 | * |
||
1581 | * @param \Doctrine\Common\Collections\Criteria $criteria |
||
1582 | * |
||
1583 | * @return string |
||
1584 | */ |
||
1585 | 58 | protected function getSelectConditionCriteriaSQL(Criteria $criteria) |
|
1586 | { |
||
1587 | 58 | $expression = $criteria->getWhereExpression(); |
|
1588 | |||
1589 | 58 | if ($expression === null) { |
|
1590 | 2 | return ''; |
|
1591 | } |
||
1592 | |||
1593 | 57 | $visitor = new SqlExpressionVisitor($this, $this->class); |
|
1594 | |||
1595 | 57 | return $visitor->dispatch($expression); |
|
1596 | } |
||
1597 | |||
1598 | /** |
||
1599 | * {@inheritdoc} |
||
1600 | */ |
||
1601 | 604 | public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null) |
|
1602 | { |
||
1603 | 604 | $selectedColumns = []; |
|
1604 | 604 | $columns = $this->getSelectConditionStatementColumnSQL($field, $assoc); |
|
1605 | |||
1606 | 600 | if (count($columns) > 1 && $comparison === Comparison::IN) { |
|
1607 | /* |
||
1608 | * @todo try to support multi-column IN expressions. |
||
1609 | * Example: (col1, col2) IN (('val1A', 'val2A'), ('val1B', 'val2B')) |
||
1610 | */ |
||
1611 | 1 | throw ORMException::cantUseInOperatorOnCompositeKeys(); |
|
1612 | } |
||
1613 | |||
1614 | 599 | foreach ($columns as $column) { |
|
1615 | 599 | $placeholder = '?'; |
|
1616 | |||
1617 | 599 | View Code Duplication | if (isset($this->class->fieldMappings[$field]['requireSQLConversion'])) { |
1618 | 1 | $type = Type::getType($this->class->fieldMappings[$field]['type']); |
|
1619 | 1 | $placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->platform); |
|
1620 | } |
||
1621 | |||
1622 | 599 | if (null !== $comparison) { |
|
1623 | // special case null value handling |
||
1624 | 61 | View Code Duplication | if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && null ===$value) { |
1625 | 6 | $selectedColumns[] = $column . ' IS NULL'; |
|
1626 | |||
1627 | 6 | continue; |
|
1628 | } |
||
1629 | |||
1630 | 55 | View Code Duplication | if ($comparison === Comparison::NEQ && null === $value) { |
1631 | 3 | $selectedColumns[] = $column . ' IS NOT NULL'; |
|
1632 | |||
1633 | 3 | continue; |
|
1634 | } |
||
1635 | |||
1636 | 52 | $selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder); |
|
1637 | |||
1638 | 52 | continue; |
|
1639 | } |
||
1640 | |||
1641 | 571 | if (is_array($value)) { |
|
1642 | 14 | $in = sprintf('%s IN (%s)', $column, $placeholder); |
|
1643 | |||
1644 | 14 | if (false !== array_search(null, $value, true)) { |
|
1645 | 4 | $selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column); |
|
1646 | |||
1647 | 4 | continue; |
|
1648 | } |
||
1649 | |||
1650 | 10 | $selectedColumns[] = $in; |
|
1651 | |||
1652 | 10 | continue; |
|
1653 | } |
||
1654 | |||
1655 | 560 | if (null === $value) { |
|
1656 | 9 | $selectedColumns[] = sprintf('%s IS NULL', $column); |
|
1657 | |||
1658 | 9 | continue; |
|
1659 | } |
||
1660 | |||
1661 | 552 | $selectedColumns[] = sprintf('%s = %s', $column, $placeholder); |
|
1662 | } |
||
1663 | |||
1664 | 599 | return implode(' AND ', $selectedColumns); |
|
1665 | } |
||
1666 | |||
1667 | /** |
||
1668 | * Builds the left-hand-side of a where condition statement. |
||
1669 | * |
||
1670 | * @param string $field |
||
1671 | * @param array|null $assoc |
||
1672 | * |
||
1673 | * @return string[] |
||
1674 | * |
||
1675 | * @throws \Doctrine\ORM\ORMException |
||
1676 | */ |
||
1677 | 604 | private function getSelectConditionStatementColumnSQL($field, $assoc = null) |
|
1678 | { |
||
1679 | 604 | if (isset($this->class->fieldMappings[$field])) { |
|
1680 | 508 | $className = (isset($this->class->fieldMappings[$field]['inherited'])) |
|
1681 | 54 | ? $this->class->fieldMappings[$field]['inherited'] |
|
1682 | 508 | : $this->class->name; |
|
1683 | |||
1684 | 508 | return [$this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->class, $this->platform)]; |
|
1685 | } |
||
1686 | |||
1687 | 295 | if (isset($this->class->associationMappings[$field])) { |
|
1688 | 148 | $association = $this->class->associationMappings[$field]; |
|
1689 | // Many-To-Many requires join table check for joinColumn |
||
1690 | 148 | $columns = []; |
|
1691 | 148 | $class = $this->class; |
|
1692 | |||
1693 | 148 | if ($association['type'] === ClassMetadata::MANY_TO_MANY) { |
|
1694 | 3 | if ( ! $association['isOwningSide']) { |
|
1695 | 2 | $association = $assoc; |
|
1696 | } |
||
1697 | |||
1698 | 3 | $joinTableName = $this->quoteStrategy->getJoinTableName($association, $class, $this->platform); |
|
1699 | 3 | $joinColumns = $assoc['isOwningSide'] |
|
1700 | 2 | ? $association['joinTable']['joinColumns'] |
|
1701 | 3 | : $association['joinTable']['inverseJoinColumns']; |
|
1702 | |||
1703 | |||
1704 | 3 | foreach ($joinColumns as $joinColumn) { |
|
1705 | 3 | $columns[] = $joinTableName . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); |
|
1706 | } |
||
1707 | |||
1708 | } else { |
||
1709 | 146 | if ( ! $association['isOwningSide']) { |
|
1710 | 1 | throw ORMException::invalidFindByInverseAssociation($this->class->name, $field); |
|
1711 | } |
||
1712 | |||
1713 | 145 | $className = (isset($association['inherited'])) |
|
1714 | 11 | ? $association['inherited'] |
|
1715 | 145 | : $this->class->name; |
|
1716 | |||
1717 | 145 | View Code Duplication | foreach ($association['joinColumns'] as $joinColumn) { |
1718 | 145 | $columns[] = $this->getSQLTableAlias($className) . '.' . $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform); |
|
1719 | } |
||
1720 | } |
||
1721 | 147 | return $columns; |
|
1722 | } |
||
1723 | |||
1724 | 163 | if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) { |
|
1725 | // very careless developers could potentially open up this normally hidden api for userland attacks, |
||
1726 | // therefore checking for spaces and function calls which are not allowed. |
||
1727 | |||
1728 | // found a join column condition, not really a "field" |
||
1729 | 160 | return [$field]; |
|
1730 | } |
||
1731 | |||
1732 | 3 | throw ORMException::unrecognizedField($field); |
|
1733 | } |
||
1734 | |||
1735 | /** |
||
1736 | * Gets the conditional SQL fragment used in the WHERE clause when selecting |
||
1737 | * entities in this persister. |
||
1738 | * |
||
1739 | * Subclasses are supposed to override this method if they intend to change |
||
1740 | * or alter the criteria by which entities are selected. |
||
1741 | * |
||
1742 | * @param array $criteria |
||
1743 | * @param array|null $assoc |
||
1744 | * |
||
1745 | * @return string |
||
1746 | */ |
||
1747 | 598 | protected function getSelectConditionSQL(array $criteria, $assoc = null) |
|
1748 | { |
||
1749 | 598 | $conditions = []; |
|
1750 | |||
1751 | 598 | foreach ($criteria as $field => $value) { |
|
1752 | 573 | $conditions[] = $this->getSelectConditionStatementSQL($field, $value, $assoc); |
|
1753 | } |
||
1754 | |||
1755 | 595 | return implode(' AND ', $conditions); |
|
1756 | } |
||
1757 | |||
1758 | /** |
||
1759 | * {@inheritdoc} |
||
1760 | */ |
||
1761 | 5 | View Code Duplication | public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) |
1762 | { |
||
1763 | 5 | $this->switchPersisterContext($offset, $limit); |
|
1764 | |||
1765 | 5 | $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); |
|
1766 | |||
1767 | 5 | return $this->loadArrayFromStatement($assoc, $stmt); |
|
1768 | } |
||
1769 | |||
1770 | /** |
||
1771 | * {@inheritdoc} |
||
1772 | */ |
||
1773 | 76 | public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) |
|
1774 | { |
||
1775 | 76 | $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); |
|
1776 | |||
1777 | 76 | return $this->loadCollectionFromStatement($assoc, $stmt, $coll); |
|
1778 | } |
||
1779 | |||
1780 | /** |
||
1781 | * Builds criteria and execute SQL statement to fetch the one to many entities from. |
||
1782 | * |
||
1783 | * @param array $assoc |
||
1784 | * @param object $sourceEntity |
||
1785 | * @param int|null $offset |
||
1786 | * @param int|null $limit |
||
1787 | * |
||
1788 | * @return \Doctrine\DBAL\Statement |
||
1789 | */ |
||
1790 | 81 | private function getOneToManyStatement(array $assoc, $sourceEntity, $offset = null, $limit = null) |
|
1791 | { |
||
1792 | 81 | $this->switchPersisterContext($offset, $limit); |
|
1793 | |||
1794 | 81 | $criteria = []; |
|
1795 | 81 | $parameters = []; |
|
1796 | 81 | $owningAssoc = $this->class->associationMappings[$assoc['mappedBy']]; |
|
1797 | 81 | $sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']); |
|
1798 | 81 | $tableAlias = $this->getSQLTableAlias($owningAssoc['inherited'] ?? $this->class->name); |
|
1799 | |||
1800 | 81 | foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) { |
|
1801 | 81 | if ($sourceClass->containsForeignIdentifier) { |
|
1802 | 3 | $field = $sourceClass->getFieldForColumn($sourceKeyColumn); |
|
1803 | 3 | $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); |
|
1804 | |||
1805 | 3 | View Code Duplication | if (isset($sourceClass->associationMappings[$field])) { |
1806 | 3 | $value = $this->em->getUnitOfWork()->getEntityIdentifier($value); |
|
1807 | 3 | $value = $value[$this->em->getClassMetadata($sourceClass->associationMappings[$field]['targetEntity'])->identifier[0]]; |
|
1808 | } |
||
1809 | |||
1810 | 3 | $criteria[$tableAlias . "." . $targetKeyColumn] = $value; |
|
1811 | 3 | $parameters[] = [ |
|
1812 | 3 | 'value' => $value, |
|
1813 | 3 | 'field' => $field, |
|
1814 | 3 | 'class' => $sourceClass, |
|
1815 | ]; |
||
1816 | |||
1817 | 3 | continue; |
|
1818 | } |
||
1819 | |||
1820 | 79 | $field = $sourceClass->fieldNames[$sourceKeyColumn]; |
|
1821 | 79 | $value = $sourceClass->reflFields[$field]->getValue($sourceEntity); |
|
1822 | |||
1823 | 79 | $criteria[$tableAlias . "." . $targetKeyColumn] = $value; |
|
1824 | 79 | $parameters[] = [ |
|
1825 | 79 | 'value' => $value, |
|
1826 | 79 | 'field' => $field, |
|
1827 | 79 | 'class' => $sourceClass, |
|
1828 | ]; |
||
1829 | |||
1830 | } |
||
1831 | |||
1832 | 81 | $sql = $this->getSelectSQL($criteria, $assoc, null, $limit, $offset); |
|
1833 | 81 | list($params, $types) = $this->expandToManyParameters($parameters); |
|
1834 | |||
1835 | 81 | return $this->conn->executeQuery($sql, $params, $types); |
|
1836 | } |
||
1837 | |||
1838 | /** |
||
1839 | * {@inheritdoc} |
||
1840 | */ |
||
1841 | 574 | public function expandParameters($criteria) |
|
1842 | { |
||
1843 | 574 | $params = []; |
|
1844 | 574 | $types = []; |
|
1845 | |||
1846 | 574 | foreach ($criteria as $field => $value) { |
|
1847 | 549 | if ($value === null) { |
|
1848 | 3 | continue; // skip null values. |
|
1849 | } |
||
1850 | |||
1851 | 547 | $types = array_merge($types, $this->getTypes($field, $value, $this->class)); |
|
1852 | 547 | $params = array_merge($params, $this->getValues($value)); |
|
1853 | } |
||
1854 | |||
1855 | 574 | return [$params, $types]; |
|
1856 | } |
||
1857 | |||
1858 | /** |
||
1859 | * Expands the parameters from the given criteria and use the correct binding types if found, |
||
1860 | * specialized for OneToMany or ManyToMany associations. |
||
1861 | * |
||
1862 | * @param mixed[][] $criteria an array of arrays containing following: |
||
1863 | * - field to which each criterion will be bound |
||
1864 | * - value to be bound |
||
1865 | * - class to which the field belongs to |
||
1866 | * |
||
1867 | * |
||
1868 | * @return array |
||
1869 | */ |
||
1870 | 156 | private function expandToManyParameters($criteria) |
|
1871 | { |
||
1872 | 156 | $params = []; |
|
1873 | 156 | $types = []; |
|
1874 | |||
1875 | 156 | foreach ($criteria as $criterion) { |
|
1876 | 156 | if ($criterion['value'] === null) { |
|
1877 | 6 | continue; // skip null values. |
|
1878 | } |
||
1879 | |||
1880 | 150 | $types = array_merge($types, $this->getTypes($criterion['field'], $criterion['value'], $criterion['class'])); |
|
1881 | 150 | $params = array_merge($params, $this->getValues($criterion['value'])); |
|
1882 | } |
||
1883 | |||
1884 | 156 | return [$params, $types]; |
|
1885 | } |
||
1886 | |||
1887 | /** |
||
1888 | * Infers field types to be used by parameter type casting. |
||
1889 | * |
||
1890 | * @param string $field |
||
1891 | * @param mixed $value |
||
1892 | * @param ClassMetadata $class |
||
1893 | * |
||
1894 | * @return array |
||
1895 | * |
||
1896 | * @throws \Doctrine\ORM\Query\QueryException |
||
1897 | */ |
||
1898 | 706 | private function getTypes($field, $value, ClassMetadata $class) |
|
1899 | { |
||
1900 | 706 | $types = []; |
|
1901 | |||
1902 | switch (true) { |
||
1903 | 706 | case (isset($class->fieldMappings[$field])): |
|
1904 | 646 | $types = array_merge($types, [$class->fieldMappings[$field]['type']]); |
|
1905 | 646 | break; |
|
1906 | |||
1907 | 147 | case (isset($class->associationMappings[$field])): |
|
1908 | 146 | $assoc = $class->associationMappings[$field]; |
|
1909 | 146 | $class = $this->em->getClassMetadata($assoc['targetEntity']); |
|
1910 | |||
1911 | 146 | View Code Duplication | if (! $assoc['isOwningSide']) { |
1912 | 2 | $assoc = $class->associationMappings[$assoc['mappedBy']]; |
|
1913 | 2 | $class = $this->em->getClassMetadata($assoc['targetEntity']); |
|
1914 | } |
||
1915 | |||
1916 | 146 | $columns = $assoc['type'] === ClassMetadata::MANY_TO_MANY |
|
1917 | 3 | ? $assoc['relationToTargetKeyColumns'] |
|
1918 | 146 | : $assoc['sourceToTargetKeyColumns']; |
|
1919 | |||
1920 | 146 | foreach ($columns as $column){ |
|
1921 | 146 | $types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em); |
|
1922 | } |
||
1923 | 146 | break; |
|
1924 | |||
1925 | default: |
||
1926 | 1 | $types[] = null; |
|
1927 | 1 | break; |
|
1928 | } |
||
1929 | |||
1930 | 706 | if (is_array($value)) { |
|
1931 | 16 | return array_map(function ($type) { |
|
1932 | 16 | $type = Type::getType($type); |
|
1933 | |||
1934 | 16 | return $type->getBindingType() + Connection::ARRAY_PARAM_OFFSET; |
|
1935 | 16 | }, $types); |
|
1936 | } |
||
1937 | |||
1938 | 696 | return $types; |
|
1939 | } |
||
1940 | |||
1941 | /** |
||
1942 | * Retrieves the parameters that identifies a value. |
||
1943 | * |
||
1944 | * @param mixed $value |
||
1945 | * |
||
1946 | * @return array |
||
1947 | */ |
||
1948 | 581 | private function getValues($value) |
|
1949 | { |
||
1950 | 581 | if (is_array($value)) { |
|
1951 | 16 | $newValue = []; |
|
1952 | |||
1953 | 16 | foreach ($value as $itemValue) { |
|
1954 | 16 | $newValue = array_merge($newValue, $this->getValues($itemValue)); |
|
1955 | } |
||
1956 | |||
1957 | 16 | return [$newValue]; |
|
1958 | } |
||
1959 | |||
1960 | 581 | if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) { |
|
1961 | 44 | $class = $this->em->getClassMetadata(get_class($value)); |
|
1962 | 44 | if ($class->isIdentifierComposite) { |
|
1963 | 3 | $newValue = []; |
|
1964 | |||
1965 | 3 | foreach ($class->getIdentifierValues($value) as $innerValue) { |
|
1966 | 3 | $newValue = array_merge($newValue, $this->getValues($innerValue)); |
|
1967 | } |
||
1968 | |||
1969 | 3 | return $newValue; |
|
1970 | } |
||
1971 | } |
||
1972 | |||
1973 | 581 | return [$this->getIndividualValue($value)]; |
|
1974 | } |
||
1975 | |||
1976 | /** |
||
1977 | * Retrieves an individual parameter value. |
||
1978 | * |
||
1979 | * @param mixed $value |
||
1980 | * |
||
1981 | * @return mixed |
||
1982 | */ |
||
1983 | 581 | private function getIndividualValue($value) |
|
1984 | { |
||
1985 | 581 | if ( ! is_object($value) || ! $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) { |
|
1986 | 578 | return $value; |
|
1987 | } |
||
1988 | |||
1989 | 44 | return $this->em->getUnitOfWork()->getSingleIdentifierValue($value); |
|
1990 | } |
||
1991 | |||
1992 | /** |
||
1993 | * {@inheritdoc} |
||
1994 | */ |
||
1995 | 14 | public function exists($entity, Criteria $extraConditions = null) |
|
1996 | { |
||
1997 | 14 | $criteria = $this->class->getIdentifierValues($entity); |
|
1998 | |||
1999 | 14 | if ( ! $criteria) { |
|
2000 | 2 | return false; |
|
2001 | } |
||
2002 | |||
2003 | 13 | $alias = $this->getSQLTableAlias($this->class->name); |
|
2004 | |||
2005 | $sql = 'SELECT 1 ' |
||
2006 | 13 | . $this->getLockTablesSql(null) |
|
2007 | 13 | . ' WHERE ' . $this->getSelectConditionSQL($criteria); |
|
2008 | |||
2009 | 13 | list($params, $types) = $this->expandParameters($criteria); |
|
2010 | |||
2011 | 13 | if (null !== $extraConditions) { |
|
2012 | 9 | $sql .= ' AND ' . $this->getSelectConditionCriteriaSQL($extraConditions); |
|
2013 | 9 | list($criteriaParams, $criteriaTypes) = $this->expandCriteriaParameters($extraConditions); |
|
2014 | |||
2015 | 9 | $params = array_merge($params, $criteriaParams); |
|
2016 | 9 | $types = array_merge($types, $criteriaTypes); |
|
2017 | } |
||
2018 | |||
2019 | 13 | if ($filterSql = $this->generateFilterConditionSQL($this->class, $alias)) { |
|
2020 | 3 | $sql .= ' AND ' . $filterSql; |
|
2021 | } |
||
2022 | |||
2023 | 13 | return (bool) $this->conn->fetchColumn($sql, $params, 0, $types); |
|
2024 | } |
||
2025 | |||
2026 | /** |
||
2027 | * Generates the appropriate join SQL for the given join column. |
||
2028 | * |
||
2029 | * @param array $joinColumns The join columns definition of an association. |
||
2030 | * |
||
2031 | * @return string LEFT JOIN if one of the columns is nullable, INNER JOIN otherwise. |
||
2032 | */ |
||
2033 | 13 | protected function getJoinSQLForJoinColumns($joinColumns) |
|
2034 | { |
||
2035 | // if one of the join columns is nullable, return left join |
||
2036 | 13 | foreach ($joinColumns as $joinColumn) { |
|
2037 | 13 | if ( ! isset($joinColumn['nullable']) || $joinColumn['nullable']) { |
|
2038 | 13 | return 'LEFT JOIN'; |
|
2039 | } |
||
2040 | } |
||
2041 | |||
2042 | 5 | return 'INNER JOIN'; |
|
2043 | } |
||
2044 | |||
2045 | /** |
||
2046 | * {@inheritdoc} |
||
2047 | */ |
||
2048 | 592 | public function getSQLColumnAlias($columnName) |
|
2049 | { |
||
2050 | 592 | return $this->quoteStrategy->getColumnAlias($columnName, $this->currentPersisterContext->sqlAliasCounter++, $this->platform); |
|
2051 | } |
||
2052 | |||
2053 | /** |
||
2054 | * Generates the filter SQL for a given entity and table alias. |
||
2055 | * |
||
2056 | * @param ClassMetadata $targetEntity Metadata of the target entity. |
||
2057 | * @param string $targetTableAlias The table alias of the joined/selected table. |
||
2058 | * |
||
2059 | * @return string The SQL query part to add to a query. |
||
2060 | */ |
||
2061 | 616 | View Code Duplication | protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias) |
2062 | { |
||
2063 | 616 | $filterClauses = []; |
|
2064 | |||
2065 | 616 | foreach ($this->em->getFilters()->getEnabledFilters() as $filter) { |
|
2066 | 22 | if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) { |
|
2067 | 22 | $filterClauses[] = '(' . $filterExpr . ')'; |
|
2068 | } |
||
2069 | } |
||
2070 | |||
2071 | 616 | $sql = implode(' AND ', $filterClauses); |
|
2072 | |||
2073 | 616 | return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" |
|
2074 | } |
||
2075 | |||
2076 | /** |
||
2077 | * Switches persister context according to current query offset/limits |
||
2078 | * |
||
2079 | * This is due to the fact that to-many associations cannot be fetch-joined when a limit is involved |
||
2080 | * |
||
2081 | * @param null|int $offset |
||
2082 | * @param null|int $limit |
||
2083 | */ |
||
2084 | 598 | protected function switchPersisterContext($offset, $limit) |
|
2085 | { |
||
2086 | 598 | if (null === $offset && null === $limit) { |
|
2087 | 585 | $this->currentPersisterContext = $this->noLimitsContext; |
|
2088 | |||
2089 | 585 | return; |
|
2090 | } |
||
2091 | |||
2092 | 42 | $this->currentPersisterContext = $this->limitsHandlingContext; |
|
2093 | 42 | } |
|
2094 | } |
||
2095 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.