Mapper   D
last analyzed

Complexity

Total Complexity 58

Size/Duplication

Total Lines 393
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 58
eloc 121
dl 0
loc 393
c 2
b 0
f 0
rs 4.5599

22 Methods

Rating   Name   Duplication   Size   Complexity  
A instantiate() 0 18 3
A me() 0 3 1
A getConstraints() 0 3 1
A getAnnotation() 0 3 1
A getColumns() 0 3 1
A getTable() 0 19 2
A getEntityClass() 0 3 1
A findColumnByOriginName() 0 8 3
A getPrimaryKey() 0 10 4
A meWithoutEnforcer() 0 3 1
A checkProperty() 0 10 4
A name() 0 5 1
A processColumns() 0 14 4
B getAllVariables() 0 47 7
B processConstraints() 0 33 7
A processComplexes() 0 12 3
A processEmbedded() 0 11 3
A __get() 0 7 2
A getComplex() 0 3 1
A getVarNameByColumn() 0 9 3
A getOriginFieldNames() 0 14 4
A getEmbedded() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Mapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
/********************************************************************************
3
 *   Apache License, Version 2.0                                                *
4
 *                                                                              *
5
 *   Copyright [2020] [Nurlan Mukhanov <[email protected]>]                      *
6
 *                                                                              *
7
 *   Licensed under the Apache License, Version 2.0 (the "License");            *
8
 *   you may not use this file except in compliance with the License.           *
9
 *   You may obtain a copy of the License at                                    *
10
 *                                                                              *
11
 *       http://www.apache.org/licenses/LICENSE-2.0                             *
12
 *                                                                              *
13
 *   Unless required by applicable law or agreed to in writing, software        *
14
 *   distributed under the License is distributed on an "AS IS" BASIS,          *
15
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
16
 *   See the License for the specific language governing permissions and        *
17
 *   limitations under the License.                                             *
18
 *                                                                              *
19
 ********************************************************************************/
20
21
declare(strict_types=1);
22
23
namespace DBD\Entity;
24
25
use DBD\Common\Instantiatable;
26
use DBD\Common\Singleton;
27
use DBD\Entity\Common\Enforcer;
28
use DBD\Entity\Common\EntityException;
29
use DBD\Entity\Common\Utils;
30
31
/**
32
 * Class Mapper
33
 * @todo Check child classes for methods
34
 * @todo check for private vars
35
 *
36
 * @package DBD\Entity
37
 */
