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
13340
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
72
 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 */
19abstract class Base implements \Countable, \ArrayAccess, \JsonSerializable
20{
21    use ItemNeedsUpdateTrait;
22    use PDOTrait;
23
24    protected $fieldMapping = [];
25
26    protected $fields = [];
27
28    protected $referanceMapping = [];
29
30    protected $preFormatFields = [];
31
32    protected $references = [];
33
34    protected $dataRaw = [];
35
36    protected $setupFields = true;
37    protected $setupReferences = true;
38
39    protected $status = 1;
40
41    const STATUS_NEW = 1;
42    const STATUS_OLD = 0;
43
44    public function __construct(PDOAccess $mySqlAccess, array $dataRaw = [], bool $setup = true)
45    {
46        try {
47            $this->pdoAccess = $mySqlAccess;
48            $this->dataRaw = $dataRaw;
49
50            if (true === $setup) {
51                $this->setupMapping();
52
53                $this->preSetup();
54
55                $this->setupFields();
56
57                $this->setupReferences();
58
59                $this->postSetup();
60            }
61        } catch (\Exception $e) {
62            throw $e;
63        }
64    }
65
66    public static function entityFactory(
67        string $entityName,
68        PDOAccess $mySqlAccess,
69        array $dataRaw = [],
70        bool $setup = true
71    ) {
72        try {
73            $className = preg_replace_callback('/[_-]([a-z0-9]*)/i', function ($matches) {
74                return ucfirst($matches[1] ?? '');
75            }, $entityName);
76            $className = '\\BO\\Zmsdldb\\Importer\\MySQL\\Entity' . $className;
77
78            return new $className($mySqlAccess, $dataRaw, $setup);
79        } catch (\Exception $e) {
80            throw $e;
81        }
82    }
83
84    public function factory(string $entityName, array $dataRaw = [], bool $setup = true)
85    {
86        try {
87            return static::entityFactory($entityName, $this->getPDOAccess(), $dataRaw, $setup);
88        } catch (\Exception $e) {
89            throw $e;
90        }
91    }
92
93    public function setRawData(array $rawData = [])
94    {
95        $this->dataRaw = $rawData;
96        return $this;
97    }
98
99    public function getRawData(): array
100    {
101        return $this->dataRaw;
102    }
103
104    public function setStatus(int $status = Base::STATUS_NEW)
105    {
106        $this->status = $status;
107    }
108
109    public function getStatus(): int
110    {
111        return $this->status;
112    }
113
114    public function getReferenceMapping($setup = false): array
115    {
116        try {
117            if (true === $setup) {
118                $this->setupMapping();
119            }
120
121            return $this->referanceMapping;
122        } catch (\Exception $e) {
123            throw $e;
124        }
125    }
126
127    protected function setupPreFormatFields()
128    {
129    }
130
131    protected function setupMapping()
132    {
133    }
134
135    public function preSetup()
136    {
137    }
138
139    public function postSetup()
140    {
141    }
142
143    public function preSetupFields()
144    {
145    }
146
147    public function postSetupFields()
148    {
149    }
150
151    final public function setupFields(): bool
152    {
153        try {
154            if (false === $this->setupFields) {
155                return true;
156            }
157            $this->preSetupFields();
158
159            $values = $this->get(array_keys(array_filter($this->fieldMapping)));
160            foreach ($values as $key => $value) {
161                $this->__set($key, $value);
162            }
163            $this->postSetupFields();
164            $this->setupFields = false;
165            return true;
166        } catch (\Exception $e) {
167            throw $e;
168        }
169    }
170
171    protected function getReferenceFields()
172    {
173        $referenceFields = array_flip(array_keys(array_filter($this->referanceMapping)));
174
175        foreach (array_keys($referenceFields) as $name) {
176            $referenceFields[$name] = $this->get($name);
177        }
178        return $referenceFields;
179    }
180
181    final public function setupReferences()
182    {
183        try {
184            if (false === $this->setupReferences) {
185                return true;
186            }
187            $values = $this->getReferenceFields();
188
189            foreach ($values as $name => $references) {
190                $referenceEntityClass = $this->referanceMapping[$name]['class'];
191                $addFields = [];
192
193                foreach (($this->referanceMapping[$name]['neededFields'] ?? []) as $sourceKey => $destinationKey) {
194                    $addFields[$destinationKey] = $this->get($sourceKey);
195                }
196                $isMultiple = $this->referanceMapping[$name]['multiple'] ?? true;
197
198                if (false === $isMultiple) {
199                    $references = [$references];
200                }
201
202                $position = 0;
203                foreach (($references ?? []) as $reference) {
204                    foreach ($this->referanceMapping[$name]['addFields'] as $key => $value) {
205                        if (is_callable($value)) {
206                            $addFields[$key] = call_user_func_array($value, [$position, $name, $reference]);
207                        } else {
208                            $addFields[$key] = $value;
209                        }
210                    }
211                    if (true === ($this->referanceMapping[$name]['selfAsArray'] ?? false)) {
212                        $reference = [
213                            $name => $reference
214                        ];
215                    }
216                    $referencesInstance = new $referenceEntityClass(
217                        $this->getPDOAccess(),
218                        array_merge(
219                            $reference,
220                            $addFields
221                        )
222                    );
223
224                    $this->addReference($name, $referencesInstance);
225                    $position++;
226                }
227            }
228            $this->setupReferences = false;
229            return true;
230        } catch (\Exception $e) {
231            throw $e;
232        }
233    }
234
235    final public function __set($name, $value)
236    {
237        if (array_key_exists($name, $this->fieldMapping)) {
238            $name = $this->fieldMapping[$name];
239            if (is_bool($value)) {
240                $value = (int)$value;
241            } elseif (stripos($name, '_json')) {
242                $value = json_encode($value);
243            }
244            $this->fields[$name] = $value;
245        } elseif (array_key_exists($name, $this->referanceMapping)) {
246            $this->addReference($name, $value);
247        }
248    }
249
250    public function addReference(string $name, Base $reference)
251    {
252        if (array_key_exists($name, $this->referanceMapping)) {
253            if (!isset($this->references[$name])) {
254                $this->references[$name] = new EntityCollection();
255            }
256            $this->references[$name][] = $reference;
257        }
258    }
259
260    public function getReference(string $name)
261    {
262        if (array_key_exists($name, $this->references)) {
263            return $this->references[$name];
264        }
265        throw new \InvalidArgumentException(__METHOD__ . " reference {$name} has not been set!");
266    }
267
268    final public function __get($name)
269    {
270        if (array_key_exists($name, $this->fields)) {
271            return $this->fields[$name];
272        }
273        if (array_key_exists($name, $this->references)) {
274            return $this->references[$name];
275        }
276        throw new \InvalidArgumentException(__METHOD__ . " {$name} has not been set!");
277    }
278
279    final public function __isset($name): bool
280    {
281        return array_key_exists($name, $this->fields) || array_key_exists($name, $this->references);
282    }
283
284    final public function __unset($name)
285    {
286        if (array_key_exists($name, $this->fields)) {
287            unset($this->fields[$name]);
288        }
289        if (array_key_exists($name, $this->references)) {
290            unset($this->references[$name]);
291        }
292    }
293
294    #[\Override]
295    final public function offsetExists($offset): bool
296    {
297        return $this->__isset($offset);
298    }
299
300    #[\Override]
301    final public function offsetGet($offset)
302    {
303        return $this->__get($offset);
304    }
305
306    #[\Override]
307    final public function offsetSet($offset, $value): Base
308    {
309        $this->__set($offset, $value);
310        return $this;
311    }
312
313    #[\Override]
314    final public function offsetUnset($offset): Base
315    {
316        $this->__unset($offset);
317        return $this;
318    }
319
320    #[\Override]
321    final public function count(): int
322    {
323        return count($this->fields);
324    }
325
326    #[\Override]
327    public function jsonSerialize(): mixed
328    {
329        return $this->fields;
330    }
331
332    public function getFields(): array
333    {
334        return $this->fields;
335    }
336
337    public function get($key = null, $default = null)
338    {
339        if (null === $key) {
340            return $this->dataRaw;
341        }
342        $keys = $key;
343        if (!is_array($keys)) {
344            $keys = [$keys];
345        }
346        $values = [];
347
348        foreach ($keys as $key) {
349            if ('__RAW__' == $key) {
350                $values[$key] = $this->dataRaw;
351                continue;
352            }
353            $levels = static::arrayAccessByDotPerpareKeys($key);
354
355            $value = $default;
356
357            $pointer = &$this->dataRaw;
358            $levelsCount = count($levels);
359            for ($i = 0; $i < $levelsCount; ++$i) {
360                if (array_key_exists($levels[$i], $pointer)) {
361                    $pointer = &$pointer[$levels[$i]];
362                    $value = $pointer;
363
364                    continue;
365                }
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}