GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( f95cc0...b08239 )
by Ivan
10:56
created

HasProperties   D

Complexity

Total Complexity 89

Size/Duplication

Total Lines 490
Duplicated Lines 4.9 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 89
c 1
b 0
f 0
lcom 1
cbo 10
dl 24
loc 490
rs 4.8717

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getGroupIdBypropertyId() 0 15 4
A getObject() 0 10 3
F getPropertyGroups() 0 102 24
B addPropertyGroup() 0 24 3
A removePropertyGroup() 0 10 1
A setPropertiesFormName() 0 4 1
F saveProperties() 0 69 17
A updatePropertyGroupsInformation() 0 10 1
A getAbstractModel() 0 7 2
A setAbstractModel() 0 4 1
A getPropertyValuesByKey() 12 12 4
B property() 0 27 5
A getPropertyValuesByPropertyId() 12 12 4
C getPropertyValues() 0 26 8
B getEavRows() 0 30 3
B getTableInheritanceRow() 0 30 4
B getStaticValues() 0 37 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like HasProperties 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 HasProperties, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace app\properties;
4
5
use app\models\Object;
6
use app\models\ObjectPropertyGroup;
7
use app\models\ObjectStaticValues;
8
use app\models\Property;
9
use app\models\PropertyGroup;
10
use app\models\PropertyStaticValues;
11
use app\modules\core\helpers\ContentBlockHelper;
12
use app\modules\core\models\ContentBlock;
13
use devgroup\TagDependencyHelper\ActiveRecordHelper;
14
use Yii;
15
use yii\base\Behavior;
16
use yii\caching\TagDependency;
17
use yii\db\ActiveRecord;
18
use yii\db\AfterSaveEvent;
19
use yii\db\Query;
20
use yii\helpers\ArrayHelper;
21
use yii\helpers\Json;
22
23
/**
24
 * Class HasProperties
25
 * @property \yii\db\ActiveRecord $owner
26
 * @property $object Object
27
 * @package app\properties
28
 */
