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    final public function offsetExists($offset): bool
295    {
296        return $this->__isset($offset);
297    }
298
299    final public function offsetGet($offset)
300    {
301        return $this->__get($offset);
302    }
303
304    final public function offsetSet($offset, $value): Base
305    {
306        $this->__set($offset, $value);
307        return $this;
308    }
309
310    final public function offsetUnset($offset): Base
311    {
312        $this->__unset($offset);
313        return $this;
314    }
315
316    final public function count(): int
317    {
318        return count($this->fields);
319    }
320
321    public function jsonSerialize(): mixed
322    {
323        return $this->fields;
324    }
325
326    public function getFields(): array
327    {
328        return $this->fields;
329    }
330
331    public function get($key = null, $default = null)
332    {
333        if (null === $key) {
334            return $this->dataRaw;
335        }
336        $keys = $key;
337        if (!is_array($keys)) {
338            $keys = [$keys];
339        }
340        $values = [];
341
342        foreach ($keys as $key) {
343            if ('__RAW__' == $key) {
344                $values[$key] = $this->dataRaw;
345                continue;
346            }
347            $levels = static::arrayAccessByDotPerpareKeys($key);
348
349            $value = $default;
350
351            $pointer = &$this->dataRaw;
352            $levelsCount = count($levels);
353            for ($i = 0; $i < $levelsCount; ++$i) {
354                if (array_key_exists($levels[$i], $pointer)) {
355                    $pointer = &$pointer[$levels[$i]];
356                    $value = $pointer;
357
358                    continue;
359                }
360            }
361            $values[$key] = ($value);
362        }
363        return 1 == count($keys) ? $values[$keys[0]] : $values;
364    }
365
366    protected static function arrayAccessByDotPerpareKeys(string $key = null): array
367    {
368        if (null === $key) {
369            return [];
370        }
371        $keys = explode('.', $key);
372        if (false === $keys) {
373            throw new \Exception('Invalid key, key must be a string!');
374        }
375        $keys = array_filter($keys, 'strlen');
376        $keys = array_map(function ($key) {
377            return ((is_numeric($key) && !is_double(1 * $key)) ? (int)$key : $key);
378        }, $keys);
379
380        return $keys;
381    }
382
383    public function save()
384    {
385        try {
386            if (static::STATUS_NEW !== $this->getStatus()) {
387                return false;
388            }
389            $this->saveEntitiy();
390            $this->saveReferences();
391            return true;
392        } catch (\Exception $e) {
393            throw $e;
394        }
395    }
396
397    final public function saveEntitiy(): bool
398    {
399        try {
400            if (static::STATUS_NEW !== $this->getStatus()) {
401                return false;
402            }
403            if (!empty($this->fields)) {
404                $sql = 'REPLACE INTO ' . static::getTableName() . ' ';
405                $sql .= '(`' . implode('`, `', array_keys($this->fields)) . '`) ';
406
407                $questionMarks = array_fill(0, count($this->fields), '?');
408                $sql .= 'VALUES (' . implode(', ', $questionMarks) . ') ';
409
410                #print_r($sql . \PHP_EOL) ;
411                $stm = $this->getPDOAccess()->prepare($sql);
412
413                $stm->execute(array_values($this->fields));
414
415                #$this->postSave($stm, $this);
416
417                if ($stm && 0 < $stm->rowCount()) {
418                    return true;
419                }
420                throw new \Exception('Could not save entity');
421            }
422            throw new \Exception('Could not save entity, fields are empty');
423            return false;
424        } catch (\Exception $e) {
425            throw $e;
426        }
427        return false;
428    }
429
430    /**
431     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
432     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
433     */
434    public function postSave(\PDOStatement $stm, Base $entity)
435    {
436    }
437
438    final public function saveReferences(): bool
439    {
440        try {
441            if (static::STATUS_NEW !== $this->getStatus()) {
442                return false;
443            }
444            if (!empty($this->references)) {
445                array_map(function ($referencesCollection) {
446                    $referencesCollection->saveEntities();
447                }, $this->references);
448            }
449            return true;
450        } catch (\Exception $e) {
451            throw $e;
452        }
453    }
454
455    public function delete(): bool
456    {
457        try {
458            $this->deleteEntity();
459            $this->deleteReferences();
460            return true;
461        } catch (\Exception $e) {
462            throw $e;
463        }
464    }
465
466    abstract public function deleteEntity(): bool;
467
468    public function deleteReferences(): bool
469    {
470        #return true;
471        try {
472            foreach ($this->referanceMapping as $name => $mappingData) {
473                if (isset($mappingData['deleteFunction']) && is_callable($mappingData['deleteFunction'])) {
474                    call_user_func_array($mappingData['deleteFunction'], [$this, $name, $mappingData]);
475                    continue;
476                }
477                if (array_key_exists('delete', $mappingData) && false === $mappingData['delete']) {
478                    continue;
479                }
480                if (!array_key_exists('deleteFields', $mappingData) || empty($mappingData['deleteFields'])) {
481                    throw new \Exception(static::class . ' missing $deleteFields in reference mapping');
482                }
483                $addFields = [];
484                $referenceEntityClass = $mappingData['class'];
485                foreach ($mappingData['deleteFields'] as $sourceKey => $val) {
486                    $addFields[$sourceKey] = $val;
487                }
488
489                $referencesInstance = new $referenceEntityClass(
490                    $this->getPDOAccess(),
491                    $addFields,
492                    false
493                );
494                $referencesInstance->setupFields();
495
496                #print_r(array_intersect_key($referencesInstance->getFields(), $addFields));
497                #exit;
498
499                $referencesInstance->deleteWith(
500                    array_intersect_key($referencesInstance->getFields(), $addFields)
501                );
502            }
503            return true;
504        } catch (\Exception $e) {
505            throw $e;
506        }
507    }
508
509    final public function deleteWith(array $fields): bool
510    {
511        try {
512            $sql = "DELETE FROM " . static::getTableName();
513            if (!empty($fields)) {
514                $where = array_map(function ($field) {
515                    return $field . ' = ?';
516                }, array_keys($fields));
517                $sql .= " WHERE " . implode(' AND ', $where);
518            }
519
520            $stm = $this->getPDOAccess()->prepare($sql);
521            $stm->execute(array_values($fields));
522            if ($stm && 0 < $stm->rowCount()) {
523                return true;
524            }
525            return false;
526        } catch (\Exception $e) {
527            throw $e;
528        }
529    }
530
531    public function clearEntity(array $addWhere = []): bool
532    {
533        try {
534            return $this->deleteWith($addWhere);
535        } catch (\Exception $e) {
536            throw $e;
537        }
538    }
539
540    public function clearEntityReferences(): bool
541    {
542        try {
543            foreach ($this->getReferenceMapping(true) as $name => $mappingData) {
544                $referenceEntityClass = $mappingData['class'];
545                $clearFields = [];
546                $position = 0;
547                foreach (($mappingData['clearFields'] ?? []) as $key => $value) {
548                    if (is_callable($value)) {
549                        $clearFields[$key] = call_user_func_array($value, [$position++, $name, null]);
550                    } else {
551                        $clearFields[$key] = $value;
552                    }
553                }
554
555                $referencesInstance = new $referenceEntityClass(
556                    $this->getPDOAccess(),
557                    [],
558                    false
559                );
560                if (!empty($clearFields)) {
561                    $referencesInstance->deleteWith($clearFields);
562                } else {
563                    $referencesInstance->clearEntity();
564                }
565            }
566            return true;
567        } catch (\Exception $e) {
568            throw $e;
569        }
570    }
571
572    public static function getTableName(): string
573    {
574        if (defined('static::TABLENAME')) {
575            return strtolower(static::TABLENAME);
576        }
577        $classNameWithNs = explode("\\", static::class);
578        $className = end($classNameWithNs);
579
580        return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $className));
581    }
582}