38
abstract class Mapper extends Singleton
39
{
40
    const ANNOTATION = "abstract";
41
    const POSTFIX = "Map";
42
43
    /**
44
     * Used for quick access to the mapper without instantiating it and have only one instance
45
     *
46
     * @return Mapper|static
47
     * @throws EntityException
48
     */
49
    public static function me(): Instantiatable
50
    {
51
        return self::instantiate();
52
    }
53
54
    /**
55
     * @param bool $callEnforcer
56
     * @return Mapper|static
57
     * @throws Common\EntityException
58
     * @throws EntityException
59
     */
60
    private static function instantiate(bool $callEnforcer = true): Mapper
61
    {
62
        /** @var static $self */
63
        $self = parent::me();
64
65
        $class = get_class($self);
66
67
        if (!isset(MapperCache::me()->fullyInstantiated[$class])) {
68
69
            // Check we set ANNOTATION properly in Mapper instance
70
            if ($callEnforcer) {
71
                Enforcer::__add(__CLASS__, $class);
72
            }
73
            $self->getAllVariables();
74
75
            MapperCache::me()->fullyInstantiated[$class] = true;
76
        }
77
        return $self;
78
    }
79
80
    /**
81
     * Read all public, private and protected variable names and their values.
82
     * Used when we need convert Mapper to Table instance
83
     *
84
     * @return MapperVariables
85
     * @throws EntityException
86
     */
87
    public function getAllVariables(): MapperVariables
88
    {
89
        $thisName = $this->name();
90
91
        if (!isset(MapperCache::me()->allVariables[$thisName])) {
92
93
            /**
94
             * All available variables
95
             * Columns are always PUBLIC
96
             * Complex, Constraints and Embedded are always PROTECTED
97
             */
98
            $allVars = get_object_vars($this);
99
            $publicVars = Utils::getObjectVars($this);
100
            $protectedVars = Utils::arrayDiff($allVars, $publicVars);
101
102
            $constraints = $embedded = $complex = $columns = [];
103
104
            foreach ($publicVars as $varName => $varValue) {
105
                $this->checkProperty($varValue, $varName);
106
                $columns[$varName] = $varValue;
107
            }
108
109
            foreach ($protectedVars as $varName => $varValue) {
110
                $this->checkProperty($varValue, $varName);
111
112
                if (isset($varValue[Constraint::LOCAL_COLUMN])) {
113
                    $constraints[$varName] = $varValue;
114
                } else {
115
                    if (isset($varValue[Embedded::NAME])) {
116
                        $embedded[$varName] = $varValue;
117
                    } else if (isset($varValue[Complex::TYPE])) {
118
                        $complex[$varName] = $varValue;
119
                    }
120
                }
121
            }
122
123
            $this->processComplexes($complex);
124
125
            $this->processEmbedded($embedded);
126
127
            $this->processColumns($columns);
128
129
            $this->processConstraints($constraints, $columns, $embedded, $complex);
130
131
        }
132
133
        return MapperCache::me()->allVariables[$thisName];
134
    }
135
136
    /**
137
     * Get simple Mapper class name without namespace
138
     */
139
    public function name()
140
    {
141
        $name = get_class($this);
142
143
        return substr($name, strrpos($name, '\\') + 1);
144
    }
145
146
    /**
147
     * @param $varValue
148
     * @param string $varName
149
     * @throws EntityException
150
     */
151
    private function checkProperty($varValue, string $varName): void
152
    {
153
        if (is_null($varValue)) {
154
            throw new EntityException(sprintf("property '\$%s' of %s is null", $varName, get_class($this)));
155
        }
156
        if (!is_array($varValue)) {
157
            throw new EntityException(sprintf("property '\$%s' of %s is not array", $varName, get_class($this)));
158
        }
159
        if (count($varValue) == 0) {
160
            throw new EntityException(sprintf("property '\$%s' of %s does not have definitions", $varName, get_class($this)));
161
        }
162
    }
163
164
    /**
165
     * @param array $complex
166
     * @throws EntityException
167
     */
168
    private function processComplexes(array $complex): void
169
    {
170
        $thisName = $this->name();
171
172
        /** ----------------------COMPLEX------------------------ */
173
        foreach ($complex as $complexName => $complexValue) {
174
            $this->$complexName = new Complex($complexValue);
175
            MapperCache::me()->complex[$thisName][$complexName] = $this->$complexName;
176
        }
177
        // У нас может не быть комплексов
178
        if (!isset(MapperCache::me()->complex[$thisName])) {
179
            MapperCache::me()->complex[$thisName] = [];
180
        }
181
    }
182
183
    /**
184
     * @param array $embedded
185
     * @throws EntityException
186
     */
187
    private function processEmbedded(array $embedded): void
188
    {
189
        $thisName = $this->name();
190
        /** ----------------------EMBEDDED------------------------ */
191
        foreach ($embedded as $embeddedName => $embeddedValue) {
192
            $this->$embeddedName = new Embedded($embeddedValue);
193
            MapperCache::me()->embedded[$thisName][$embeddedName] = $this->$embeddedName;
194
        }
195
        // У нас может не быть эмбедов
196
        if (!isset(MapperCache::me()->embedded[$thisName])) {
197
            MapperCache::me()->embedded[$thisName] = [];
198
        }
199
    }
200
201
    /**
202
     * @param array $columns
203
     * @throws EntityException
204
     */
205
    private function processColumns(array $columns): void
206
    {
207
        $thisName = $this->name();
208
209
        /** ----------------------COLUMNS------------------------ */
210
        if (!isset(MapperCache::me()->columns[$thisName])) {
211
            foreach ($columns as $columnName => $columnValue) {
212
                $this->$columnName = new Column($columnValue);
213
                MapperCache::me()->columns[$thisName][$columnName] = $this->$columnName;
214
            }
215
        }
216
        // У нас может не быть колонок
217
        if (!isset(MapperCache::me()->columns[$thisName])) {
218
            MapperCache::me()->columns[$thisName] = [];
219
        }
220
    }
221
222
    /**
223
     * @param array $constraints
224
     * @param array $columns
225
     * @param array $embedded
226
     * @param array $complex
227
     * @throws EntityException
228
     */
229
    private function processConstraints(array $constraints, array $columns, array $embedded, array $complex): void
230
    {
231
        $thisName = $this->name();
232
233
        /** ----------------------CONSTRAINTS------------------------ */
234
        $temporaryConstraints = [];
235
        if (!isset(MapperCache::me()->constraints[$thisName])) {
236
            $entityClass = get_parent_class($this);
237
238
            foreach ($constraints as $constraintName => $constraintValue) {
239
                $temporaryConstraint = new Constraint($constraintValue);
240
                // we asking provide self instance while table still not ready
241
                //$temporaryConstraint->localTable = $this->getTable();
242
243
                // If we use View - we do not always need to define constraint fields
244
                if ($entityClass !== View::class && is_string($temporaryConstraint->localColumn)) {
245
                    $temporaryConstraint->localColumn = $this->findColumnByOriginName($temporaryConstraint->localColumn);
246
                }
247
                $temporaryConstraints[$constraintName] = $temporaryConstraint;
248
            }
249
        }
250
251
        // У нас может не быть ограничений
252
        if (!isset(MapperCache::me()->constraints[$thisName])) {
253
            MapperCache::me()->constraints[$thisName] = [];
254
        }
255
        MapperCache::me()->allVariables[$thisName] = new MapperVariables($columns, $constraints, $embedded, $complex);
256
257
        // Now fill constraint as map is ready
258
        foreach ($temporaryConstraints as $constraintName => $temporaryConstraint) {
259
            $temporaryConstraint->localTable = $this->getTable();
260
            $this->$constraintName = $temporaryConstraint;
261
            MapperCache::me()->constraints[$thisName][$constraintName] = $this->$constraintName;
262
        }
263
    }
264
265
    /**
266
     * @param string $originName
267
     *
268
     * @return Column
269
     * @throws EntityException
270
     */
271
    public function findColumnByOriginName(string $originName): Column
272
    {
273
        foreach ($this->getColumns() as $column) {
274
            if ($column->name == $originName) {
275
                return $column;
276
            }
277
        }
278
        throw new EntityException(sprintf("Can't find origin column '%s' in %s", $originName, get_class($this)));
279
    }
280
281
    /**
282
     * @return Column[]
283
     */
284
    public function getColumns(): array
285
    {
286
        return MapperCache::me()->columns[$this->name()];
287
    }
288
289
    /**
290
     * @return mixed
291
     */
292
    public function getTable()
293
    {
294
        $thisName = $this->name();
295
296
        if (!isset(MapperCache::me()->table[$thisName])) {
297
            $parentClass = $this->getEntityClass();
298
            $table = new Table();
299
            /** @var Entity $parentClass */
300
            $table->name = $parentClass::TABLE;
301
            $table->scheme = $parentClass::SCHEME;
302
            $table->columns = $this->getColumns();
303
            $table->constraints = $this->getConstraints();
304
            $table->keys = $this->getPrimaryKey();
305
            $table->annotation = $this->getAnnotation();
306
307
            MapperCache::me()->table[$thisName] = $table;
308
        }
309
310
        return MapperCache::me()->table[$thisName];
311
    }
312
313
    /**
314
     * Returns Entity class name which uses this Mapper
315
     *
316
     * @return string
317
     */
318
    public function getEntityClass(): string
319
    {
320
        return substr(get_class($this), 0, strlen(self::POSTFIX) * -1);
321
    }
322
323
    /**
324
     * @return Constraint[]
325
     */
326
    public function getConstraints(): array
327
    {
328
        return MapperCache::me()->constraints[$this->name()];
329
    }
330
331
    /**
332
     * @return Column[] that is associative array where key is property name
333
     */
334
    public function getPrimaryKey(): array
335
    {
336
        $keys = [];
337
        foreach (MapperCache::me()->columns[$this->name()] as $columnName => $column) {
338
            if (isset($column->key) and $column->key === true) {
339
                $keys[$columnName] = $column;
340
            }
341
        }
342
343
        return $keys;
344
    }
345
346
    /**
347
     * Returns table comment
348
     *
349
     * @return string
350
     */
351
    public function getAnnotation(): string
352
    {
353
        return $this::ANNOTATION;
354
    }
355
356
    /**
357
     * @return Mapper|static
358
     * @throws EntityException
359
     */
360
    public static function meWithoutEnforcer(): Mapper
361
    {
362
        return self::instantiate(false);
363
    }
364
365
    /**
366
     * Special getter to access protected and private properties
367
     * @param string $property
368
     *
369
     * @return mixed
370
     * @throws EntityException
371
     */
372
    public function __get(string $property)
373
    {
374
        if (!property_exists($this, $property)) {
375
            throw new EntityException(sprintf("Can't find property '\$%s' of '%s'", $property, get_class($this)));
376
        }
377
378
        return $this->$property;
379
    }
380
381
    /**
382
     * @return Complex[]
383
     */
384
    public function getComplex(): array
385
    {
386
        return MapperCache::me()->complex[$this->name()];
387
    }
388
389
    /**
390
     * @return Embedded[]
391
     */
392
    public function getEmbedded(): array
393
    {
394
        return MapperCache::me()->embedded[$this->name()];
395
    }
396
397
    /**
398
     * @param Column $column
399
     *
400
     * @return int|string
401
     * @throws EntityException
402
     */
403
    public function getVarNameByColumn(Column $column)
404
    {
405
        foreach ($this->getOriginFieldNames() as $varName => $originFieldName) {
406
            if ($originFieldName == $column->name) {
407
                return $varName;
408
            }
409
        }
410
411
        throw new EntityException(sprintf("Seems column '%s' does not belong to this mapper", $column->name));
412
    }
413
414
    /**
415
     * @return array
416
     */
417
    public function getOriginFieldNames(): array
418
    {
419
        $thisName = $this->name();
420
        if (!isset(MapperCache::me()->originFieldNames[$thisName])) {
421
            if (count($this->getColumns())) {
422
                foreach ($this->getColumns() as $columnName => $column) {
423
                    MapperCache::me()->originFieldNames[$thisName][$columnName] = $column->name;
424
                }
425
            } else {
426
                MapperCache::me()->originFieldNames[$thisName] = [];
427
            }
428
        }
429
430
        return MapperCache::me()->originFieldNames[$thisName];
431
    }
432
}
433