FileBehaviour   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 1
Metric Value
wmc 46
eloc 109
c 5
b 1
f 1
dl 0
loc 245
rs 8.72

10 Methods

Rating   Name   Duplication   Size   Complexity  
A events() 0 7 1
C attach() 0 56 15
A canSetProperty() 0 6 2
B validateRequiredFields() 0 13 7
A canGetProperty() 0 4 2
A filesDelete() 0 5 1
B filesSave() 0 27 8
B __get() 0 37 7
A __set() 0 7 2
A getRealAttributeName() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like FileBehaviour 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 FileBehaviour, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: floor12
5
 * Date: 31.12.2017
6
 * Time: 15:14
7
 */
8
9
namespace floor12\files\components;
10
11
12
use floor12\files\models\File;
13
use Yii;
14
use yii\base\Behavior;
15
use yii\base\ErrorException;
16
use yii\db\ActiveRecord;
17
use yii\db\Expression;
18
use yii\validators\Validator;
19
20
class FileBehaviour extends Behavior
21
{
22
    /** Массив для хранения файловых атрибутов и их параметров.
23
     *  Задается через Behaviors в моделе
24
     * @var array
25
     */
26
    public $attributes = [];
27
28
    /** В этот массив помещаются id связанных файлов с текущей моделью для последующейго сохранения.
29
     * @var array
30
     */
31
    private $_values = [];
32
33
    /**
34
     * Вещаем сохранение данных на события.
35
     */
36
    public function events()
37
    {
38
        return [
39
            ActiveRecord::EVENT_AFTER_INSERT => 'filesSave',
40
            ActiveRecord::EVENT_AFTER_UPDATE => 'filesSave',
41
            ActiveRecord::EVENT_AFTER_DELETE => 'filesDelete',
42
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'validateRequiredFields'
43
        ];
44
    }
45
46
    protected $cachedFiles = [];
47
48
49
    /**
50
     * Метод сохранения в базу связей с файлами. Вызывается после сохранения основной модели AR.
51
     * @throws ErrorException
52
     * @throws \yii\db\Exception
53
     */
54
55
    public function filesSave()
56
    {
57
        $order = 0;
58
        if ($this->_values) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
59
60
            foreach ($this->_values as $field => $ids) {
61
62
                Yii::$app->db->createCommand()->update(
63
                    "{{%file}}",
64
                    ['object_id' => 0],
65
                    [
66
                        'class' => $this->owner->className(),
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

66
                        'class' => /** @scrutinizer ignore-deprecated */ $this->owner->className(),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
Bug introduced by
The method className() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
                        'class' => $this->owner->/** @scrutinizer ignore-call */ className(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
67
                        'object_id' => $this->owner->id,
68
                        'field' => $field,
69
                    ]
70
                )->execute();
71
72
                if ($ids) foreach ($ids as $id) {
73
                    if (empty($id))
74
                        continue;
75
                    $file = File::findOne($id);
76
                    if ($file) {
77
                        $file->object_id = $this->owner->id;
78
                        $file->ordering = $order++;
79
                        $file->save();
80
                        if (!$file->save()) {
81
                            throw new ErrorException('Невозможно обновить объект File.');
82
                        }
83
                    }
84
85
                }
86
            }
87
        }
88
    }
89
90
    public function filesDelete()
91
    {
92
        File::deleteAll([
93
            'class' => $this->owner->className(),
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

93
            'class' => /** @scrutinizer ignore-deprecated */ $this->owner->className(),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
94
            'object_id' => $this->owner->id,
95
        ]);
96
    }
97
98
    public function validateRequiredFields()
99
    {
100
        foreach ($this->attributes as $attributeName => $params) {
101
            $attributeIds = $this->getRealAttributeName($attributeName);
102
103
            if (
104
                isset($params['required']) &&
105
                $params['required'] &&
106
                in_array($this->owner->scenario, $params['requiredOn']) &&
107
                !in_array($this->owner->scenario, $params['requiredExcept']) &&
108
                !isset($this->_values[$attributeIds][1])
109
            )
110
                $this->owner->addError($attributeName, $params['requiredMessage']);
111
        }
112
    }
113
114
    /**
115
     * Устанавливаем валидаторы.
116
     * @param ActiveRecord $owner
117
     */
118
    public
119
    function attach($owner)
120
    {
121
        parent::attach($owner);
122
123
        // Получаем валидаторы AR
124
        $validators = $owner->validators;
125
126
        // Пробегаемся по валидаторам и вычисляем, какие из них касаются наших файл-полей
127
        if ($validators)
128
            foreach ($validators as $key => $validator) {
129
130
                // Сначала пробегаемся по файловым валидаторам
131
                if ($validator::className() == 'yii\validators\FileValidator' || $validator::className() == 'floor12\files\components\ReformatValidator') {
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

131
                if ($validator::className() == 'yii\validators\FileValidator' || /** @scrutinizer ignore-deprecated */ $validator::className() == 'floor12\files\components\ReformatValidator') {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
132
                    foreach ($this->attributes as $field => $params) {
133
134
                        if (is_string($params)) {
135
                            $field = $params;
136
                            $params = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $params is dead and can be removed.
Loading history...
137
                        }
138
139
                        $index = array_search($field, $validator->attributes);
0 ignored issues
show
Bug introduced by
It seems like $validator->attributes can also be of type string; however, parameter $haystack of array_search() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
                        $index = array_search($field, /** @scrutinizer ignore-type */ $validator->attributes);
Loading history...
140
                        if ($index !== false) {
141
                            $this->attributes[$field]['validator'][$validator::className()] = $validator;
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

141
                            $this->attributes[$field]['validator'][/** @scrutinizer ignore-deprecated */ $validator::className()] = $validator;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
142
                            unset($validator->attributes[$index]);
143
                        }
144
                    }
145
                }
146
147
148
                if ($validator::className() == 'yii\validators\RequiredValidator') {
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

148
                if (/** @scrutinizer ignore-deprecated */ $validator::className() == 'yii\validators\RequiredValidator') {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
149
                    foreach ($this->attributes as $field => $params) {
150
151
                        if (is_string($params)) {
152
                            $field = $params;
153
                            $params = [];
154
                        }
155
156
                        $index = array_search($field, $validator->attributes);
157
                        if ($index !== false) {
158
                            unset($validator->attributes[$index]);
159
                            $this->attributes[$field]['required'] = true;
160
                            $this->attributes[$field]['requiredExcept'] = $validator->except;
161
                            $this->attributes[$field]['requiredOn'] = sizeof($validator->on) ? $validator->on : [ActiveRecord::SCENARIO_DEFAULT];
0 ignored issues
show
Bug introduced by
It seems like $validator->on can also be of type string; however, parameter $value of sizeof() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
                            $this->attributes[$field]['requiredOn'] = sizeof(/** @scrutinizer ignore-type */ $validator->on) ? $validator->on : [ActiveRecord::SCENARIO_DEFAULT];
Loading history...
162
                            $this->attributes[$field]['requiredMessage'] = str_replace("{attribute}", $this->owner->getAttributeLabel($field), $validator->message);
163
                        }
164
                    }
165
                }
166
167
168
            }
169
170
        // Добавляем дефолтный валидатор для прилетающих айдишников
171
        if ($this->attributes) foreach ($this->attributes as $fieldName => $fieldParams) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->attributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
172
            $validator = Validator::createValidator('safe', $owner, ["{$fieldName}_ids"]);
173
            $validators->append($validator);
174
        }
175
    }
176
177
178
    /**
179
     * @inheritdoc
180
     */
181
    public function canGetProperty($name, $checkVars = true)
182
    {
183
        return array_key_exists($name, $this->attributes) ?
184
            true : parent::canGetProperty($name, $checkVars);
185
    }
186
187
188
    /**
189
     * @inheritdoc
190
     */
191
    public function canSetProperty($name, $checkVars = true)
192
    {
193
        if (array_key_exists($this->getRealAttributeName($name), $this->attributes))
194
            return true;
195
196
        return parent::canSetProperty($name, $checkVars = true);
197
    }
198
199
200
    /**
201
     * @inheritdoc
202
     */
203
    public function __get($att_name)
204
    {
205
        if (isset($this->_values[$att_name])) {
206
            unset($this->_values[$att_name][0]);
207
            if (sizeof($this->_values[$att_name]))
208
                return array_map(function ($fileId) {
209
                    return File::findOne($fileId);
210
                }, $this->_values[$att_name]);
211
        } else {
212
            if (!isset($this->cachedFiles[$att_name])) {
213
                if (
214
                    isset($this->attributes[$att_name]['validator']) &&
215
                    isset($this->attributes[$att_name]['validator']['yii\validators\FileValidator']) &&
216
                    $this->attributes[$att_name]['validator']['yii\validators\FileValidator']->maxFiles > 1
217
                )
218
                    $this->cachedFiles[$att_name] = File::find()
219
                        ->where(
220
                            [
221
                                'object_id' => $this->owner->id,
222
                                'field' => $att_name,
223
                                'class' => $this->owner->className()
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

223
                                'class' => /** @scrutinizer ignore-deprecated */ $this->owner->className()

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
224
                            ])
225
                        ->orderBy('ordering ASC')
226
                        ->all();
227
                else {
228
                    $this->cachedFiles[$att_name] = File::find()
229
                        ->where(
230
                            [
231
                                'object_id' => $this->owner->id,
232
                                'field' => $att_name,
233
                                'class' => $this->owner->className()
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

233
                                'class' => /** @scrutinizer ignore-deprecated */ $this->owner->className()

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
234
                            ])
235
                        ->orderBy('ordering ASC')
236
                        ->one();
237
                }
238
            }
239
            return $this->cachedFiles[$att_name];
240
        }
241
    }
242
243
244
    /**
245
     * @inheritdoc
246
     */
247
    public
248
    function __set($name, $value)
249
    {
250
        $attribute = $this->getRealAttributeName($name);
251
252
        if (array_key_exists($attribute, $this->attributes))
253
            $this->_values[$attribute] = $value;
254
    }
255
256
257
    /** Отбрасываем постфикс _ids
258
     * @param $attribute string
259
     * @return string
260
     */
261
    private
262
    function getRealAttributeName($attribute)
263
    {
264
        return str_replace("_ids", "", $attribute);
265
    }
266
}
267