Completed
Push — master ( 2bb1de...99d827 )
by Nurlan
02:10
created

Mapper   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 55
eloc 117
dl 0
loc 357
c 0
b 0
f 0
rs 6

19 Methods

Rating   Name   Duplication   Size   Complexity  
A instantiate() 0 18 3
A me() 0 3 1
A checkProperty() 0 10 4
A __get() 0 7 2
A name() 0 5 1
A getConstraints() 0 3 1
A getAnnotation() 0 3 1
A getColumns() 0 3 1
A getTable() 0 19 2
D getAllVariables() 0 101 20
A getEntityClass() 0 3 1
A getComplex() 0 3 1
A findColumnByOriginName() 0 8 3
A getPrimaryKey() 0 10 4
A getVarNameByColumn() 0 9 3
A getBaseColumns() 0 3 1
A meWithoutEnforcer() 0 3 1
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 = [];
103
            $embedded = [];
104
            $complex = [];
105
            $columns = [];
106
107
            foreach ($publicVars as $varName => $varValue) {
108
                $this->checkProperty($varValue, $varName);
109
                $columns[$varName] = $varValue;
110
            }
111
112
            foreach ($protectedVars as $varName => $varValue) {
113
                $this->checkProperty($varValue, $varName);
114
115
                if (isset($varValue[Constraint::LOCAL_COLUMN])) {
116
                    $constraints[$varName] = $varValue;
117
                } else {
118
                    if (isset($varValue[Embedded::NAME])) {
119
                        $embedded[$varName] = $varValue;
120
                    } else if (isset($varValue[Complex::TYPE])) {
121
                        $complex[$varName] = $varValue;
122
                    }
123
                }
124
            }
125
126
            /** ----------------------COMPLEX------------------------ */
127
            foreach ($complex as $complexName => $complexValue) {
128
                $this->$complexName = new Complex($complexValue);
129
                MapperCache::me()->complex[$thisName][$complexName] = $this->$complexName;
130
            }
131
            // У нас может не быть комплексов
132
            if (!isset(MapperCache::me()->complex[$thisName])) {
133
                MapperCache::me()->complex[$thisName] = [];
134
            }
135
            /** ----------------------EMBEDDED------------------------ */
136
            foreach ($embedded as $embeddedName => $embeddedValue) {
137
                $this->$embeddedName = new Embedded($embeddedValue);
138
                MapperCache::me()->embedded[$thisName][$embeddedName] = $this->$embeddedName;
139
            }
140
            // У нас может не быть эмбедов
141
            if (!isset(MapperCache::me()->embedded[$thisName])) {
142
                MapperCache::me()->embedded[$thisName] = [];
143
            }
144
            /** ----------------------COLUMNS------------------------ */
145
            if (!isset(MapperCache::me()->columns[$thisName])) {
146
                foreach ($columns as $columnName => $columnValue) {
147
                    $this->$columnName = new Column($columnValue);
148
                    MapperCache::me()->columns[$thisName][$columnName] = $this->$columnName;
149
                }
150
            }
151
            // У нас может не быть колонок
152
            if (!isset(MapperCache::me()->columns[$thisName])) {
153
                MapperCache::me()->columns[$thisName] = [];
154
            }
155
            /** ----------------------CONSTRAINTS------------------------ */
156
            $temporaryConstraints = [];
157
            if (!isset(MapperCache::me()->constraints[$thisName])) {
158
                $entityClass = get_parent_class($this);
159
160
                foreach ($constraints as $constraintName => $constraintValue) {
161
                    $temporaryConstraint = new Constraint($constraintValue);
162
                    // we asking provide self instance while table still not ready
163
                    //$temporaryConstraint->localTable = $this->getTable();
164
165
                    // If we use View - we do not always need to define constraint fields
166
                    if ($entityClass !== View::class && is_string($temporaryConstraint->localColumn)) {
167
                        $temporaryConstraint->localColumn = $this->findColumnByOriginName($temporaryConstraint->localColumn);
168
                    }
169
                    $temporaryConstraints[$constraintName] = $temporaryConstraint;
170
                }
171
            }
172
173
            // У нас может не быть констрейнтов
174
            if (!isset(MapperCache::me()->constraints[$thisName])) {
175
                MapperCache::me()->constraints[$thisName] = [];
176
            }
177
            MapperCache::me()->allVariables[$thisName] = new MapperVariables($columns, $constraints, $embedded, $complex);
178
179
            // Now fill constraint as map is ready
180
            foreach ($temporaryConstraints as $constraintName => $temporaryConstraint) {
181
                $temporaryConstraint->localTable = $this->getTable();
182
                $this->$constraintName = $temporaryConstraint;
183
                MapperCache::me()->constraints[$thisName][$constraintName] = $this->$constraintName;
184
            }
185
        }
186
187
        return MapperCache::me()->allVariables[$thisName];
188
    }
189
190
    /**
191
     * Get simple Mapper class name without namespace
192
     */
193
    public function name()
194
    {
195
        $name = get_class($this);
196
197
        return substr($name, strrpos($name, '\\') + 1);
198
    }
199
200
    /**
201
     * @param $varValue
202
     * @param string $varName
203
     * @throws EntityException
204
     */
205
    private function checkProperty($varValue, string $varName): void
