Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 255
0.00% covered (danger)
0.00%
0 / 43
CRAP
0.00% covered (danger)
0.00%
0 / 1
Base
0.00% covered (danger)
0.00%
0 / 255
0.00% covered (danger)
0.00%
0 / 43
13572
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 entityFactory
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 factory
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setRawData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getRawData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReferenceMapping
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 setupPreFormatFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setupMapping
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 preSetup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 postSetup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 preSetupFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 postSetupFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setupFields
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getReferenceFields
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setupReferences
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
110
 __set
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 addReference
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getReference
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 __get
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 __isset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 __unset
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 offsetExists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 offsetGet
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 offsetSet
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 offsetUnset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 count
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 jsonSerialize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
90
 arrayAccessByDotPerpareKeys
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 save
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 saveEntitiy
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 postSave
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 saveReferences
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 delete
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 deleteEntity
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0
 deleteReferences
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
110
 deleteWith
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 clearEntity
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 clearEntityReferences
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 getTableName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BO\Zmsdldb\Importer\MySQL\Entity;
4
5use BO\Zmsdldb\PDOAccess;
6use BO\Zmsdldb\Importer\PDOTrait;
7use BO\Zmsdldb\Importer\ItemNeedsUpdateTrait;
8use BO\Zmsdldb\Importer\MySQL\Entity\Collection as EntityCollection
9;
10
11/**
12 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
13 * @SuppressWarnings(PHPMD.TooManyMethods)
14 * @SuppressWarnings(PHPMD.NumberOfChildren)
15 * @SuppressWarnings(PHPMD.CyclomaticComplexity)
16 * @SuppressWarnings(PHPMD.NPathComplexity)
17 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
18 * @implements \ArrayAccess<string, mixed>
19 */
20abstract class Base implements \Countable, \ArrayAccess, \JsonSerializable
21{
22    use ItemNeedsUpdateTrait;
23    use PDOTrait;
24
25    protected $fieldMapping = [];
26
27    protected $fields = [];
28
29    protected $referanceMapping = [];
30
31    protected $preFormatFields = [];
32
33    protected $references = [];
34
35    protected $dataRaw = [];
36
37    protected $setupFields = true;
38    protected $setupReferences = true;
39
40    protected $status = 1;
41
42    const STATUS_NEW = 1;
43    const STATUS_OLD = 0;
44
45    public function __construct(PDOAccess $mySqlAccess, array $dataRaw = [], bool $setup = true)
46    {
47        try {
48            $this->pdoAccess = $mySqlAccess;
49            $this->dataRaw = $dataRaw;
50
51            if (true === $setup) {
52                $this->setupMapping();
53
54                $this->preSetup();
55
56                $this->setupFields();
57
58                $this->setupReferences();
59
60                $this->postSetup();
61            }
62        } catch (\Exception $e) {
63            throw $e;
64        }
65    }
66
67    public static function entityFactory(
68        string $entityName,
69        PDOAccess $mySqlAccess,
70        array $dataRaw = [],
71        bool $setup = true
72    ) {
73        try {
74            $className = preg_replace_callback('/[_-]([a-z0-9]*)/i', function ($matches) {
75                return ucfirst($matches[1] ?? '');
76            }, $entityName);
77            $className = '\\BO\\Zmsdldb\\Importer\\MySQL\\Entity' . $className;
78
79            return new $className($mySqlAccess, $dataRaw, $setup);
80        } catch (\Exception $e) {
81            throw $e;
82        }
83    }
84
85    public function factory(string $entityName, array $dataRaw = [], bool $setup = true)
86    {
87        try {
88            return static::entityFactory($entityName, $this->getPDOAccess(), $dataRaw, $setup);
89        } catch (\Exception $e) {
90            throw $e;
91        }
92    }
93
94    public function setRawData(array $rawData = [])
95    {
96        $this->dataRaw = $rawData;
97        return $this;
98    }
99
100    public function getRawData(): array
101    {
102        return $this->dataRaw;
103    }
104
105    public function setStatus(int $status = Base::STATUS_NEW)
106    {
107        $this->status = $status;
108    }
109
110    public function getStatus(): int
111    {
112        return $this->status;
113    }
114
115    public function getReferenceMapping($setup = false): array
116    {
117        try {
118            if (true === $setup) {
119                $this->setupMapping();
120            }
121
122            return $this->referanceMapping;
123        } catch (\Exception $e) {
124            throw $e;
125        }
126    }
127
128    protected function setupPreFormatFields()
129    {
130    }
131
132    protected function setupMapping()
133    {
134    }
135
136    public function preSetup()
137    {
138    }
139
140    public function postSetup()
141    {
142    }
143
144    public function preSetupFields()
145    {
146    }
147
148    public function postSetupFields()
149    {
150    }
151
152    final public function setupFields(): bool
153    {
154        try {
155            if (false === $this->setupFields) {
156                return true;
157            }
158            $this->preSetupFields();
159
160            $values = $this->get(array_keys(array_filter($this->fieldMapping)));
161            foreach ($values as $key => $value) {
162                $this->__set($key, $value);
163            }
164            $this->postSetupFields();
165            $this->setupFields = false;
166            return true;
167        } catch (\Exception $e) {
168            throw $e;
169        }
170    }
171
172    protected function getReferenceFields()
173    {
174        $referenceFields = array_flip(array_keys(array_filter($this->referanceMapping)));
175
176        foreach (array_keys($referenceFields) as $name) {
177            $referenceFields[$name] = $this->get($name);
178        }
179        return $referenceFields;
180    }
181
182    final public function setupReferences()
183    {
184        try {
185            if (false === $this->setupReferences) {
186                return true;
187            }
188            $values = $this->getReferenceFields();
189
190            foreach ($values as $name => $references) {
191                $referenceEntityClass = $this->referanceMapping[$name]['class'];
192                $addFields = [];
193
194                foreach (($this->referanceMapping[$name]['neededFields'] ?? []) as $sourceKey => $destinationKey) {
195                    $addFields[$destinationKey] = $this->get($sourceKey);
196                }
197                $isMultiple = $this->referanceMapping[$name]['multiple'] ?? true;
198
199                if (false === $isMultiple) {
200                    $references = [$references];
201                }
202
203                $position = 0;
204                foreach (($references ?? []) as $reference) {
205                    foreach ($this->referanceMapping[$name]['addFields'] as $key => $value) {
206                        if (is_callable($value)) {
207                            $addFields[$key] = call_user_func_array($value, [$position, $name, $reference]);
208                        } else {
209                            $addFields[$key] = $value;
210                        }
211                    }
212                    if (true === ($this->referanceMapping[$name]['selfAsArray'] ?? false)) {
213                        $reference = [
214                            $name => $reference
215                        ];
216                    }
217                    $referencesInstance = new $referenceEntityClass(
218                        $this->getPDOAccess(),
219                        array_merge(
220                            $reference,
221                            $addFields
222                        )
223                    );
224
225                    $this->addReference($name, $referencesInstance);
226                    $position++;
227                }
228            }
229            $this->setupReferences = false;
230            return true;
231        } catch (\Exception $e) {
232            throw $e;
233        }
234    }
235
236    final public function __set($name, $value)
237    {
238        if (array_key_exists($name, $this->fieldMapping)) {
239            $name = $this->fieldMapping[$name];
240            if (is_bool($value)) {
241                $value = (int)$value;
242            } elseif (stripos($name, '_json')) {
243                $value = json_encode($value);
244            }
245            $this->fields[$name] = $value;
246        } elseif (array_key_exists($name, $this->referanceMapping)) {
247            $this->addReference($name, $value);
248        }
249    }
250
251    public function addReference(string $name, Base $reference)
252    {
253        if (array_key_exists($name, $this->referanceMapping)) {
254            if (!isset($this->references[$name])) {
255                $this->references[$name] = new EntityCollection();
256            }
257            $this->references[$name][] = $reference;
258        }
259    }
260
261    public function getReference(string $name)
262    {
263        if (array_key_exists($name, $this->references)) {
264            return $this->references[$name];
265        }
266        throw new \InvalidArgumentException(__METHOD__ . " reference {$name} has not been set!");
267    }
268
269    final public function __get($name)
270    {
271        if (array_key_exists($name, $this->fields)) {
272            return $this->fields[$name];
273        }
274        if (array_key_exists($name, $this->references)) {
275            return $this->references[$name];
276        }
277        throw new \InvalidArgumentException(__METHOD__ . " {$name} has not been set!");
278    }
279
280    final public function __isset($name): bool
281    {
282        return array_key_exists($name, $this->fields) || array_key_exists($name, $this->references);
283    }
284
285    final public function __unset($name)
286    {
287        if (array_key_exists($name, $this->fields)) {
288            unset($this->fields[$name]);
289        }
290        if (array_key_exists($name, $this->references)) {
291            unset($this->references[$name]);
292        }
293    }
294
295    #[\Override]
296    final public function offsetExists($offset): bool
297    {
298        return $this->__isset($offset);
299    }
300
301    #[\Override]
302    final public function offsetGet($offset): mixed
303    {
304        return $this->__get($offset);
305    }
306
307    #[\Override]
308    final public function offsetSet($offset, $value): Base
309    {
310        $this->__set($offset, $value);
311        return $this;
312    }
313
314    #[\Override]
315    final public function offsetUnset($offset): Base
316    {
317        $this->__unset($offset);
318        return $this;
319    }
320
321    #[\Override]
322    final public function count(): int
323    {
324        return count($this->fields);
325    }
326
327    #[\Override]
328    public function jsonSerialize(): mixed
329    {
330        return $this->fields;
331    }
332
333    public function getFields(): array
334    {
335        return $this->fields;
336    }
337
338    public function get($key = null, $default = null)
339    {
340        if (null === $key) {
341            return $this->dataRaw;
342        }
343        $keys = $key;
344        if (!is_array($keys)) {
345            $keys = [$keys];
346        }
347        $values = [];
348
349        foreach ($keys as $key) {
350            if ('__RAW__' == $key) {
351                $values[$key] = $this->dataRaw;
352                continue;
353            }
354            $levels = static::arrayAccessByDotPerpareKeys($key);
355
356            $value = $default;
357
358            $current = $this->dataRaw;
359            $levelsCount = count($levels);
360            for ($i = 0; $i < $levelsCount; ++$i) {
361                if (!is_array($current) || !array_key_exists($levels[$i], $current)) {
362                    continue;
363                }
364                $current = $current[$levels[$i]];
365                $value = $current;
366            }
367            $values[$key] = ($value);
368        }
369        return 1 == count($keys) ? $values[$keys[0]] : $values;
370    }
371
372    protected static function arrayAccessByDotPerpareKeys(string $key = null): array
373    {
374        if (null === $key) {
375            return [];
376        }
377        $keys = explode('.', $key);
378        if (false === $keys) {
379            throw new \Exception('Invalid key, key must be a string!');
380        }
381        $keys = array_filter($keys, 'strlen');
382        $keys = array_map(function ($key) {
383            return ((is_numeric($key) && !is_double(1 * $key)) ? (int)$key : $key);
384        }, $keys);
385
386        return $keys;
387    }
388
389    public function save()
390    {
391        try {
392            if (static::STATUS_NEW !== $this->getStatus()) {
393                return false;
394            }
395            $this->saveEntitiy();
396            $this->saveReferences();
397            return true;
398        } catch (\Exception $e) {
399            throw $e;
400        }
401    }
402
403    final public function saveEntitiy(): bool
404    {
405        try {
406            if (static::STATUS_NEW !== $this->getStatus()) {
407                return false;
408            }
409            if (!empty($this->fields)) {
410                $sql = 'REPLACE INTO ' . static::getTableName() . ' ';
411                $sql .= '(`' . implode('`, `', array_keys($this->fields)) . '`) ';
412
413                $questionMarks = array_fill(0, count($this->fields), '?');
414                $sql .= 'VALUES (' . implode(', ', $questionMarks) . ') ';
415
416                #print_r($sql . \PHP_EOL) ;
417                $stm = $this->getPDOAccess()->prepare($sql);
418
419                $stm->execute(array_values($this->fields));
420
421                #$this->postSave($stm, $this);
422
423                if ($stm && 0 < $stm->rowCount()) {
424                    return true;
425                }
426                throw new \Exception('Could not save entity');
427            }
428            throw new \Exception('Could not save entity, fields are empty');
429            return false;
430        } catch (\Exception $e) {
431            throw $e;
432        }
433        return false;
434    }
435
436    /**
437     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
438     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
439     */
440    public function postSave(\PDOStatement $stm, Base $entity)
441    {
442    }
443
444    final public function saveReferences(): bool
445    {
446        try {
447            if (static::STATUS_NEW !== $this->getStatus()) {
448                return false;
449            }
450            if (!empty($this->references)) {
451                array_map(function ($referencesCollection) {
452                    $referencesCollection->saveEntities();
453                }, $this->references);
454            }
455            return true;
456        } catch (\Exception $e) {
457            throw $e;
458        }
459    }
460
461    public function delete(): bool
462    {
463        try {
464            $this->deleteEntity();
465            $this->deleteReferences();
466            return true;
467        } catch (\Exception $e) {
468            throw $e;
469        }
470    }
471
472    abstract public function deleteEntity(): bool;
473
474    public function deleteReferences(): bool
475    {
476        #return true;
477        try {
478            foreach ($this->referanceMapping as $name => $mappingData) {
479                if (isset($mappingData['deleteFunction']) && is_callable($mappingData['deleteFunction'])) {
480                    call_user_func_array($mappingData['deleteFunction'], [$this, $name, $mappingData]);
481                    continue;
482                }
483                if (array_key_exists('delete', $mappingData) && false === $mappingData['delete']) {
484                    continue;
485                }
486                if (!array_key_exists('deleteFields', $mappingData) || empty($mappingData['deleteFields'])) {
487                    throw new \Exception(static::class . ' missing $deleteFields in reference mapping');
488                }
489                $addFields = [];
490                $referenceEntityClass = $mappingData['class'];
491                foreach ($mappingData['deleteFields'] as $sourceKey => $val) {
492                    $addFields[$sourceKey] = $val;
493                }
494
495                $referencesInstance = new $referenceEntityClass(
496                    $this->getPDOAccess(),
497                    $addFields,
498                    false
499                );
500                $referencesInstance->setupFields();
501
502                #print_r(array_intersect_key($referencesInstance->getFields(), $addFields));
503                #exit;
504
505                $referencesInstance->deleteWith(
506                    array_intersect_key($referencesInstance->getFields(), $addFields)
507                );
508            }
509            return true;
510        } catch (\Exception $e) {
511            throw $e;
512        }
513    }
514
515    final public function deleteWith(array $fields): bool
516    {
517        try {
518            $sql = "DELETE FROM " . static::getTableName();
519            if (!empty($fields)) {
520                $where = array_map(function ($field) {
521                    return $field . ' = ?';
522                }, array_keys($fields));
523                $sql .= " WHERE " . implode(' AND ', $where);
524            }
525
526            $stm = $this->getPDOAccess()->prepare($sql);
527            $stm->execute(array_values($fields));
528            if ($stm && 0 < $stm->rowCount()) {
529                return true;
530            }
531            return false;
532        } catch (\Exception $e) {
533            throw $e;
534        }
535    }
536
537    public function clearEntity(array $addWhere = []): bool
538    {
539        try {
540            return $this->deleteWith($addWhere);
541        } catch (\Exception $e) {
542            throw $e;
543        }
544    }
545
546    public function clearEntityReferences(): bool
547    {
548        try {
549            foreach ($this->getReferenceMapping(true) as $name => $mappingData) {
550                $referenceEntityClass = $mappingData['class'];
551                $clearFields = [];
552                $position = 0;
553                foreach (($mappingData['clearFields'] ?? []) as $key => $value) {
554                    if (is_callable($value)) {
555                        $clearFields[$key] = call_user_func_array($value, [$position++, $name, null]);
556                    } else {
557                        $clearFields[$key] = $value;
558                    }
559                }
560
561                $referencesInstance = new $referenceEntityClass(
562                    $this->getPDOAccess(),
563                    [],
564                    false
565                );
566                if (!empty($clearFields)) {
567                    $referencesInstance->deleteWith($clearFields);
568                } else {
569                    $referencesInstance->clearEntity();
570                }
571            }
572            return true;
573        } catch (\Exception $e) {
574            throw $e;
575        }
576    }
577
578    public static function getTableName(): string
579    {
580        if (defined('static::TABLENAME')) {
581            return strtolower(static::TABLENAME);
582        }
583        $classNameWithNs = explode("\\", static::class);
584        $className = end($classNameWithNs);
585
586        return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $className));
587    }
588}