3
3
namespace Composite \DB ;
4
4
5
5
use Composite \DB \MultiQuery \MultiInsert ;
6
+ use Composite \DB \MultiQuery \MultiSelect ;
6
7
use Composite \Entity \Helpers \DateTimeHelper ;
7
8
use Composite \Entity \AbstractEntity ;
8
9
use Composite \DB \Exceptions \DbException ;
9
10
use Composite \Entity \Exceptions \EntityException ;
10
11
use Doctrine \DBAL \Connection ;
11
12
use Doctrine \DBAL \Platforms \PostgreSQLPlatform ;
12
13
use Doctrine \DBAL \Query \QueryBuilder ;
14
+ use Ramsey \Uuid \UuidInterface ;
13
15
14
16
abstract class AbstractTable
15
17
{
@@ -70,35 +72,20 @@ public function save(AbstractEntity &$entity): void
70
72
$ entity ->updated_at = new \DateTimeImmutable ();
71
73
$ changedColumns ['updated_at ' ] = DateTimeHelper::dateTimeToString ($ entity ->updated_at );
72
74
}
73
-
74
- if ($ this ->config ->hasOptimisticLock () && isset ($ entity ->version )) {
75
- $ currentVersion = $ entity ->version ;
76
- try {
77
- $ connection ->beginTransaction ();
78
- $ connection ->update (
79
- $ this ->getTableName (),
80
- $ changedColumns ,
81
- $ where
82
- );
83
- $ versionUpdated = $ connection ->update (
84
- $ this ->getTableName (),
85
- ['version ' => $ currentVersion + 1 ],
86
- $ where + ['version ' => $ currentVersion ]
87
- );
88
- if (!$ versionUpdated ) {
89
- throw new DbException ('Failed to update entity version, concurrency modification, rolling back. ' );
90
- }
91
- $ connection ->commit ();
92
- } catch (\Throwable $ e ) {
93
- $ connection ->rollBack ();
94
- throw $ e ;
95
- }
96
- } else {
97
- $ connection ->update (
98
- $ this ->getTableName (),
99
- $ changedColumns ,
100
- $ where
101
- );
75
+ if ($ this ->config ->hasOptimisticLock ()
76
+ && method_exists ($ entity , 'getVersion ' )
77
+ && method_exists ($ entity , 'incrementVersion ' )) {
78
+ $ where ['lock_version ' ] = $ entity ->getVersion ();
79
+ $ entity ->incrementVersion ();
80
+ $ changedColumns ['lock_version ' ] = $ entity ->getVersion ();
81
+ }
82
+ $ entityUpdated = $ connection ->update (
83
+ table: $ this ->getTableName (),
84
+ data: $ changedColumns ,
85
+ criteria: $ where ,
86
+ );
87
+ if ($ this ->config ->hasOptimisticLock () && !$ entityUpdated ) {
88
+ throw new Exceptions \LockException ('Failed to update entity version, concurrency modification, rolling back. ' );
102
89
}
103
90
$ entity ->resetChangedColumns ();
104
91
}
@@ -211,66 +198,31 @@ protected function findByPkInternal(mixed $pk): ?array
211
198
212
199
/**
213
200
* @param array<string, mixed> $where
201
+ * @param array<string, string>|string $orderBy
214
202
* @return array<string, mixed>|null
215
203
* @throws \Doctrine\DBAL\Exception
216
204
*/
217
- protected function findOneInternal (array $ where ): ?array
205
+ protected function findOneInternal (array $ where, array | string $ orderBy = [] ): ?array
218
206
{
219
207
$ query = $ this ->select ();
220
208
$ this ->buildWhere ($ query , $ where );
209
+ $ this ->applyOrderBy ($ query , $ orderBy );
221
210
return $ query ->fetchAssociative () ?: null ;
222
211
}
223
212
224
213
/**
225
214
* @param array<int|string|array<string,mixed>> $pkList
226
215
* @return array<array<string, mixed>>
227
216
* @throws DbException
228
- * @throws EntityException
229
217
* @throws \Doctrine\DBAL\Exception
230
218
*/
231
219
protected function findMultiInternal (array $ pkList ): array
232
220
{
233
221
if (!$ pkList ) {
234
222
return [];
235
223
}
236
- /** @var class-string<AbstractEntity> $class */
237
- $ class = $ this ->config ->entityClass ;
238
-
239
- $ pkColumns = [];
240
- foreach ($ this ->config ->primaryKeys as $ primaryKeyName ) {
241
- $ pkColumns [$ primaryKeyName ] = $ class ::schema ()->getColumn ($ primaryKeyName );
242
- }
243
- if (count ($ pkColumns ) === 1 ) {
244
- if (!array_is_list ($ pkList )) {
245
- throw new DbException ('Input argument $pkList must be list ' );
246
- }
247
- /** @var \Composite\Entity\Columns\AbstractColumn $pkColumn */
248
- $ pkColumn = reset ($ pkColumns );
249
- $ preparedPkValues = array_map (fn ($ pk ) => $ pkColumn ->uncast ($ pk ), $ pkList );
250
- $ query = $ this ->select ();
251
- $ this ->buildWhere ($ query , [$ pkColumn ->name => $ preparedPkValues ]);
252
- } else {
253
- $ query = $ this ->select ();
254
- $ expressions = [];
255
- foreach ($ pkList as $ i => $ pkArray ) {
256
- if (!is_array ($ pkArray )) {
257
- throw new DbException ('For tables with composite keys, input array must consist associative arrays ' );
258
- }
259
- $ pkOrExpr = [];
260
- foreach ($ pkArray as $ pkName => $ pkValue ) {
261
- if (is_string ($ pkName ) && isset ($ pkColumns [$ pkName ])) {
262
- $ preparedPkValue = $ pkColumns [$ pkName ]->cast ($ pkValue );
263
- $ pkOrExpr [] = $ query ->expr ()->eq ($ pkName , ': ' . $ pkName . $ i );
264
- $ query ->setParameter ($ pkName . $ i , $ preparedPkValue );
265
- }
266
- }
267
- if ($ pkOrExpr ) {
268
- $ expressions [] = $ query ->expr ()->and (...$ pkOrExpr );
269
- }
270
- }
271
- $ query ->where ($ query ->expr ()->or (...$ expressions ));
272
- }
273
- return $ query ->executeQuery ()->fetchAllAssociative ();
224
+ $ multiSelect = new MultiSelect ($ this ->getConnection (), $ this ->config , $ pkList );
225
+ return $ multiSelect ->getQueryBuilder ()->executeQuery ()->fetchAllAssociative ();
274
226
}
275
227
276
228
/**
@@ -294,22 +246,7 @@ protected function findAllInternal(
294
246
$ query ->setParameter ($ param , $ value );
295
247
}
296
248
}
297
- if ($ orderBy ) {
298
- if (is_array ($ orderBy )) {
299
- foreach ($ orderBy as $ column => $ direction ) {
300
- $ query ->addOrderBy ($ column , $ direction );
301
- }
302
- } else {
303
- foreach (explode (', ' , $ orderBy ) as $ orderByPart ) {
304
- $ orderByPart = trim ($ orderByPart );
305
- if (preg_match ('/(.+)\s(asc|desc)$/i ' , $ orderByPart , $ orderByPartMatch )) {
306
- $ query ->addOrderBy ($ orderByPartMatch [1 ], $ orderByPartMatch [2 ]);
307
- } else {
308
- $ query ->addOrderBy ($ orderByPart );
309
- }
310
- }
311
- }
312
- }
249
+ $ this ->applyOrderBy ($ query , $ orderBy );
313
250
if ($ limit > 0 ) {
314
251
$ query ->setMaxResults ($ limit );
315
252
}
@@ -366,7 +303,7 @@ final protected function createEntities(mixed $data, ?string $keyColumnName = nu
366
303
* @return array<string, mixed>
367
304
* @throws EntityException
368
305
*/
369
- protected function getPkCondition (int |string |array |AbstractEntity $ data ): array
306
+ protected function getPkCondition (int |string |array |AbstractEntity | UuidInterface $ data ): array
370
307
{
371
308
$ condition = [];
372
309
if ($ data instanceof AbstractEntity) {
@@ -395,7 +332,7 @@ protected function select(string $select = '*'): QueryBuilder
395
332
/**
396
333
* @param array<string, mixed> $where
397
334
*/
398
- private function buildWhere (QueryBuilder $ query , array $ where ): void
335
+ private function buildWhere (\ Doctrine \ DBAL \ Query \ QueryBuilder $ query , array $ where ): void
399
336
{
400
337
foreach ($ where as $ column => $ value ) {
401
338
if ($ value === null ) {
@@ -433,4 +370,28 @@ private function formatData(array $data): array
433
370
}
434
371
return $ data ;
435
372
}
373
+
374
+ /**
375
+ * @param array<string, string>|string $orderBy
376
+ */
377
+ private function applyOrderBy (QueryBuilder $ query , string |array $ orderBy ): void
378
+ {
379
+ if (!$ orderBy ) {
380
+ return ;
381
+ }
382
+ if (is_array ($ orderBy )) {
383
+ foreach ($ orderBy as $ column => $ direction ) {
384
+ $ query ->addOrderBy ($ column , $ direction );
385
+ }
386
+ } else {
387
+ foreach (explode (', ' , $ orderBy ) as $ orderByPart ) {
388
+ $ orderByPart = trim ($ orderByPart );
389
+ if (preg_match ('/(.+)\s(asc|desc)$/i ' , $ orderByPart , $ orderByPartMatch )) {
390
+ $ query ->addOrderBy ($ orderByPartMatch [1 ], $ orderByPartMatch [2 ]);
391
+ } else {
392
+ $ query ->addOrderBy ($ orderByPart );
393
+ }
394
+ }
395
+ }
396
+ }
436
397
}
0 commit comments