Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
50.00% |
73 / 146 |
|
28.57% |
4 / 14 |
CRAP | |
0.00% |
0 / 1 |
DataMapperAbstract | |
50.00% |
73 / 146 |
|
28.57% |
4 / 14 |
510.00 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
query | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
with | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
sort | |
68.75% |
11 / 16 |
|
0.00% |
0 / 1 |
5.76 | |||
offset | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
limit | |
68.75% |
11 / 16 |
|
0.00% |
0 / 1 |
5.76 | |||
where | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
join | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
on | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
leftJoin | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
rightJoin | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
innerJoin | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createRelationMapper | |
40.54% |
15 / 37 |
|
0.00% |
0 / 1 |
123.74 | |||
parseValue | |
80.95% |
17 / 21 |
|
0.00% |
0 / 1 |
15.35 | |||
execute | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\DataStorage\Database\Mapper |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\DataStorage\Database\Mapper; |
16 | |
17 | use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; |
18 | use phpOMS\DataStorage\Database\Query\Builder; |
19 | use phpOMS\DataStorage\Database\Query\JoinType; |
20 | use phpOMS\DataStorage\Database\Query\OrderType; |
21 | |
22 | /** |
23 | * Mapper abstract. |
24 | * |
25 | * @package phpOMS\DataStorage\Database\Mapper |
26 | * @license OMS License 2.0 |
27 | * @link https://jingga.app |
28 | * @since 1.0.0 |
29 | */ |
30 | abstract class DataMapperAbstract |
31 | { |
32 | /** |
33 | * Base mapper |
34 | * |
35 | * @var DataMapperFactory |
36 | * @since 1.0.0 |
37 | */ |
38 | protected DataMapperFactory $mapper; |
39 | |
40 | /** |
41 | * Mapper type (e.g. writer, reader, ...) |
42 | * |
43 | * @var int |
44 | * @since 1.0.0 |
45 | */ |
46 | protected int $type = 0; |
47 | |
48 | /** |
49 | * Mapper depths. |
50 | * |
51 | * Mappers may have relations to other models (e.g. hasMany) which can have other relations, ... |
52 | * The depths indicates how deep in the relation tree we are |
53 | * |
54 | * @var int |
55 | * @since 1.0.0 |
56 | */ |
57 | protected int $depth = 1; |
58 | |
59 | /** |
60 | * Relations which should be loaded |
61 | * |
62 | * @var array |
63 | * @since 1.0.0 |
64 | */ |
65 | protected array $with = []; |
66 | |
67 | /** |
68 | * Sort order |
69 | * |
70 | * @var array |
71 | * @since 1.0.0 |
72 | */ |
73 | protected array $sort = []; |
74 | |
75 | /** |
76 | * Offset |
77 | * |
78 | * @var array |
79 | * @since 1.0.0 |
80 | */ |
81 | protected array $offset = []; |
82 | |
83 | /** |
84 | * Limit |
85 | * |
86 | * @var array |
87 | * @since 1.0.0 |
88 | */ |
89 | protected array $limit = []; |
90 | |
91 | /** |
92 | * Where conditions |
93 | * |
94 | * @var array |
95 | * @since 1.0.0 |
96 | */ |
97 | protected array $where = []; |
98 | |
99 | /** |
100 | * Join conditions |
101 | * |
102 | * @var array |
103 | * @since 1.0.0 |
104 | */ |
105 | protected array $join = []; |
106 | |
107 | /** |
108 | * Join conditions |
109 | * |
110 | * @var array |
111 | * @since 1.0.0 |
112 | */ |
113 | protected array $on = []; |
114 | |
115 | /** |
116 | * Base query which is merged with the query in the mapper |
117 | * |
118 | * Sometimes you want to merge two queries together. |
119 | * |
120 | * @var null|Builder |
121 | * @since 1.0.0 |
122 | */ |
123 | protected ?Builder $query = null; |
124 | |
125 | /** |
126 | * Database connection. |
127 | * |
128 | * @var ConnectionAbstract |
129 | * @since 1.0.0 |
130 | */ |
131 | protected ConnectionAbstract $db; |
132 | |
133 | /** Constructor. |
134 | * |
135 | * @param DataMapperFactory $mapper Base mapper |
136 | * @param ConnectionAbstract $db Database connection |
137 | * |
138 | * @since 1.0.0 |
139 | */ |
140 | public function __construct(DataMapperFactory $mapper, ConnectionAbstract $db) |
141 | { |
142 | $this->mapper = $mapper; |
143 | $this->db = $db; |
144 | } |
145 | |
146 | /** |
147 | * Define a query which is merged with the internal query generation. |
148 | * |
149 | * @param Builder $query Query |
150 | * |
151 | * @return static |
152 | * |
153 | * @since 1.0.0 |
154 | */ |
155 | public function query(Builder $query = null) : self |
156 | { |
157 | $this->query = $query; |
158 | |
159 | return $this; |
160 | } |
161 | |
162 | /** |
163 | * Define model relations which should be loaded |
164 | * |
165 | * @param string $member Property name of the relation (e.g. hasMany, belongsTo, ...) |
166 | * |
167 | * @return static |
168 | * |
169 | * @since 1.0.0 |
170 | */ |
171 | public function with(string $member) : self |
172 | { |
173 | $split = \explode('/', $member); |
174 | $memberSplit = \array_shift($split); |
175 | |
176 | $this->with[$memberSplit][] = [ |
177 | 'child' => \implode('/', $split), |
178 | ]; |
179 | |
180 | return $this; |
181 | } |
182 | |
183 | /** |
184 | * Sort order |
185 | * |
186 | * @param string $member Property name to sort by |
187 | * @param string $order Order type (DESC/ASC) |
188 | * |
189 | * @return static |
190 | * |
191 | * @since 1.0.0 |
192 | */ |
193 | public function sort(string $member, string $order = OrderType::DESC) : self |
194 | { |
195 | $split = \explode('/', $member); |
196 | $memberSplit = \array_shift($split); |
197 | |
198 | $child = \implode('/', $split); |
199 | $overwritten = false; |
200 | |
201 | if (isset($this->sort[$memberSplit])) { |
202 | foreach ($this->sort[$memberSplit] as $key => $element) { |
203 | if ($element['child'] === $child) { |
204 | $this->sort[$memberSplit][$key]['order'] = $order; |
205 | $overwritten = true; |
206 | |
207 | break; |
208 | } |
209 | } |
210 | } |
211 | |
212 | if (!$overwritten) { |
213 | $this->sort[$memberSplit][] = [ |
214 | 'child' => \implode('/', $split), |
215 | 'order' => $order, |
216 | ]; |
217 | } |
218 | |
219 | return $this; |
220 | } |
221 | |
222 | /** |
223 | * Define the result offset |
224 | * |
225 | * @param int $offset Offset |
226 | * @param string $member Property name to offset by ('' = base model, anything else for relations such as hasMany relations) |
227 | * |
228 | * @return static |
229 | * |
230 | * @since 1.0.0 |
231 | */ |
232 | public function offset(int $offset = 0, string $member = '') : self |
233 | { |
234 | $split = \explode('/', $member); |
235 | $memberSplit = \array_shift($split); |
236 | |
237 | $child = \implode('/', $split); |
238 | $overwritten = false; |
239 | |
240 | if (isset($this->offset[$memberSplit])) { |
241 | foreach ($this->offset[$memberSplit] as $key => $element) { |
242 | if ($element['child'] === $child) { |
243 | $this->offset[$memberSplit][$key]['offset'] = $offset; |
244 | $overwritten = true; |
245 | |
246 | break; |
247 | } |
248 | } |
249 | } |
250 | |
251 | if (!$overwritten) { |
252 | $this->offset[$memberSplit][] = [ |
253 | 'child' => \implode('/', $split), |
254 | 'offset' => $offset, |
255 | ]; |
256 | } |
257 | |
258 | return $this; |
259 | } |
260 | |
261 | /** |
262 | * Define the result limit |
263 | * |
264 | * @param int $limit Limit |
265 | * @param string $member Property name to limit by ('' = base model, anything else for relations such as hasMany relations) |
266 | * |
267 | * @return static |
268 | * |
269 | * @since 1.0.0 |
270 | */ |
271 | public function limit(int $limit = 0, string $member = '') : self |
272 | { |
273 | $split = \explode('/', $member); |
274 | $memberSplit = \array_shift($split); |
275 | |
276 | $child = \implode('/', $split); |
277 | $overwritten = false; |
278 | |
279 | if (isset($this->limit[$memberSplit])) { |
280 | foreach ($this->limit[$memberSplit] as $key => $element) { |
281 | if ($element['child'] === $child) { |
282 | $this->limit[$memberSplit][$key]['limit'] = $limit; |
283 | $overwritten = true; |
284 | |
285 | break; |
286 | } |
287 | } |
288 | } |
289 | |
290 | if (!$overwritten) { |
291 | $this->limit[$memberSplit][] = [ |
292 | 'child' => \implode('/', $split), |
293 | 'limit' => $limit, |
294 | ]; |
295 | } |
296 | |
297 | return $this; |
298 | } |
299 | |
300 | /** |
301 | * Define the result filtering |
302 | * |
303 | * @param string $member Property name to filter by |
304 | * @param mixed $value Filter value |
305 | * @param string $logic Comparison logic (e.g. =, in, ...) |
306 | * @param string $connector Filter connector (e.g. AND, OR, ...) |
307 | * |
308 | * @return static |
309 | * |
310 | * @since 1.0.0 |
311 | */ |
312 | public function where(string $member, mixed $value, string $logic = '=', string $connector = 'AND') : self |
313 | { |
314 | $split = \explode('/', $member); |
315 | $memberSplit = \array_shift($split); |
316 | |
317 | $this->where[$memberSplit][] = [ |
318 | 'child' => \implode('/', $split), |
319 | 'value' => $value, |
320 | 'logic' => $logic, |
321 | 'comparison' => $connector, |
322 | ]; |
323 | |
324 | return $this; |
325 | } |
326 | |
327 | /** |
328 | * Define the joining data |
329 | * |
330 | * @param string $member Property name to filter by |
331 | * @param string $mapper Mapper |
332 | * @param mixed $value Filter value |
333 | * @param string $logic Comparison logic (e.g. =, in, ...) |
334 | * @param string $type Join type (e.g. left, right, inner) |
335 | * |
336 | * @return static |
337 | * |
338 | * @since 1.0.0 |
339 | */ |
340 | public function join(string $member, string $mapper, mixed $value, string $logic = '=', string $type = JoinType::LEFT_JOIN) : self |
341 | { |
342 | $split = \explode('/', $member); |
343 | $memberSplit = \array_shift($split); |
344 | |
345 | $this->join[$memberSplit][] = [ |
346 | 'child' => \implode('/', $split), |
347 | 'mapper' => $mapper, |
348 | 'value' => $value, |
349 | 'logic' => $logic, |
350 | 'type' => $type, |
351 | ]; |
352 | |
353 | return $this; |
354 | } |
355 | |
356 | /** |
357 | * Define the joining data |
358 | * |
359 | * @param string $member Property name to filter by |
360 | * @param mixed $value Filter value |
361 | * @param string $logic Comparison logic (e.g. =, in, ...) |
362 | * @param string $connector Filter connector (e.g. AND, OR, ...) |
363 | * |
364 | * @return self |
365 | * |
366 | * @since 1.0.0 |
367 | */ |
368 | public function on(string $member, mixed $value, string $logic = '=', string $connector = 'AND', string $relation = '') : self |
369 | { |
370 | $this->on[$relation][] = [ |
371 | 'child' => '', |
372 | 'member' => $member, |
373 | 'value' => $value, |
374 | 'logic' => $logic, |
375 | 'comparison' => $connector, |
376 | ]; |
377 | |
378 | return $this; |
379 | } |
380 | |
381 | /** |
382 | * Define the joining data |
383 | * |
384 | * @param string $member Property name to filter by |
385 | * @param string $mapper Mapper |
386 | * @param mixed $value Filter value |
387 | * @param string $logic Comparison logic (e.g. =, in, ...) |
388 | * |
389 | * @return static |
390 | * |
391 | * @since 1.0.0 |
392 | */ |
393 | public function leftJoin(string $member, string $mapper, mixed $value, string $logic = '=') : self |
394 | { |
395 | return $this->join($member, $mapper, $value, $logic, JoinType::LEFT_JOIN); |
396 | } |
397 | |
398 | /** |
399 | * Define the joining data |
400 | * |
401 | * @param string $member Property name to filter by |
402 | * @param string $mapper Mapper |
403 | * @param mixed $value Filter value |
404 | * @param string $logic Comparison logic (e.g. =, in, ...) |
405 | * |
406 | * @return static |
407 | * |
408 | * @since 1.0.0 |
409 | */ |
410 | public function rightJoin(string $member, string $mapper, mixed $value, string $logic = '=') : self |
411 | { |
412 | return $this->join($member, $mapper, $value, $logic, JoinType::RIGHT_JOIN); |
413 | } |
414 | |
415 | /** |
416 | * Define the joining data |
417 | * |
418 | * @param string $member Property name to filter by |
419 | * @param string $mapper Mapper |
420 | * @param mixed $value Filter value |
421 | * @param string $logic Comparison logic (e.g. =, in, ...) |
422 | * |
423 | * @return static |
424 | * |
425 | * @since 1.0.0 |
426 | */ |
427 | public function innerJoin(string $member, string $mapper, mixed $value, string $logic = '=') : self |
428 | { |
429 | return $this->join($member, $mapper, $value, $logic, JoinType::INNER_JOIN); |
430 | } |
431 | |
432 | /** |
433 | * Populate a mapper (e.g. child mapper, relation mapper) based on the current mapper information. |
434 | * |
435 | * @param DataMapperAbstract $mapper Relation mapper to populate |
436 | * @param string $member Relation property (e.g. ownsOne, hasMany, ... property name) |
437 | * |
438 | * @return self |
439 | * |
440 | * @since 1.0.0 |
441 | */ |
442 | public function createRelationMapper(self $mapper, string $member) : self |
443 | { |
444 | $relMapper = $mapper; |
445 | |
446 | if (isset($this->with[$member])) { |
447 | foreach ($this->with[$member] as $with) { |
448 | if ($with['child'] === '') { |
449 | continue; |
450 | } |
451 | |
452 | $relMapper->with($with['child']); |
453 | } |
454 | } |
455 | |
456 | if (isset($this->sort[$member])) { |
457 | foreach ($this->sort[$member] as $sort) { |
458 | // member = child element in this case |
459 | if ($member === '') { |
460 | continue; |
461 | } |
462 | |
463 | $relMapper->sort($sort['child'], $sort['order']); |
464 | } |
465 | } |
466 | |
467 | if (isset($this->offset[$member])) { |
468 | foreach ($this->offset[$member] as $offset) { |
469 | if ($offset['child'] === '') { |
470 | continue; |
471 | } |
472 | |
473 | $relMapper->offset($offset['offset'], $offset['child']); |
474 | } |
475 | } |
476 | |
477 | if (isset($this->limit[$member])) { |
478 | foreach ($this->limit[$member] as $limit) { |
479 | // member = child element in this case |
480 | if ($member === '') { |
481 | continue; |
482 | } |
483 | |
484 | $relMapper->limit($limit['limit'], $limit['child']); |
485 | } |
486 | } |
487 | |
488 | if (isset($this->where[$member])) { |
489 | foreach ($this->where[$member] as $where) { |
490 | if ($where['child'] === '') { |
491 | continue; |
492 | } |
493 | |
494 | $relMapper->where($where['child'], $where['value'], $where['logic'], $where['comparison']); |
495 | } |
496 | } |
497 | |
498 | if (isset($this->join[$member])) { |
499 | foreach ($this->join[$member] as $join) { |
500 | if ($join['child'] === '') { |
501 | continue; |
502 | } |
503 | |
504 | $relMapper->join($join['child'], $join['mapper'], $join['value'], $join['logic'], $join['type']); |
505 | } |
506 | } |
507 | |
508 | if (isset($this->on[$member])) { |
509 | foreach ($this->on[$member] as $on) { |
510 | if ($on['child'] === '') { |
511 | continue; |
512 | } |
513 | |
514 | $relMapper->on($on['child'], $on['value'], $on['logic'], $on['comparison'], $on['field']); |
515 | } |
516 | } |
517 | |
518 | return $relMapper; |
519 | } |
520 | |
521 | /** |
522 | * Parse value |
523 | * |
524 | * @param string $type Value type |
525 | * @param mixed $value Value to parse |
526 | * |
527 | * @return mixed |
528 | * |
529 | * @since 1.0.0 |
530 | */ |
531 | public function parseValue(string $type, mixed $value = null) : mixed |
532 | { |
533 | if ($value === null) { |
534 | return null; |
535 | } elseif ($type === 'int') { |
536 | return (int) $value; |
537 | } elseif ($type === 'string') { |
538 | return (string) $value; |
539 | } elseif ($type === 'float') { |
540 | return (float) $value; |
541 | } elseif ($type === 'bool') { |
542 | return (bool) $value; |
543 | } elseif ($type === 'DateTime' || $type === 'DateTimeImmutable') { |
544 | return $value === null ? null : $value->format($this->mapper::$datetimeFormat); |
545 | } elseif ($type === 'Json') { |
546 | return (string) \json_encode($value); |
547 | } elseif ($type === 'compress') { |
548 | return (string) \gzdeflate($value); |
549 | } elseif ($type === 'Serializable') { |
550 | return $value->serialize(); |
551 | } elseif (\is_object($value) && \method_exists($value, 'getId')) { |
552 | return $value->getId(); |
553 | } |
554 | |
555 | return $value; |
556 | } |
557 | |
558 | /** |
559 | * Execute mapper |
560 | * |
561 | * @param mixed ...$options Data for the mapper |
562 | * |
563 | * @return mixed |
564 | * |
565 | * @since 1.0.0 |
566 | */ |
567 | abstract public function execute(mixed ...$options) : mixed; |
568 | } |