29
class HasProperties extends Behavior
30
{
31
    const FIELD_ADD_PROPERTY_GROUP = 'AddPropertyGroup';
32
    const FIELD_REMOVE_PROPERTY_GROUP = 'RemovePropertyGroup';
33
    const FIELD_ADD_PROPERTY = 'AddProperty';
34
    /**
35
     * @var AbstractModel
36
     */
37
    private $abstract_model = null;
38
    private $eav_rows = null;
39
    private $object = null;
40
    private $properties = null;
41
    private $property_key_to_id = [];
42
    private $property_id_to_group_id = [];
43
    private $static_values = null;
44
    private $table_inheritance_row = null;
45
    private $propertiesFormName = null;
46
    public $props;
47
48
    /**
49
     * Get property group id by property id
50
     * @param int $id property id
51
     * @return int property group id
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
52
     */
53
    private function getGroupIdBypropertyId($id)
54
    {
55
        foreach ($this->property_id_to_group_id as $groupId => $propertyIds) {
56
            if (in_array($id, $propertyIds)) {
57
                return $groupId;
58
            }
59
        }
60
        $property = Property::findById($id);
61
        if (!array_key_exists($property->property_group_id, $this->property_id_to_group_id)) {
62
            $this->property_id_to_group_id[$property->property_group_id] = [$id];
63
        } else {
64
            $this->property_id_to_group_id[$property->property_group_id][] = $id;
65
        }
66
        return intval($property->property_group_id);
67
    }
68
69
    public function getObject()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
70
    {
71
        if ($this->object === null) {
72
            $this->object = Object::getForClass(get_class($this->owner));
73
            if ($this->object === null) {
74
                throw new \Exception("Can't find Object row for ".get_class($this->owner));
75
            }
76
        }
77
        return $this->object;
78
    }
79
80
    public function getPropertyGroups($force = false, $getByObjectId = false, $createAbstractModel = false)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
81
    {
82
        $message = "getPropertyGroups ". $this->owner->className() . ':'.$this->owner->id.
83
            " force:".($force?'true':'false').
84
            " getByObjectId:".($getByObjectId?'true':'false') .
85
            " createAbstractModel:".($createAbstractModel?'true':'false');
86
        Yii::trace(
87
            $message,
88
            'properties'
89
        );
90
        $cacheKey = $message;
91
92
93
        if ($this->properties === null && $force === false) {
94
            // try to get from cache
95
            $data = Yii::$app->cache->get($message);
96
            if ($data !== false) {
97
                Yii::trace("Properties from cache", 'properties');
98
                $this->abstract_model = isset($data['abstract_model'])?$data['abstract_model']:[];
99
                $this->properties = $data['properties'];
100
                $this->property_key_to_id = $data['property_key_to_id'];
101
                $this->property_id_to_group_id = $data['property_id_to_group_id'];
102
            }
103
        }
104
105
        if ($createAbstractModel === true && is_null($this->abstract_model)) {
106
            // force creating abstract model if it is null and needed
107
            $force = true;
108
        }
109
110
        if ($this->properties === null || $force === true) {
111
            $tags = [ActiveRecordHelper::getObjectTag($this->owner->className(), $this->owner->id)];
112
            $this->properties = [];
113
            if ($getByObjectId === true) {
114
                $groups = PropertyGroup::getForObjectId($this->getObject()->id);
115
            } else {
116
                $groups = PropertyGroup::getForModel($this->getObject()->id, $this->owner->id);
117
            }
118
            $values_for_abstract = [];
119
            $properties_models = [];
120
            $rules = [];
121
            /** @var PropertyGroup $group */
122
            foreach ($groups as $group) {
0 ignored issues
show
Bug introduced by
The expression $groups of type null|array<integer,object<yii\db\ActiveRecord>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
123
                $tags[] = ActiveRecordHelper::getObjectTag(PropertyGroup::className(), $group->id);
124
                $this->properties[$group->id] = [];
125
                $props = Property::getForGroupId($group->id);
126
                foreach ($props as $p) {
0 ignored issues
show
Bug introduced by
The expression $props of type null|array<integer,object<app\models\Property>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
127
                    $values = $this->getPropertyValues($p);
128
                    $this->properties[$group->id][$p->key] = $values;
129
130
                    if ($createAbstractModel === true) {
131
                        $values_for_abstract[$p->key] = $values;
132
                    }
133
134
                    $properties_models[$p->key] = $p;
135
                    $this->property_key_to_id[$p->key] = $p->id;
136
                    if (!isset($this->property_id_to_group_id[$group->id])) {
137
                        $this->property_id_to_group_id[$group->id] = [$p->key];
138
                    } else {
139
                        $this->property_id_to_group_id[$group->id][] = $p->key;
140
                    }
141
142
                    if ($createAbstractModel === true) {
143
                        $handlerAdditionalParams = Json::decode($p->handler_additional_params);
144
                        if (isset($handlerAdditionalParams['rules']) && is_array($handlerAdditionalParams['rules'])) {
145
                            foreach ($handlerAdditionalParams['rules'] as $rule) {
146
                                if (is_array($rule)) {
147
                                    $rules[] = ArrayHelper::merge([$p->key], $rule);
148
                                } else {
149
                                    $rules[] = [$p->key, $rule];
150
                                }
151
                            }
152
                        }
153
                    }
154
                }
155
            }
156
157
            if ($createAbstractModel === true) {
158
                $this->abstract_model = new AbstractModel([], $this->owner);
159
                $this->abstract_model->setPropertiesModels($properties_models);
160
                $this->abstract_model->setAttributes($values_for_abstract);
161
                $this->abstract_model->setFormName('Properties_' . $this->owner->formName() . '_' . $this->owner->id);
162
                $this->abstract_model->addRules($rules);
163
            }
164
165
            $res = Yii::$app->cache->set(
166
                $cacheKey,
167
                [
168
                    'properties' => $this->properties,
169
                    'abstract_model' => $this->abstract_model,
170
                    'property_id_to_group_id' => $this->property_id_to_group_id,
171
                    'property_key_to_id' => $this->property_key_to_id,
172
                ],
173
                86400,
174
                new TagDependency([
175
                    'tags' => $tags,
176
                ])
177
            );
178
            Yii::trace('putting props to cache: ' . ($res?'true':'false') . ' key:' . $cacheKey, 'properties');
179
        }
180
        return $this->properties;
181
    }
182
183
    /**
184
     * Adds property group to object instance
185
     *
186
     * @param int|string $property_group_id Id of Property Group
187
     * @param bool $refreshProperties
188
     * @throws \Exception
189
     * @throws \yii\db\Exception
190
     */
191
    public function addPropertyGroup($property_group_id, $refreshProperties = true, $createAbstractModel = false)
192
    {
193
        $params = [];
194
        $where = [
195
            'object_id' => $this->getObject()->id,
196
            'object_model_id' => $this->owner->id,
197
            'property_group_id' => $property_group_id,
198
        ];
199
        if (null === ObjectPropertyGroup::findOne($where)) {
200
            $sql = Yii::$app->db->queryBuilder->insert(
201
                ObjectPropertyGroup::tableName(),
202
                $where,
203
                $params
204
            );
205
206
            Yii::$app->db->createCommand(
207
                $sql,
208
                $params
209
            )->execute();
210
        }
211
        if ($refreshProperties === true) {
212
            $this->updatePropertyGroupsInformation($createAbstractModel);
213
        }
214
    }
215
216
    public function removePropertyGroup($property_group_id)
217
    {
218
        ObjectPropertyGroup::deleteAll(
219
            [
220
                'object_id' => $this->getObject()->id,
221
                'object_model_id' => $this->owner->id,
222
                'property_group_id' => $property_group_id,
223
            ]
224
        );
225
    }
226
227
    public function setPropertiesFormName($name = null)
228
    {
229
        $this->propertiesFormName = $name;
230
    }
231
232
    public function saveProperties($data)
233
    {
234
        $form = $this->owner->formName();
235
        $formProperties = empty($this->propertiesFormName)
236
            ? 'Properties_'.$form.'_'.$this->owner->id
237
            : $this->propertiesFormName;
238
239
        $this->getPropertyGroups();
240
        if (isset($data[self::FIELD_ADD_PROPERTY_GROUP]) && isset($data[self::FIELD_ADD_PROPERTY_GROUP][$form])) {
241
            $groups_to_add = is_array($data[self::FIELD_ADD_PROPERTY_GROUP][$form])
242
                ? $data[self::FIELD_ADD_PROPERTY_GROUP][$form]
243
                : [$data[self::FIELD_ADD_PROPERTY_GROUP][$form]];
244
            foreach ($groups_to_add as $group_id) {
245
                $this->addPropertyGroup($group_id, false);
246
            }
247
            $this->updatePropertyGroupsInformation(true);
248
        }
249
        if (isset($data[self::FIELD_REMOVE_PROPERTY_GROUP]) && isset($data[self::FIELD_REMOVE_PROPERTY_GROUP][$form])) {
250
            $this->removePropertyGroup($data[self::FIELD_REMOVE_PROPERTY_GROUP][$form]);
251
        }
252
        if (isset($data[$formProperties])) {
253
            $my_data = $data[$formProperties];
254
255
            if (isset($data[self::FIELD_ADD_PROPERTY])) {
256
                // admin clicked add property button for multiple properties
257
                if (!isset($my_data[$data[self::FIELD_ADD_PROPERTY]])) {
258
                    $my_data[$data[self::FIELD_ADD_PROPERTY]] = [];
259
                }
260
                $my_data[$data[self::FIELD_ADD_PROPERTY]][] = '';
261
            }
262
263
            $propertiesModels = $this->getAbstractModel()->getPropertiesModels();
264
265
            $new_values_for_abstract = [];
266
            foreach ($my_data as $property_key => $values) {
267
                if (isset($this->property_key_to_id[$property_key])) {
268
                    $vals = [];
269
                    $property_id = $this->property_key_to_id[$property_key];
270
                    $values = is_array($values) ? $values : [$values];
271
272
                    if (isset($propertiesModels[$property_key])) {
273
                        $_property = $propertiesModels[$property_key];
274
                        $propertyHandler = PropertyHandlers::getHandlerById($_property->property_handler_id);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $propertyHandler is correct as \app\properties\Property...y->property_handler_id) (which targets app\properties\PropertyHandlers::getHandlerById()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
275
                        if (null === $propertyHandler) {
276
                            $propertyHandler = PropertyHandlers::createHandler($_property->handler);
277
                        }
278
279
                        $values = $propertyHandler->processValues($_property, $formProperties, $values);
280
                    }
281
282
                    foreach ($values as $index => $val) {
283
                        $vals[] = ['value' => $val, 'property_id' => $property_id, 'sort_order' => $index];
284
                    }
285
286
                    $val = new PropertyValue(
287
                        $vals,
288
                        $property_id,
289
                        $this->getObject()->id,
290
                        $this->owner->id,
291
                        $this->getGroupIdBypropertyId($property_id)
292
                    );
293
                    $new_values_for_abstract[$property_key] = $val;
294
                }
295
            }
296
297
            $this->abstract_model->updateValues($new_values_for_abstract, $this->getObject()->id, $this->owner->id);
298
            $this->owner->trigger(ActiveRecord::EVENT_AFTER_UPDATE, new AfterSaveEvent(['changedAttributes' => []]));
299
        }
300
    }
301
302
    public function updatePropertyGroupsInformation($createAbstractModel = false)
303
    {
304
        $this->owner->invalidateTags();
305
        $this->table_inheritance_row = null;
306
        $this->eav_rows = null;
307
        $this->static_values = null;
308
        $this->abstract_model = null;
309
        $this->property_key_to_id = [];
310
        $this->getPropertyGroups(true, false, $createAbstractModel);
311
    }
312
313
    public function getAbstractModel()
314
    {
315
        if (empty($this->abstract_model)) {
316
            $this->getPropertyGroups(!is_object($this->abstract_model), false, true);
317
        }
318
        return $this->abstract_model;
319
    }
320
321
    public function setAbstractModel(AbstractModel $model)
322
    {
323
        $this->abstract_model = $model;
324
    }
325
326
    /**
327
     * Get PropertyValue model by key. It consists object property values
328
     * @param string $key
329
     * @return PropertyValue
330
     */
331 View Code Duplication
    public function getPropertyValuesByKey($key)
332
    {
333
        $this->getPropertyGroups();
334
        foreach ($this->properties as $group_id => $group) {
335
            foreach ($group as $property_key => $value) {
336
                if ($property_key === $key) {
337
                    return $value;
338
                }
339
            }
340
        }
341
        return null;
342
    }
343
344
    /**
345
     * Get property values by key.
346
     * @param $key
347
     * @param bool $asString
348
     * @param string $delimiter
349
     * @return array|string
350
     */
351
    public function property($key, $asString = true, $delimiter = ', ')
352
    {
353
        $values = [];
354
        $propertyValue = $this->getPropertyValuesByKey($key);
355
        if (is_null($propertyValue)) {
356
            return $asString ? '' : [];
357
        }
358
        foreach ($propertyValue->values as $value) {
359
            $values[] = $value['value'];
360
        }
361
        if ($asString) {
362
            return ContentBlockHelper::compileContentString(
363
                implode($delimiter, $values),
364
                "{$this->owner->className()}:{$this->owner->id}:property={$key}",
365
                new TagDependency(
366
                    [
367
                        'tags' => [
368
                            $this->owner->objectTag(),
369
                            ActiveRecordHelper::getCommonTag(ContentBlock::className())
370
                        ]
371
                    ]
372
                )
373
            );
374
        } else {
375
            return $values;
376
        }
377
    }
378
379 View Code Duplication
    public function getPropertyValuesByPropertyId($property_id)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
380
    {
381
        $this->getPropertyGroups();
382
        foreach ($this->properties as $group_id => $group) {
383
            foreach ($group as $property_key => $value) {
384
                if ($value->property_id == $property_id) {
385
                    return $value;
386
                }
387
            }
388
        }
389
        return null;
390
    }
391
392
    private function getPropertyValues($property)
393
    {
394
        $values = [];
395
        if ($property->is_column_type_stored) {
396
            $row = $this->getTableInheritanceRow();
397
            if (isset($row[$property->key])) {
398
                $values[] = [
399
                    'value' => $row[$property->key],
400
                    'property_id' => $property->id,
401
                ];
402
            }
403
        } elseif ($property->has_static_values) {
404
            $static_values = $this->getStaticValues();
405
            if (isset($static_values[$property->id])) {
406
                $values = $static_values[$property->id];
407
            }
408
        } elseif ($property->is_eav) {
409
            foreach ($this->getEavRows() as $row) {
410
                if ($row['key'] === $property->key) {
411
                    $values[] = $row;
412
                }
413
            }
414
415
        }
416
        return new PropertyValue($values, $property->id, $this->getObject()->id, $this->owner->id);
417
    }
418
419
    private function getEavRows()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
420
    {
421
        if ($this->eav_rows === null) {
422
            $cacheKey = md5("EAV_ROWS:".$this->getObject()->eav_table_name.":".$this->owner->id);
423
424
            $this->eav_rows = Yii::$app->cache->get($cacheKey);
425
426
            if ($this->eav_rows === false) {
427
                $this->eav_rows =
428
                    (new Query())
429
                        ->select('`key`, value, sort_order, id as eav_id')
430
                        ->from($this->getObject()->eav_table_name)
431
                        ->where(['object_model_id' => $this->owner->id])
432
                        ->orderBy('sort_order')
433
                        ->all();
434
435
                Yii::$app->cache->set(
436
                    $cacheKey,
437
                    $this->eav_rows,
438
                    86400,
439
                    new TagDependency([
440
                        'tags' => [
441
                            ActiveRecordHelper::getObjectTag($this->owner->className(), $this->owner->id)
442
                        ]
443
                    ])
444
                );
445
            }
446
        }
447
        return $this->eav_rows;
448
    }
449
450
    private function getTableInheritanceRow()
451
    {
452
        if ($this->table_inheritance_row === null) {
453
            $cacheKey = "TIR:" . $this->getObject()->id . ':' . $this->owner->id;
454
            $this->table_inheritance_row = Yii::$app->cache->get($cacheKey);
455
            if (!is_array($this->table_inheritance_row)) {
456
                $this->table_inheritance_row = (new Query())
457
                    ->select('*')
458
                    ->from($this->getObject()->column_properties_table_name)
459
                    ->where(['object_model_id' => $this->owner->id])
460
                    ->one();
461
                if (!is_array($this->table_inheritance_row)) {
462
                    $this->table_inheritance_row = [];
463
                }
464
                Yii::$app->cache->set(
465
                    $cacheKey,
466
                    $this->table_inheritance_row,
467
                    86400,
468
                    new TagDependency(
469
                        [
470
                            'tags' => [
471
                                ActiveRecordHelper::getObjectTag($this->owner, $this->owner->id),
472
                            ],
473
                        ]
474
                    )
475
                );
476
            }
477
        }
478
        return $this->table_inheritance_row;
479
    }
480
481
    private function getStaticValues()
482
    {
483
        if ($this->static_values === null) {
484
            $cacheKey = "PSV:" . $this->getObject()->id . ":" . $this->owner->id;
485
            $array = Yii::$app->cache->get($cacheKey);
486
            if (!is_array($array)) {
487
                $array = (new Query())
488
                    ->select("PSV.property_id, PSV.value, PSV.slug, PSV.name, PSV.id as psv_id")
489
                    ->from(ObjectStaticValues::tableName() . " OSV")
490
                    ->innerJoin(PropertyStaticValues::tableName() . " PSV", "PSV.id = OSV.property_static_value_id")
491
                    ->where(
492
                        [
493
                            'OSV.object_id' => $this->getObject()->id,
494
                            'OSV.object_model_id' => $this->owner->id,
495
                        ]
496
                    )->orderBy('PSV.sort_order')
497
                    ->all();
498
                Yii::$app->cache->set(
499
                    $cacheKey,
500
                    $array,
501
                    86400,
502
                    new TagDependency(
503
                        [
504
                            'tags' => [
505
                                ActiveRecordHelper::getObjectTag($this->owner, $this->owner->id),
506
                            ],
507
                        ]
508
                    )
509
                );
510
            }
511
            $this->static_values = [];
512
            foreach ($array as $row) {
513
                $this->static_values[$row['property_id']][$row['value']] = $row;
514
            }
515
        }
516
        return $this->static_values;
517
    }
518
}
519