206
    {
207
        if (is_null($varValue)) {
208
            throw new EntityException(sprintf("property '\$%s' of %s is null", $varName, get_class($this)));
209
        }
210
        if (!is_array($varValue)) {
211
            throw new EntityException(sprintf("property '\$%s' of %s is not array", $varName, get_class($this)));
212
        }
213
        if (count($varValue) == 0) {
214
            throw new EntityException(sprintf("property '\$%s' of %s does not have definitions", $varName, get_class($this)));
215
        }
216
    }
217
218
    /**
219
     * @param string $originName
220
     *
221
     * @return Column
222
     * @throws EntityException
223
     */
224
    public function findColumnByOriginName(string $originName): Column
225
    {
226
        foreach ($this->getColumns() as $column) {
227
            if ($column->name == $originName) {
228
                return $column;
229
            }
230
        }
231
        throw new EntityException(sprintf("Can't find origin column '%s' in %s", $originName, get_class($this)));
232
    }
233
234
    /**
235
     * @return Column[]
236
     */
237
    public function getColumns(): array
238
    {
239
        return MapperCache::me()->columns[$this->name()];
240
    }
241
242
    /**
243
     * @return mixed
244
     */
245
    public function getTable()
246
    {
247
        $thisName = $this->name();
248
249
        if (!isset(MapperCache::me()->table[$thisName])) {
250
            $parentClass = $this->getEntityClass();
251
            $table = new Table();
252
            /** @var Entity $parentClass */
253
            $table->name = $parentClass::TABLE;
254
            $table->scheme = $parentClass::SCHEME;
255
            $table->columns = $this->getColumns();
256
            $table->constraints = $this->getConstraints();
257
            $table->keys = $this->getPrimaryKey();
258
            $table->annotation = $this->getAnnotation();
259
260
            MapperCache::me()->table[$thisName] = $table;
261
        }
262
263
        return MapperCache::me()->table[$thisName];
264
    }
265
266
    /**
267
     * Returns Entity class name which uses this Mapper
268
     *
269
     * @return string
270
     */
271
    public function getEntityClass(): string
272
    {
273
        return substr(get_class($this), 0, strlen(self::POSTFIX) * -1);
274
    }
275
276
    /**
277
     * @return Constraint[]
278
     */
279
    public function getConstraints(): array
280
    {
281
        return MapperCache::me()->constraints[$this->name()];
282
    }
283
284
    /**
285
     * @return Column[] that is associative array where key is property name
286
     */
287
    public function getPrimaryKey(): array
288
    {
289
        $keys = [];
290
        foreach (MapperCache::me()->columns[$this->name()] as $columnName => $column) {
291
            if (isset($column->key) and $column->key === true) {
292
                $keys[$columnName] = $column;
293
            }
294
        }
295
296
        return $keys;
297
    }
298
299
    /**
300
     * Returns table comment
301
     *
302
     * @return string
303
     */
304
    public function getAnnotation(): string
305
    {
306
        return $this::ANNOTATION;
307
    }
308
309
    /**
310
     * @return Mapper|static
311
     * @throws EntityException
312
     */
313
    public static function meWithoutEnforcer(): Mapper
314
    {
315
        return self::instantiate(false);
316
    }
317
318
    /**
319
     * @return mixed
320
     * @deprecated
321
     * @see Mapper::getColumns()
322
     * @todo Remove in 2.4 version
323
     */
324
    public function getBaseColumns()
325
    {
326
        return MapperCache::me()->baseColumns[$this->name()];
327
    }
328
329
    /**
330
     * Special getter to access protected and private properties
331
     * @param string $property
332
     *
333
     * @return mixed
334
     * @throws EntityException
335
     */
336
    public function __get(string $property)
337
    {
338
        if (!property_exists($this, $property)) {
339
            throw new EntityException(sprintf("Can't find property '\$%s' of '%s'", $property, get_class($this)));
340
        }
341
342
        return $this->$property;
343
    }
344
345
    /**
346
     * @return Complex[]
347
     */
348
    public function getComplex(): array
349
    {
350
        return MapperCache::me()->complex[$this->name()];
351
    }
352
353
    /**
354
     * @return Embedded[]
355
     */
356
    public function getEmbedded(): array
357
    {
358
        return MapperCache::me()->embedded[$this->name()];
359
    }
360
361
    /**
362
     * @param Column $column
363
     *
364
     * @return int|string
365
     * @throws EntityException
366
     */
367
    public function getVarNameByColumn(Column $column)
368
    {
369
        foreach ($this->getOriginFieldNames() as $varName => $originFieldName) {
370
            if ($originFieldName == $column->name) {
371
                return $varName;
372
            }
373
        }
374
375
        throw new EntityException(sprintf("Seems column '%s' does not belong to this mapper", $column->name));
376
    }
377
378
    /**
379
     * @return array
380
     */
381
    public function getOriginFieldNames(): array
382
    {
383
        $thisName = $this->name();
384
        if (!isset(MapperCache::me()->originFieldNames[$thisName])) {
385
            if (count($this->getColumns())) {
386
                foreach ($this->getColumns() as $columnName => $column) {
387
                    MapperCache::me()->originFieldNames[$thisName][$columnName] = $column->name;
388
                }
389
            } else {
390
                MapperCache::me()->originFieldNames[$thisName] = [];
391
            }
392
        }
393
394
        return MapperCache::me()->originFieldNames[$thisName];
395
    }
396
}
397