ActiveRecordBatchProcessorTrait   F
last analyzed

Complexity

Total Complexity 86

Size/Duplication

Total Lines 747
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 259
c 0
b 0
f 0
dl 0
loc 747
rs 2
wmc 86

33 Methods

Rating   Name   Duplication   Size   Complexity  
A postSave() 0 4 2
A insertEntities() 0 11 2
A clear() 0 4 1
A getWriteConnection() 0 5 1
A persist() 0 15 4
A preUpdate() 0 7 1
A postInsert() 0 4 2
A preSave() 0 7 1
A updateEntities() 0 11 2
A insertIdenticalEntities() 0 9 2
A commit() 0 8 1
A preInsert() 0 7 1
A commitIdentical() 0 8 1
A postUpdate() 0 4 2
A executeStatements() 0 14 3
A getAdapter() 0 7 2
A getTableMapClass() 0 8 2
A prepareStatement() 0 10 2
A bindInsertValuesIdentical() 0 7 2
A quote() 0 7 2
A requiresPrimaryKeyValue() 0 3 1
A getHighPrecisionDateTime() 0 7 2
A bindInsertValues() 0 14 3
A bindUpdateValues() 0 7 2
A updateDateTimes() 0 15 4
A buildInsertStatementForIdenticalEntities() 0 40 4
A buildInsertStatements() 0 37 5
A prepareValuesForSave() 0 11 3
B buildInsertStatementIdentical() 0 48 6
A buildUpdateStatements() 0 43 3
A getValue() 0 16 6
A prepareValuesForUpdate() 0 26 4
B prepareValuesForInsert() 0 33 7

How to fix   Complexity   

Complex Class

Complex classes like ActiveRecordBatchProcessorTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ActiveRecordBatchProcessorTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Propel\Persistence\BatchProcessor;
9
10
use DateTime;
11
use Exception;
12
use PDO;
13
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
14
use Propel\Runtime\Adapter\AdapterInterface;
15
use Propel\Runtime\Adapter\Pdo\PgsqlAdapter;
16
use Propel\Runtime\Connection\ConnectionInterface;
17
use Propel\Runtime\Connection\StatementInterface;
18
use Propel\Runtime\Exception\PropelException;
19
use Propel\Runtime\Map\ColumnMap;
20
use Propel\Runtime\Map\TableMap;
21
use Propel\Runtime\Propel;
22
use Propel\Runtime\Util\PropelDateTime;
23
use Spryker\Zed\Propel\Exception\StatementNotPreparedException;
24
use Throwable;
25
26
/**
27
 * This trait is not capable to do insert/update of related entities.
28
 * P&S is not triggered while using this trait.
29
 */
