1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Base daft objects. |
4
|
|
|
* |
5
|
|
|
* @author SignpostMarv |
6
|
|
|
*/ |
7
|
|
|
declare(strict_types=1); |
8
|
|
|
|
9
|
|
|
namespace SignpostMarv\DaftObject; |
10
|
|
|
|
11
|
|
|
use ParagonIE\EasyDB\EasyDB; |
12
|
|
|
use RuntimeException; |
13
|
|
|
use Throwable; |
14
|
|
|
|
15
|
|
|
abstract class AbstractDaftObjectEasyDBRepository extends DaftObjectMemoryRepository |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* @var EasyDB |
19
|
|
|
*/ |
20
|
|
|
protected $db; |
21
|
|
|
|
22
|
6 |
|
protected function __construct(string $type, EasyDB $db) |
23
|
|
|
{ |
24
|
6 |
|
parent::__construct($type); |
25
|
6 |
|
$this->db = $db; |
26
|
6 |
|
} |
27
|
|
|
|
28
|
6 |
|
public static function DaftObjectRepositoryByType( |
29
|
|
|
string $type, |
30
|
|
|
? EasyDB $db = null |
31
|
|
|
) : DaftObjectRepository { |
32
|
6 |
|
if (class_exists($type) === false) { |
33
|
1 |
|
throw new DaftObjectRepositoryTypeException( |
34
|
|
|
'Argument 1 passed to ' . |
|
|
|
|
35
|
1 |
|
static::class . |
36
|
|
|
'::' . |
37
|
|
|
__FUNCTION__ . |
38
|
|
|
'() must be an implementation of ' . |
39
|
|
|
DefinesOwnIdPropertiesInterface::class . |
40
|
1 |
|
', ' . |
41
|
1 |
|
$type . |
42
|
1 |
|
' given.' |
43
|
|
|
); |
44
|
6 |
|
} elseif (($db instanceof EasyDB) === false) { |
45
|
|
|
throw new RuntimeException('Database connection not specified!'); |
46
|
|
|
} |
47
|
|
|
|
48
|
6 |
|
return new static($type, $db); |
49
|
|
|
} |
50
|
|
|
|
51
|
5 |
|
public static function DaftObjectRepositoryByDaftObject( |
52
|
|
|
DefinesOwnIdPropertiesInterface $object, |
53
|
|
|
? EasyDB $db = null |
54
|
|
|
) : DaftObjectRepository { |
55
|
5 |
|
if (($db instanceof EasyDB) === true) { |
56
|
5 |
|
return static::DaftObjectRepositoryByType(get_class($object), $db); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
throw new RuntimeException('Database connection not specified!'); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @param mixed $id |
64
|
|
|
*/ |
65
|
2 |
|
public function RemoveDaftObjectById($id) : void |
66
|
|
|
{ |
67
|
2 |
|
$id = array_values(is_array($id) ? $id : [$id]); |
68
|
2 |
|
$type = $this->type; |
69
|
2 |
|
$idkv = []; |
70
|
|
|
|
71
|
|
|
foreach ( |
72
|
2 |
|
array_values($type::DaftObjectIdProperties()) as $i => $prop |
73
|
|
|
) { |
74
|
2 |
|
$idkv[$prop] = $id[$i]; |
75
|
|
|
} |
76
|
|
|
|
77
|
2 |
|
$where = []; |
78
|
2 |
|
foreach (array_keys($idkv) as $col) { |
79
|
2 |
|
$where[] = $this->db->escapeIdentifier($col) . ' = ?'; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
$query = ( |
83
|
|
|
'DELETE FROM ' . |
84
|
2 |
|
$this->db->escapeIdentifier( |
85
|
2 |
|
$this->DaftObjectDatabaseTable() |
86
|
|
|
) . |
87
|
2 |
|
' WHERE ' . |
88
|
2 |
|
implode(' AND ', $where) |
89
|
|
|
); |
90
|
|
|
|
91
|
2 |
|
$this->db->safeQuery($query, array_values($idkv)); |
92
|
|
|
|
93
|
2 |
|
$this->ForgetDaftObjectById($id); |
94
|
2 |
|
} |
95
|
|
|
|
96
|
|
|
abstract protected function DaftObjectDatabaseTable() : string; |
97
|
|
|
|
98
|
2 |
|
protected function RememberDaftObjectData( |
99
|
|
|
DefinesOwnIdPropertiesInterface $object |
100
|
|
|
) : void { |
101
|
2 |
|
$id = []; |
102
|
|
|
|
103
|
2 |
|
foreach ($object::DaftObjectIdProperties() as $prop) { |
104
|
2 |
|
$id[$prop] = $object->$prop; |
105
|
|
|
} |
106
|
|
|
|
107
|
2 |
|
$autoStartTransaction = ($this->db->inTransaction() === false); |
108
|
|
|
|
109
|
2 |
|
if ($autoStartTransaction === true) { |
110
|
2 |
|
$this->db->beginTransaction(); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
try { |
114
|
2 |
|
$exists = $this->DaftObjectExistsInDatabase($id); |
115
|
2 |
|
if ($exists === false) { |
116
|
2 |
|
$cols = []; |
117
|
2 |
|
$values = []; |
118
|
|
|
|
119
|
2 |
|
foreach ($object::DaftObjectProperties() as $col) { |
120
|
|
|
if ( |
121
|
2 |
|
method_exists( |
122
|
2 |
|
$object, |
123
|
2 |
|
'Get' . ucfirst($col) |
124
|
2 |
|
) === false |
125
|
|
|
) { |
126
|
2 |
|
continue; |
127
|
|
|
} |
128
|
2 |
|
$cols[] = $this->db->escapeIdentifier($col); |
129
|
2 |
|
$values[] = $object->$col; |
130
|
|
|
} |
131
|
|
|
|
132
|
2 |
|
$this->db->safeQuery( |
133
|
|
|
( |
134
|
|
|
'INSERT INTO ' . |
135
|
2 |
|
$this->db->escapeIdentifier( |
136
|
2 |
|
$this->DaftObjectDatabaseTable() |
137
|
|
|
) . |
138
|
2 |
|
' (' . |
139
|
2 |
|
implode(', ', $cols) . |
140
|
2 |
|
') VALUES (' . |
141
|
2 |
|
implode(', ', array_fill(0, count($cols), '?')) . |
142
|
2 |
|
')' |
143
|
|
|
), |
144
|
2 |
|
$values |
145
|
|
|
); |
146
|
|
|
} else { |
147
|
2 |
|
$changed = $object->ChangedProperties(); |
148
|
2 |
|
if (count($changed) > 0) { |
149
|
2 |
|
$cols = []; |
150
|
2 |
|
$values = []; |
151
|
|
|
|
152
|
2 |
|
foreach ($changed as $col) { |
153
|
2 |
|
$values[] = $object->$col; |
154
|
2 |
|
$cols[] = |
155
|
2 |
|
$this->db->escapeIdentifier($col) . |
156
|
2 |
|
' = ?'; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
$query = |
160
|
|
|
'UPDATE ' . |
161
|
2 |
|
$this->db->escapeIdentifier( |
162
|
2 |
|
$this->DaftObjectDatabaseTable() |
163
|
|
|
) . |
164
|
2 |
|
' SET ' . |
165
|
2 |
|
implode(', ', $cols); |
166
|
|
|
|
167
|
2 |
|
$cols = []; |
168
|
|
|
|
169
|
2 |
|
foreach ($id as $col => $value) { |
170
|
2 |
|
$values[] = $value; |
171
|
2 |
|
$cols[] = |
172
|
2 |
|
$this->db->escapeIdentifier($col) . |
173
|
2 |
|
' = ?'; |
174
|
|
|
} |
175
|
|
|
|
176
|
2 |
|
$query .= ' WHERE ' . implode(' AND ', $cols); |
177
|
|
|
|
178
|
2 |
|
$this->db->safeQuery($query, $values); |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
2 |
|
if ($autoStartTransaction === true) { |
183
|
2 |
|
$this->db->commit(); |
184
|
|
|
} |
185
|
|
|
} catch (Throwable $e) { |
186
|
|
|
if ($autoStartTransaction === true) { |
187
|
|
|
$this->db->rollBack(); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
throw $e; |
191
|
|
|
} |
192
|
2 |
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param mixed $id |
196
|
|
|
*/ |
197
|
2 |
|
protected function RecallDaftObjectFromData($id) : ? DaftObject |
198
|
|
|
{ |
199
|
2 |
|
$type = $this->type; |
200
|
2 |
|
$idkv = []; |
201
|
|
|
|
202
|
|
|
foreach ( |
203
|
2 |
|
array_values($type::DaftObjectIdProperties()) as $i => $prop |
204
|
|
|
) { |
205
|
2 |
|
$idkv[$prop] = $id[$i]; |
206
|
|
|
} |
207
|
|
|
|
208
|
2 |
|
if ($this->DaftObjectExistsInDatabase($idkv) === true) { |
209
|
2 |
|
$where = []; |
210
|
2 |
|
foreach (array_keys($idkv) as $col) { |
211
|
2 |
|
$where[] = $this->db->escapeIdentifier($col) . ' = ?'; |
212
|
|
|
} |
213
|
|
|
|
214
|
2 |
|
$data = $this->db->safeQuery( |
215
|
|
|
( |
216
|
|
|
'SELECT * FROM ' . |
217
|
2 |
|
$this->db->escapeIdentifier( |
218
|
2 |
|
$this->DaftObjectDatabaseTable() |
219
|
|
|
) . |
220
|
2 |
|
' WHERE ' . |
221
|
2 |
|
implode(' AND ', $where) . |
222
|
2 |
|
' LIMIT 1' |
223
|
|
|
), |
224
|
2 |
|
array_values($idkv) |
225
|
|
|
); |
226
|
|
|
|
227
|
2 |
|
return new $type($data[0]); |
228
|
|
|
} |
229
|
|
|
|
230
|
2 |
|
return null; |
231
|
|
|
} |
232
|
|
|
|
233
|
2 |
|
private function DaftObjectExistsInDatabase(array $id) : bool |
234
|
|
|
{ |
235
|
2 |
|
$where = []; |
236
|
2 |
|
foreach (array_keys($id) as $col) { |
237
|
2 |
|
$where[] = $this->db->escapeIdentifier($col) . ' = ?'; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
return |
241
|
2 |
|
(int) $this->db->single( |
242
|
|
|
( |
243
|
|
|
'SELECT COUNT(*) FROM ' . |
244
|
2 |
|
$this->db->escapeIdentifier( |
245
|
2 |
|
$this->DaftObjectDatabaseTable() |
246
|
|
|
) . |
247
|
2 |
|
' WHERE ' . |
248
|
2 |
|
implode(' AND ', $where) |
249
|
|
|
), |
250
|
2 |
|
array_values($id) |
251
|
2 |
|
) >= 1; |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.