2
2
3
3
namespace Composite \DB ;
4
4
5
+ use Composite \DB \Exceptions \DbException ;
5
6
use Composite \DB \MultiQuery \MultiInsert ;
6
7
use Composite \DB \MultiQuery \MultiSelect ;
7
- use Composite \Entity \Helpers \DateTimeHelper ;
8
8
use Composite \Entity \AbstractEntity ;
9
- use Composite \DB \ Exceptions \ DbException ;
9
+ use Composite \Entity \ Helpers \ DateTimeHelper ;
10
10
use Doctrine \DBAL \Connection ;
11
- use Doctrine \DBAL \Platforms \PostgreSQLPlatform ;
12
11
use Ramsey \Uuid \UuidInterface ;
13
12
14
13
abstract class AbstractTable
15
14
{
16
- use SelectRawTrait;
15
+ use Helpers \SelectRawTrait;
16
+ use Helpers \DatabaseSpecificTrait;
17
17
18
18
protected readonly TableConfig $ config ;
19
19
20
+
20
21
abstract protected function getConfig (): TableConfig ;
21
22
22
23
public function __construct ()
@@ -44,49 +45,51 @@ public function getConnectionName(): string
44
45
* @return void
45
46
* @throws \Throwable
46
47
*/
47
- public function save (AbstractEntity & $ entity ): void
48
+ public function save (AbstractEntity $ entity ): void
48
49
{
49
50
$ this ->config ->checkEntity ($ entity );
50
51
if ($ entity ->isNew ()) {
51
52
$ connection = $ this ->getConnection ();
52
53
$ this ->checkUpdatedAt ($ entity );
53
54
54
- $ insertData = $ this ->formatData ($ entity ->toArray ());
55
+ $ insertData = $ this ->prepareDataForSql ($ entity ->toArray ());
55
56
$ this ->getConnection ()->insert ($ this ->getTableName (), $ insertData );
56
57
57
- if ($ this ->config ->autoIncrementKey ) {
58
- $ insertData [$ this ->config ->autoIncrementKey ] = intval ($ connection -> lastInsertId () );
59
- $ entity = $ entity :: fromArray ( $ insertData );
60
- } else {
61
- $ entity-> resetChangedColumns ( );
58
+ if ($ this ->config ->autoIncrementKey && ( $ lastInsertedId = $ connection -> lastInsertId ()) ) {
59
+ $ insertData [$ this ->config ->autoIncrementKey ] = intval ($ lastInsertedId );
60
+ $ entity:: schema ()
61
+ -> getColumn ( $ this -> config -> autoIncrementKey )
62
+ -> setValue ( $ entity, $ insertData [ $ this -> config -> autoIncrementKey ] );
62
63
}
64
+ $ entity ->resetChangedColumns ($ insertData );
63
65
} else {
64
66
if (!$ changedColumns = $ entity ->getChangedColumns ()) {
65
67
return ;
66
68
}
67
- $ connection = $ this ->getConnection ();
68
- $ where = $ this ->getPkCondition ($ entity );
69
-
69
+ $ changedColumns = $ this ->prepareDataForSql ($ changedColumns );
70
70
if ($ this ->config ->hasUpdatedAt () && property_exists ($ entity , 'updated_at ' )) {
71
71
$ entity ->updated_at = new \DateTimeImmutable ();
72
72
$ changedColumns ['updated_at ' ] = DateTimeHelper::dateTimeToString ($ entity ->updated_at );
73
73
}
74
+ $ whereParams = $ this ->getPkCondition ($ entity );
74
75
if ($ this ->config ->hasOptimisticLock ()
75
76
&& method_exists ($ entity , 'getVersion ' )
76
77
&& method_exists ($ entity , 'incrementVersion ' )) {
77
- $ where ['lock_version ' ] = $ entity ->getVersion ();
78
+ $ whereParams ['lock_version ' ] = $ entity ->getVersion ();
78
79
$ entity ->incrementVersion ();
79
80
$ changedColumns ['lock_version ' ] = $ entity ->getVersion ();
80
81
}
81
- $ entityUpdated = $ connection ->update (
82
- table: $ this ->getTableName (),
83
- data: $ changedColumns ,
84
- criteria: $ where ,
82
+ $ updateString = implode (', ' , array_map (fn ($ key ) => $ this ->escapeIdentifier ($ key ) . "=? " , array_keys ($ changedColumns )));
83
+ $ whereString = implode (' AND ' , array_map (fn ($ key ) => $ this ->escapeIdentifier ($ key ) . "=? " , array_keys ($ whereParams )));
84
+
85
+ $ entityUpdated = (bool )$ this ->getConnection ()->executeStatement (
86
+ sql: "UPDATE " . $ this ->escapeIdentifier ($ this ->getTableName ()) . " SET $ updateString WHERE $ whereString; " ,
87
+ params: array_merge (array_values ($ changedColumns ), array_values ($ whereParams )),
85
88
);
86
89
if ($ this ->config ->hasOptimisticLock () && !$ entityUpdated ) {
87
90
throw new Exceptions \LockException ('Failed to update entity version, concurrency modification, rolling back. ' );
88
91
}
89
- $ entity ->resetChangedColumns ();
92
+ $ entity ->resetChangedColumns ($ changedColumns );
90
93
}
91
94
}
92
95
@@ -101,7 +104,7 @@ public function saveMany(array $entities): void
101
104
if ($ entity ->isNew ()) {
102
105
$ this ->config ->checkEntity ($ entity );
103
106
$ this ->checkUpdatedAt ($ entity );
104
- $ rowsToInsert [] = $ this ->formatData ($ entity ->toArray ());
107
+ $ rowsToInsert [] = $ this ->prepareDataForSql ($ entity ->toArray ());
105
108
unset($ entities [$ i ]);
106
109
}
107
110
}
@@ -113,14 +116,15 @@ public function saveMany(array $entities): void
113
116
}
114
117
if ($ rowsToInsert ) {
115
118
$ chunks = array_chunk ($ rowsToInsert , 1000 );
119
+ $ connection = $ this ->getConnection ();
116
120
foreach ($ chunks as $ chunk ) {
117
121
$ multiInsert = new MultiInsert (
122
+ connection: $ connection ,
118
123
tableName: $ this ->getTableName (),
119
124
rows: $ chunk ,
120
125
);
121
126
if ($ multiInsert ->getSql ()) {
122
- $ stmt = $ this ->getConnection ()->prepare ($ multiInsert ->getSql ());
123
- $ stmt ->executeQuery ($ multiInsert ->getParameters ());
127
+ $ connection ->executeStatement ($ multiInsert ->getSql (), $ multiInsert ->getParameters ());
124
128
}
125
129
}
126
130
}
@@ -135,7 +139,7 @@ public function saveMany(array $entities): void
135
139
* @param AbstractEntity $entity
136
140
* @throws \Throwable
137
141
*/
138
- public function delete (AbstractEntity & $ entity ): void
142
+ public function delete (AbstractEntity $ entity ): void
139
143
{
140
144
$ this ->config ->checkEntity ($ entity );
141
145
if ($ this ->config ->hasSoftDelete ()) {
@@ -144,8 +148,12 @@ public function delete(AbstractEntity &$entity): void
144
148
$ this ->save ($ entity );
145
149
}
146
150
} else {
147
- $ where = $ this ->getPkCondition ($ entity );
148
- $ this ->getConnection ()->delete ($ this ->getTableName (), $ where );
151
+ $ whereParams = $ this ->getPkCondition ($ entity );
152
+ $ whereString = implode (' AND ' , array_map (fn ($ key ) => $ this ->escapeIdentifier ($ key ) . "=? " , array_keys ($ whereParams )));
153
+ $ this ->getConnection ()->executeQuery (
154
+ sql: "DELETE FROM " . $ this ->escapeIdentifier ($ this ->getTableName ()) . " WHERE $ whereString; " ,
155
+ params: array_values ($ whereParams ),
156
+ );
149
157
}
150
158
}
151
159
@@ -192,8 +200,15 @@ protected function _countAll(array|Where $where = []): int
192
200
*/
193
201
protected function _findByPk (mixed $ pk ): mixed
194
202
{
195
- $ where = $ this ->getPkCondition ($ pk );
196
- return $ this ->_findOne ($ where );
203
+ $ whereParams = $ this ->getPkCondition ($ pk );
204
+ $ whereString = implode (' AND ' , array_map (fn ($ key ) => $ this ->escapeIdentifier ($ key ) . "=? " , array_keys ($ whereParams )));
205
+ $ row = $ this ->getConnection ()
206
+ ->executeQuery (
207
+ sql: "SELECT * FROM " . $ this ->escapeIdentifier ($ this ->getTableName ()) . " WHERE $ whereString; " ,
208
+ params: array_values ($ whereParams ),
209
+ )
210
+ ->fetchAssociative ();
211
+ return $ this ->createEntity ($ row );
197
212
}
198
213
199
214
/**
@@ -304,7 +319,14 @@ protected function getPkCondition(int|string|array|AbstractEntity|UuidInterface
304
319
{
305
320
$ condition = [];
306
321
if ($ data instanceof AbstractEntity) {
307
- $ data = $ data ->toArray ();
322
+ if ($ data ->isNew ()) {
323
+ $ data = $ data ->toArray ();
324
+ } else {
325
+ foreach ($ this ->config ->primaryKeys as $ key ) {
326
+ $ condition [$ key ] = $ data ->getOldValue ($ key );
327
+ }
328
+ return $ condition ;
329
+ }
308
330
}
309
331
if (is_array ($ data )) {
310
332
foreach ($ this ->config ->primaryKeys as $ key ) {
@@ -324,20 +346,4 @@ private function checkUpdatedAt(AbstractEntity $entity): void
324
346
$ entity ->updated_at = new \DateTimeImmutable ();
325
347
}
326
348
}
327
-
328
- /**
329
- * @param array<string, mixed> $data
330
- * @return array<string, mixed>
331
- * @throws \Doctrine\DBAL\Exception
332
- */
333
- private function formatData (array $ data ): array
334
- {
335
- $ supportsBoolean = $ this ->getConnection ()->getDatabasePlatform () instanceof PostgreSQLPlatform;
336
- foreach ($ data as $ columnName => $ value ) {
337
- if (is_bool ($ value ) && !$ supportsBoolean ) {
338
- $ data [$ columnName ] = $ value ? 1 : 0 ;
339
- }
340
- }
341
- return $ data ;
342
- }
343
349
}
0 commit comments