30
trait ActiveRecordBatchProcessorTrait
31
{
32
    /**
33
     * @var array<string, array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>>
34
     */
35
    protected $entitiesToInsert = [];
36
37
    /**
38
     * @var array<string, array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>>
39
     */
40
    protected $entitiesToUpdate = [];
41
42
    /**
43
     * @var array<\Propel\Runtime\Map\TableMap>
44
     */
45
    protected $tableMapClasses = [];
46
47
    /**
48
     * @var \Propel\Runtime\Adapter\AdapterInterface
49
     */
50
    protected $adapter;
51
52
    /**
53
     * @var \DateTime
54
     */
55
    protected $highPrecisionDateTime;
56
57
    /**
58
     * @param \Propel\Runtime\ActiveRecord\ActiveRecordInterface $entity
59
     *
60
     * @return void
61
     */
62
    public function persist(ActiveRecordInterface $entity): void
63
    {
64
        if (!$entity->isModified()) {
65
            return;
66
        }
67
68
        $storageName = $entity->isNew() ? 'entitiesToInsert' : 'entitiesToUpdate';
69
70
        $className = get_class($entity);
71
72
        if (!isset($this->{$storageName}[$className])) {
73
            $this->{$storageName}[$className] = [];
74
        }
75
76
        $this->{$storageName}[$className][] = $entity;
77
    }
78
79
    /**
80
     * @return bool
81
     */
82
    public function commit(): bool
83
    {
84
        $this->insertEntities($this->entitiesToInsert);
85
        $this->updateEntities($this->entitiesToUpdate);
86
87
        $this->clear();
88
89
        return true;
90
    }
91
92
    /**
93
     * @return bool
94
     */
95
    public function commitIdentical(): bool
96
    {
97
        $this->insertIdenticalEntities($this->entitiesToInsert);
98
        $this->updateEntities($this->entitiesToUpdate);
99
100
        $this->clear();
101
102
        return true;
103
    }
104
105
    /**
106
     * @param array<string, array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>> $entitiesToInsert
107
     *
108
     * @return void
109
     */
110
    protected function insertEntities(array $entitiesToInsert): void
111
    {
112
        foreach ($entitiesToInsert as $entityClassName => $entities) {
113
            $connection = $this->getWriteConnection($entityClassName);
114
115
            $entities = $this->preSave($entities, $connection);
116
            $entities = $this->preInsert($entities, $connection);
117
            $statements = $this->buildInsertStatements($entityClassName, $entities);
118
            $this->executeStatements($statements, $entityClassName, 'insert');
119
            $this->postInsert($entities, $connection);
120
            $this->postSave($entities, $connection);
121
        }
122
    }
123
124
    /**
125
     * All entities have to be identical in terms of modified columns.
126
     *
127
     * @param array<string, array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>> $entitiesToInsert
128
     *
129
     * @return void
130
     */
131
    protected function insertIdenticalEntities(array $entitiesToInsert): void
132
    {
133
        foreach ($entitiesToInsert as $entityClassName => $entities) {
134
            $connection = $this->getWriteConnection($entityClassName);
135
            $entities = $this->preSave($entities, $connection);
136
            $entities = $this->preInsert($entities, $connection);
137
            $statement = $this->buildInsertStatementIdentical($entityClassName, $entities);
138
            $this->executeStatements([$statement], $entityClassName, 'insert');
139
            $this->postInsert($entities, $connection);
140
        }
141
    }
142
143
    /**
144
     * @param array<string, array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>> $entitiesToUpdate
145
     *
146
     * @return void
147
     */
148
    protected function updateEntities(array $entitiesToUpdate): void
149
    {
150
        foreach ($entitiesToUpdate as $entityClassName => $entities) {
151
            $connection = $this->getWriteConnection($entityClassName);
152
153
            $entities = $this->preSave($entities, $connection);
154
            $entities = $this->preUpdate($entities, $connection);
155
            $statements = $this->buildUpdateStatements($entityClassName, $entities);
156
            $this->executeStatements($statements, $entityClassName, 'update');
157
            $this->postUpdate($entities, $connection);
158
            $this->postSave($entities, $connection);
159
        }
160
    }
161
162
    /**
163
     * @param array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface> $entities
164
     * @param \Propel\Runtime\Connection\ConnectionInterface $connection
165
     *
166
     * @return array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>
167
     */
168
    protected function preSave(array $entities, ConnectionInterface $connection): array
169
    {
170
        array_filter($entities, function (ActiveRecordInterface $entity) use ($connection) {
171
            return $entity->preSave($connection);
172
        });
173
174
        return $entities;
175
    }
176
177
    /**
178
     * @param array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface> $entities
179
     * @param \Propel\Runtime\Connection\ConnectionInterface $connection
180
     *
181
     * @return void
182
     */
183
    protected function postSave(array $entities, ConnectionInterface $connection): void
184
    {
185
        foreach ($entities as $entity) {
186
            $entity->postSave($connection);
187
        }
188
    }
189
190
    /**
191
     * @param array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface> $entities
192
     * @param \Propel\Runtime\Connection\ConnectionInterface $connection
193
     *
194
     * @return array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>
195
     */
196
    protected function preInsert(array $entities, ConnectionInterface $connection): array
197
    {
198
        array_filter($entities, function (ActiveRecordInterface $entity) use ($connection) {
199
            return $entity->preInsert($connection);
200
        });
201
202
        return $entities;
203
    }
204
205
    /**
206
     * @param array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface> $entities
207
     * @param \Propel\Runtime\Connection\ConnectionInterface $connection
208
     *
209
     * @return void
210
     */
211
    protected function postInsert(array $entities, ConnectionInterface $connection): void
212
    {
213
        foreach ($entities as $entity) {
214
            $entity->postInsert($connection);
215
        }
216
    }
217
218
    /**
219
     * @param array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface> $entities
220
     * @param \Propel\Runtime\Connection\ConnectionInterface $connection
221
     *
222
     * @return array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface>
223
     */
224
    protected function preUpdate(array $entities, ConnectionInterface $connection): array
225
    {
226
        array_filter($entities, function (ActiveRecordInterface $entity) use ($connection) {
227
            return $entity->preUpdate($connection);
228
        });
229
230
        return $entities;
231
    }
232
233
    /**
234
     * @param array<\Propel\Runtime\ActiveRecord\ActiveRecordInterface> $entities
235
     * @param \Propel\Runtime\Connection\ConnectionInterface $connection
236
     *
237
     * @return void
238
     */
239
    protected function postUpdate(array $entities, ConnectionInterface $connection): void
240
    {
241
        foreach ($entities as $entity) {
242
            $entity->postUpdate($connection);
243
        }
244
    }
245
246
    /**
247
     * @param array<\Propel\Runtime\Connection\StatementInterface> $statements
248
     * @param string $entityClassName
249
     * @param string $type
250
     *
251
     * @throws \Exception
252
     *
253
     * @return void
254
     */
255
    protected function executeStatements(array $statements, string $entityClassName, string $type): void
256
    {
257
        try {
258
            $connection = $this->getWriteConnection($entityClassName);
259
260
            $connection->beginTransaction();
261
            foreach ($statements as $statement) {
262
                $statement->execute();
263
            }
264
            $connection->commit();
265
        } catch (Throwable $throwable) {
266
            $connection->rollBack();
267
268
            throw new Exception(sprintf('Failed to execute %s statement for %s. Error: %s', $type, $entityClassName, $throwable->getMessage()), 0, $throwable);
269
        }
270
    }
271
272
    /**
273
     * @param string $entityClassName
274
     *
275
     * @return \Propel\Runtime\Connection\ConnectionInterface
276
     */
277
    protected function getWriteConnection(string $entityClassName): ConnectionInterface
278
    {
279
        $tableMapClass = $this->getTableMapClass($entityClassName);
280
281
        return Propel::getServiceContainer()->getWriteConnection($tableMapClass::DATABASE_NAME);
282
    }
283
284
    /**
285
     * @return void
286
     */
287
    protected function clear(): void
288
    {
289
        $this->entitiesToInsert = [];
290
        $this->entitiesToUpdate = [];
291
    }
292
293
    /**
294
     * @param string $entityClassName
295
     * @param array $entities
296
     *
297
     * @return array<\Propel\Runtime\Connection\StatementInterface>
298
     */
299
    protected function buildInsertStatements(string $entityClassName, array $entities): array
300
    {
301
        $tableMapClass = $this->getTableMapClass($entityClassName);
302
303
        $statements = [];
304
305
        $statementsGroupedByInsertedColumns = [];
306
307
        foreach ($entities as $entity) {
308
            $keyIndex = 0;
309
            $entity = $this->updateDateTimes($entity);
310
            $valuesForInsert = $this->prepareValuesForInsert(
311
                $tableMapClass->getColumns(),
312
                $tableMapClass,
313
                $entityClassName::TABLE_MAP,
314
                $entity,
315
                $this->requiresPrimaryKeyValue(),
316
            );
317
318
            $columnNamesForInsertWithPdoPlaceholder = array_map(function (array $columnDetails) use (&$keyIndex, $tableMapClass) {
319
                if ($columnDetails['columnMap']->isPrimaryKey() && $tableMapClass->getPrimaryKeyMethodInfo() !== null) {
320
                    return sprintf('(SELECT nextval(\'%s\'))', $tableMapClass->getPrimaryKeyMethodInfo());
321
                }
322
323
                return sprintf(':p%d', $keyIndex++);
324
            }, $valuesForInsert);
325
326
            $key = implode(',', array_keys($columnNamesForInsertWithPdoPlaceholder));
327
328
            $statementsGroupedByInsertedColumns[$key][] = $entity;
329
        }
330
331
        foreach ($statementsGroupedByInsertedColumns as $entities) {
332
            $statements[] = $this->buildInsertStatementIdentical($entityClassName, $entities);
333
        }
334
335
        return $statements;
336
    }
337
338
    /**
339
     * @param string $entityClassName
340
     * @param array $entities
341
     *
342
     * @return \Propel\Runtime\Connection\StatementInterface
343
     */
344
    protected function buildInsertStatementIdentical(string $entityClassName, array $entities): StatementInterface
345
    {
346
        $tableMapClass = $this->getTableMapClass($entityClassName);
347
        $requiresPrimaryKeyValue = $this->requiresPrimaryKeyValue();
348
349
        $connection = $this->getWriteConnection($entityClassName);
350
        $keyIndex = 0;
351
        $valuesForBind = [];
352
        $entitiesQueryParams = [];
353
        $entityQueryParams = [];
354
355
        foreach ($entities as $entity) {
356
            $entity = $this->updateDateTimes($entity);
357
            $valuesForInsert = $this->prepareValuesForInsert(
358
                $tableMapClass->getColumns(),
359
                $tableMapClass,
360
                $entityClassName::TABLE_MAP,
361
                $entity,
362
                $requiresPrimaryKeyValue,
363
            );
364
365
            foreach ($valuesForInsert as $columnDetails) {
366
                if ($requiresPrimaryKeyValue && $columnDetails['columnMap']->isPrimaryKey() && $tableMapClass->getPrimaryKeyMethodInfo() !== null) {
367
                    $entityQueryParams[] = sprintf('(SELECT nextval(\'%s\'))', $tableMapClass->getPrimaryKeyMethodInfo());
368
369
                    continue;
370
                }
371
372
                $queryParamKey = sprintf(':p%d', $keyIndex++);
373
                $valuesForBind[$queryParamKey] = $columnDetails;
374
                $entityQueryParams[] = $queryParamKey;
375
            }
376
377
            $entitiesQueryParams[] = sprintf('(%s)', implode(', ', $entityQueryParams));
378
            $entityQueryParams = [];
379
        }
380
381
        $sql = sprintf(
382
            'INSERT INTO %s (%s) VALUES %s;',
383
            $tableMapClass->getName(),
384
            implode(', ', array_keys($valuesForInsert)),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $valuesForInsert seems to be defined by a foreach iteration on line 355. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
385
            implode(', ', $entitiesQueryParams),
386
        );
387
388
        $statement = $this->prepareStatement($sql, $connection);
389
        $statement = $this->bindInsertValuesIdentical($statement, $valuesForBind);
390
391
        return $statement;
392
    }
393
394
    /**
395
     * @deprecated Use {@link buildInsertStatementIdentical()} instead.
396
     *
397
     * @param string $entityClassName
398
     * @param array $entities
399
     *
400
     * @return \Propel\Runtime\Connection\StatementInterface
401
     */
402
    protected function buildInsertStatementForIdenticalEntities(string $entityClassName, array $entities): StatementInterface
403
    {
404
        $tableMapClass = $this->getTableMapClass($entityClassName);
405
406
        $connection = $this->getWriteConnection($entityClassName);
407
        $statements = [];
408
409
        foreach ($entities as $entity) {
410
            $keyIndex = 0;
411
            $entity = $this->updateDateTimes($entity);
412
            $valuesForInsert = $this->prepareValuesForInsert(
413
                $tableMapClass->getColumns(),
414
                $tableMapClass,
415
                $entityClassName::TABLE_MAP,
416
                $entity,
417
                $this->requiresPrimaryKeyValue(),
418
            );
419
420
            $columnNamesForInsertWithPdoPlaceholder = array_map(function (array $columnDetails) use (&$keyIndex, $tableMapClass) {
421
                if ($columnDetails['columnMap']->isPrimaryKey() && $tableMapClass->getPrimaryKeyMethodInfo() !== null) {
422
                    return sprintf('(SELECT nextval(\'%s\'))', $tableMapClass->getPrimaryKeyMethodInfo());
423
                }
424
425
                return sprintf(':p%d', $keyIndex++);
426
            }, $valuesForInsert);
427
428
            $sql = sprintf(
429
                'INSERT INTO %s (%s) VALUES (%s);',
430
                $tableMapClass->getName(),
431
                implode(', ', array_keys($columnNamesForInsertWithPdoPlaceholder)),
432
                implode(', ', $columnNamesForInsertWithPdoPlaceholder),
433
            );
434
435
            $statement = $this->prepareStatement($sql, $connection);
436
            $statement = $this->bindInsertValues($statement, $valuesForInsert);
437
438
            $statements[] = $statement;
439
        }
440
441
        return $statements;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statements returns the type Propel\Runtime\Connectio...tementInterface[]|array which is incompatible with the type-hinted return Propel\Runtime\Connection\StatementInterface.
Loading history...
442
    }
443
444
    /**
445
     * @param array<\Propel\Runtime\Map\ColumnMap> $columnMapCollection
446
     * @param \Propel\Runtime\Map\TableMap $tableMapClass
447
     * @param string $tableMapClassName
448
     * @param \Propel\Runtime\ActiveRecord\ActiveRecordInterface $entity
449
     * @param bool $requiresPrimaryKeyValue
450
     *
451
     * @return array<string, mixed>
452
     */
453
    protected function prepareValuesForInsert(
454
        array $columnMapCollection,
455
        TableMap $tableMapClass,
456
        string $tableMapClassName,
457
        ActiveRecordInterface $entity,
458
        bool $requiresPrimaryKeyValue
459
    ): array {
460
        $valuesForInsert = [];
461
462
        $entityData = $entity->toArray(TableMap::TYPE_FIELDNAME);
463
464
        foreach ($columnMapCollection as $columnIdentifier => $columnMap) {
465
            $quotedColumnName = $this->quote($columnMap->getName(), $tableMapClass);
466
            if ($columnMap->isPrimaryKey() && !$columnMap->isForeignKey()) {
467
                if (!$requiresPrimaryKeyValue || $tableMapClass->getPrimaryKeyMethodInfo() === null) {
468
                    continue;
469
                }
470
471
                $value = sprintf('(SELECT nextval(\'%s\'))', $tableMapClass->getPrimaryKeyMethodInfo());
472
                $valuesForInsert[$quotedColumnName] = $this->prepareValuesForSave($columnMap, $entityData, $value);
473
474
                continue;
475
            }
476
477
            $columnIdentifier = sprintf('COL_%s', $columnIdentifier);
478
            $fullyQualifiedColumnName = constant(sprintf('%s::%s', $tableMapClassName, $columnIdentifier));
479
480
            if ($entity->isColumnModified($fullyQualifiedColumnName)) {
481
                $valuesForInsert[$quotedColumnName] = $this->prepareValuesForSave($columnMap, $entityData);
482
            }
483
        }
484
485
        return $valuesForInsert;
486
    }
487
488
    /**
489
     * @param string $sql
490
     * @param \Propel\Runtime\Connection\ConnectionInterface $connection
491
     *
492
     * @throws \Spryker\Zed\Propel\Exception\StatementNotPreparedException
493
     *
494
     * @return \Propel\Runtime\Connection\StatementInterface
495
     */
496
    protected function prepareStatement(string $sql, ConnectionInterface $connection): StatementInterface
497
    {
498
        $connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
499
        $statement = $connection->prepare($sql);
500
501
        if (!$statement) {
502
            throw new StatementNotPreparedException(sprintf('Wasn\'t able to create a statement with provided query: `%s`', $sql));
503
        }
504
505
        return $statement;
506
    }
507
508
    /**
509
     * @param \Propel\Runtime\Connection\StatementInterface $statement
510
     * @param array $values
511
     *
512
     * @return \Propel\Runtime\Connection\StatementInterface
513
     */
514
    protected function bindInsertValues(StatementInterface $statement, array $values): StatementInterface
515
    {
516
        $values = array_filter($values, function (array $columnDetails) {
517
            /** @var \Propel\Runtime\Map\ColumnMap $columnMap */
518
            $columnMap = $columnDetails['columnMap'];
519
520
            return !($columnMap->isPrimaryKey() && !$columnMap->isForeignKey());
521
        });
522
523
        foreach (array_values($values) as $index => $value) {
524
            $statement->bindValue(sprintf(':p%d', $index), $value['value'], $value['type']);
525
        }
526
527
        return $statement;
528
    }
529
530
    /**
531
     * @param \Propel\Runtime\Connection\StatementInterface $statement
532
     * @param array $valuesForBind
533
     *
534
     * @return \Propel\Runtime\Connection\StatementInterface
535
     */
536
    protected function bindInsertValuesIdentical(StatementInterface $statement, array $valuesForBind): StatementInterface
537
    {
538
        foreach ($valuesForBind as $queryParam => $value) {
539
            $statement->bindValue($queryParam, $value['value'], $value['type']);
540
        }
541
542
        return $statement;
543
    }
544
545
    /**
546
     * @param \Propel\Runtime\Map\ColumnMap $columnMap
547
     * @param array $entityData
548
     * @param string|null $defaultValue
549
     *
550
     * @return array<string, mixed>
551
     */
552
    protected function prepareValuesForSave(ColumnMap $columnMap, array $entityData, ?string $defaultValue = null): array
553
    {
554
        $value = $defaultValue ?: $entityData[$columnMap->getName()];
555
        if (is_array($value)) {
556
            $value = json_encode($value);
557
        }
558
559
        return [
560
            'columnMap' => $columnMap,
561
            'value' => $value,
562
            'type' => $columnMap->getPdoType(),
563
        ];
564
    }
565
566
    /**
567
     * @param string $entityClassName
568
     * @param array $entities
569
     *
570
     * @return array<\Propel\Runtime\Connection\StatementInterface>
571
     */
572
    protected function buildUpdateStatements(string $entityClassName, array $entities): array
573
    {
574
        $tableMapClass = $this->getTableMapClass($entityClassName);
575
576
        $connection = $this->getWriteConnection($entityClassName);
577
        $statements = [];
578
579
        foreach ($entities as $entity) {
580
            $keyIndex = 0;
581
            $entity = $this->updateDateTimes($entity);
582
583
            [$valuesForUpdate, $idColumnValuesAndTypes] = $this->prepareValuesForUpdate(
584
                $tableMapClass->getColumns(),
585
                $tableMapClass,
586
                $entityClassName::TABLE_MAP,
587
                $entity,
588
            );
589
590
            $columnNamesForUpdateWithPdoPlaceholder = array_map(function ($columnName) use (&$keyIndex, $tableMapClass) {
591
                return sprintf('%s=:p%d', $this->quote($columnName, $tableMapClass), $keyIndex++);
592
            }, array_keys($valuesForUpdate));
593
594
            $values = array_merge(array_values($valuesForUpdate), array_values($idColumnValuesAndTypes));
595
596
            $whereClauses = [];
597
598
            foreach (array_keys($idColumnValuesAndTypes) as $primaryKeyColumnName) {
599
                $whereClauses[] = sprintf('%s.%s=:p%d', $tableMapClass->getName(), $primaryKeyColumnName, $keyIndex++);
600
            }
601
602
            $sql = sprintf(
603
                'UPDATE %s SET %s WHERE %s;',
604
                $tableMapClass->getName(),
605
                implode(', ', $columnNamesForUpdateWithPdoPlaceholder),
606
                implode(' AND ', $whereClauses),
607
            );
608
609
            $statement = $this->prepareStatement($sql, $connection);
610
            $statement = $this->bindUpdateValues($statement, $values);
611
            $statements[] = $statement;
612
        }
613
614
        return $statements;
615
    }
616
617
    /**
618
     * @param array $columnMapCollection
619
     * @param \Propel\Runtime\Map\TableMap $tableMapClass
620
     * @param string $tableMapClassName
621
     * @param \Propel\Runtime\ActiveRecord\ActiveRecordInterface $entity
622
     *
623
     * @return array
624
     */
625
    protected function prepareValuesForUpdate(
626
        array $columnMapCollection,
627
        TableMap $tableMapClass,
628
        string $tableMapClassName,
629
        ActiveRecordInterface $entity
630
    ): array {
631
        $valuesForUpdate = [];
632
        $idColumnValuesAndTypes = [];
633
        $entityData = $entity->toArray(TableMap::TYPE_FIELDNAME);
634
635
        foreach ($columnMapCollection as $columnIdentifier => $columnMap) {
636
            if ($columnMap->isPrimaryKey()) {
637
                $idColumnValuesAndTypes[$columnMap->getName()] = $this->prepareValuesForSave($columnMap, $entityData);
638
639
                continue;
640
            }
641
642
            $columnIdentifier = sprintf('COL_%s', $columnIdentifier);
643
            $fullyQualifiedColumnName = constant(sprintf('%s::%s', $tableMapClassName, $columnIdentifier));
644
645
            if ($entity->isColumnModified($fullyQualifiedColumnName)) {
646
                $valuesForUpdate[$columnMap->getName()] = $this->prepareValuesForSave($columnMap, $entityData);
647
            }
648
        }
649
650
        return [$valuesForUpdate, $idColumnValuesAndTypes];
651
    }
652
653
    /**
654
     * @param \Propel\Runtime\Connection\StatementInterface $statement
655
     * @param array $values
656
     *
657
     * @return \Propel\Runtime\Connection\StatementInterface
658
     */
659
    protected function bindUpdateValues(StatementInterface $statement, array $values): StatementInterface
660
    {
661
        foreach (array_values($values) as $index => $value) {
662
            $statement->bindValue(sprintf(':p%d', $index), $value['value'], $value['type']);
663
        }
664
665
        return $statement;
666
    }
667
668
    /**
669
     * @param string $columnName
670
     * @param \Propel\Runtime\Map\TableMap $tableMapClass
671
     *
672
     * @return string
673
     */
674
    protected function quote(string $columnName, TableMap $tableMapClass): string
675
    {
676
        if ($tableMapClass->isIdentifierQuotingEnabled()) {
677
            return $this->getAdapter()->quote($columnName);
678
        }
679
680
        return $columnName;
681
    }
682
683
    /**
684
     * @return \Propel\Runtime\Adapter\AdapterInterface
685
     */
686
    protected function getAdapter(): AdapterInterface
687
    {
688
        if ($this->adapter === null) {
689
            $this->adapter = Propel::getServiceContainer()->getAdapter();
690
        }
691
692
        return $this->adapter;
693
    }
694
695
    /**
696
     * @param string $entityClassName
697
     *
698
     * @return \Propel\Runtime\Map\TableMap
699
     */
700
    protected function getTableMapClass(string $entityClassName): TableMap
701
    {
702
        if (!isset($this->tableMapClasses[$entityClassName])) {
703
            $tableMapClassName = $entityClassName::TABLE_MAP;
704
            $this->tableMapClasses[$entityClassName] = new $tableMapClassName();
705
        }
706
707
        return $this->tableMapClasses[$entityClassName];
708
    }
709
710
    /**
711
     * @param \Propel\Runtime\ActiveRecord\ActiveRecordInterface $entity
712
     *
713
     * @return \Propel\Runtime\ActiveRecord\ActiveRecordInterface
714
     */
715
    protected function updateDateTimes(ActiveRecordInterface $entity): ActiveRecordInterface
716
    {
717
        $highPrecisionDateTime = $this->getHighPrecisionDateTime();
718
719
        if ($entity->isNew()) {
720
            if (method_exists($entity, 'setCreatedAt')) {
721
                $entity->setCreatedAt($highPrecisionDateTime);
722
            }
723
        }
724
725
        if (method_exists($entity, 'setUpdatedAt')) {
726
            $entity->setUpdatedAt($highPrecisionDateTime);
727
        }
728
729
        return $entity;
730
    }
731
732
    /**
733
     * @return \DateTime
734
     */
735
    protected function getHighPrecisionDateTime(): DateTime
736
    {
737
        if ($this->highPrecisionDateTime === null) {
738
            $this->highPrecisionDateTime = PropelDateTime::createHighPrecision();
739
        }
740
741
        return $this->highPrecisionDateTime;
742
    }
743
744
    /**
745
     * @param \Propel\Runtime\Map\ColumnMap $columnMap
746
     * @param \Propel\Runtime\Map\TableMap $tableMap
747
     * @param array|string|float|int|bool $value
748
     *
749
     * @throws \Propel\Runtime\Exception\PropelException
750
     *
751
     * @return string|float|int|bool
752
     */
753
    protected function getValue(ColumnMap $columnMap, TableMap $tableMap, $value)
754
    {
755
        if ($columnMap->getType() === 'ENUM' && $value !== null) {
756
            /** @psalm-suppress UndefinedMethod */
757
            $valueSet = $tableMap::getValueSet($columnMap->getFullyQualifiedName());
758
            if (!in_array($value, $valueSet)) {
759
                throw new PropelException(sprintf('Value "%s" is not accepted in this enumerated column', (string)$value));
760
            }
761
            $value = array_search($value, $valueSet);
762
        }
763
764
        if ($columnMap->getType() === 'LONGVARCHAR' && is_array($value)) {
765
            $value = (string)json_encode($value);
766
        }
767
768
        return $value;
769
    }
770
771
    /**
772
     * @return bool
773
     */
774
    protected function requiresPrimaryKeyValue(): bool
775
    {
776
        return ($this->getAdapter() instanceof PgsqlAdapter);
777
    }
778
}
779