Completed
Push — master ( 6d9f7a...ddd29f )
by Igor
03:01
created

FileBehavior::afterSave()   C

Complexity

Conditions 11
Paths 43

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 11

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 37
ccs 28
cts 28
cp 1
rs 5.2653
cc 11
eloc 21
nc 43
nop 0
crap 11

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @link https://github.com/rkit/filemanager-yii2
5
 * @copyright Copyright (c) 2015 Igor Romanov
6
 * @license [MIT](http://opensource.org/licenses/MIT)
7
 */
8
9
namespace rkit\filemanager\behaviors;
10
11
use Yii;
12
use yii\base\Behavior;
13
use yii\db\ActiveRecord;
14
use yii\helpers\ArrayHelper;
15
16
class FileBehavior extends Behavior
17
{
18
    /**
19
     * @var array
20
     */
21
    public $attributes = [];
22
    /**
23
     * @var ActiveQuery
24
     */
25
    private $relation;
26
    /**
27
     * @var FileBind
28
     */
29
    private $fileBind;
30
31
    /**
32
     * @internal
33
     */
34 31
    public function init()
35
    {
36 31
        parent::init();
37
38 31
        $this->fileBind = new FileBind();
39
40 31
        Yii::$app->fileManager->registerTranslations();
41 31
    }
42
43
    /**
44
     * @inheritdoc
45
     * @internal
46
     */
47 31
    public function events()
48
    {
49
        return [
50 31
            ActiveRecord::EVENT_AFTER_INSERT  => 'afterSave',
51 31
            ActiveRecord::EVENT_AFTER_UPDATE  => 'afterSave',
52 31
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
53 31
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
54 31
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
55 31
        ];
56
    }
57
58
    /**
59
     * @internal
60
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
61
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
62
     */
63 22
    public function beforeSave($insert)
64
    {
65 22
        foreach ($this->attributes as $attribute => $options) {
66 22
            $oldValue = $this->owner->isNewRecord ? null : $this->owner->getOldAttribute($attribute);
67 22
            $isAttributeChanged = $oldValue === null ? true : $this->owner->isAttributeChanged($attribute);
68
69 22
            $this->attributes[$attribute]['isAttributeChanged'] = $isAttributeChanged;
70 22
            $this->attributes[$attribute]['oldValue'] = $oldValue;
71 22
        }
72 22
    }
73
74
    /**
75
     * @internal
76
     */
77 22
    public function afterSave()
78 1
    {
79 22
        foreach ($this->attributes as $attribute => $options) {
80 22
            $files = $this->owner->{$attribute};
81
82 22
            $isAttributeNotChanged = $options['isAttributeChanged'] === false || $files === null;
83 22
            if ($isAttributeNotChanged) {
84 1
                continue;
85
            }
86
87 22
            if (is_numeric($files)) {
88 14
                $files = [$files];
89 14
            }
90
91 22
            if (is_array($files)) {
92 21
                $files = array_filter($files);
93 21
            }
94
95 22
            if ($files === [] || $files === '') {
96 3
                $this->fileBind->delete($this->owner, $attribute, $this->allFiles($attribute));
97 3
                continue;
98
            }
99
100 21
            $maxFiles = ArrayHelper::getValue($this->fileRules($attribute, true), 'maxFiles');
101 21
            if (is_array($files) && $maxFiles !== null) {
102 21
                $files = array_slice($files, 0, $maxFiles, true);
103 21
            }
104
105 21
            $files = $this->fileBind->bind($this->owner, $attribute, $files);
106 21
            if (is_array($files)) {
107 20
                $files = array_shift($files);
108 20
            }
109
110 21
            $this->clearState($attribute);
111 21
            $this->setValue($attribute, $files, $options['oldValue']);
112 22
        }
113 22
    }
114
115
    /**
116
     * @internal
117
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
118
     */
119 1
    public function beforeDelete()
120
    {
121 1
        foreach ($this->attributes as $attribute => $options) {
122 1
            $this->fileBind->delete($this->owner, $attribute, $this->allFiles($attribute));
123 1
        }
124 1
    }
125
126 21
    private function clearState($attribute)
127
    {
128 21
        $state = Yii::$app->session->get(Yii::$app->fileManager->sessionName);
129 21
        unset($state[$attribute]);
130 21
        Yii::$app->session->set(Yii::$app->fileManager->sessionName, $state);
131 21
    }
132
133 25
    private function setState($attribute, $file)
134
    {
135 25
        $state = Yii::$app->session->get(Yii::$app->fileManager->sessionName);
136 25
        if (!is_array($state)) {
137 25
            $state = [];
138 25
        }
139 25
        $state[$attribute][] = $file->getPrimaryKey();
140 25
        Yii::$app->session->set(Yii::$app->fileManager->sessionName, $state);
141 25
    }
142
143 21
    private function setValue($attribute, $file, $defaultValue)
144
    {
145 21
        $saveFilePath = $this->fileOption($attribute, 'saveFilePathInAttribute');
146 21
        $saveFileId = $this->fileOption($attribute, 'saveFileIdInAttribute');
147
148 21
        if ($saveFilePath || $saveFileId) {
149 21
            if (!$file) {
150 2
                $value = $defaultValue;
151 21
            } elseif ($saveFilePath) {
152 19
                $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
153 19
                $value = Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $handlerTemplatePath($file);
154 20
            } elseif ($saveFileId) {
155 1
                $value = $file->getPrimaryKey();
156 1
            }
157 21
            $this->owner->{$attribute} = $value;
0 ignored issues
show
Bug introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
158 21
            $this->owner->updateAttributes([$attribute => $value]);
159 21
        }
160 21
    }
161
162
    /**
163
     * Generate a thumb
164
     *
165
     * @param string $attribute The attribute name
166
     * @param string $preset The preset name
167
     * @param string $path The file path
168
     * @return string The thumb path
169
     */
170 25
    private function generateThumb($attribute, $preset, $path)
171
    {
172 25
        $thumbPath = pathinfo($path, PATHINFO_FILENAME);
173 25
        $thumbPath = str_replace($thumbPath, $preset . '_' . $thumbPath, $path);
174 25
        $realPath = $this->fileStorage($attribute)->path;
175
176 25
        if (!file_exists($realPath . $thumbPath) && file_exists($realPath . $path)) {
177 25
            $handlerPreset = $this->fileOption($attribute, 'preset.'.$preset);
178 25
            $handlerPreset($realPath, $path, $thumbPath);
179 25
        }
180
181 25
        return $thumbPath;
182
    }
183
184
    /**
185
     * Generate file path by template
186
     *
187
     * @param string $attribute The attribute name
188
     * @param ActiveRecord $file The file model
189
     * @return string The file path
190
     */
191 25
    private function templatePath($attribute, $file = null)
192
    {
193 25
        $value = $this->owner->{$attribute};
194
195 25
        $saveFilePath = $this->fileOption($attribute, 'saveFilePathInAttribute');
196 25
        $isFilledPath = $saveFilePath && !empty($value);
197
198 25
        $saveFileId = $this->fileOption($attribute, 'saveFileIdInAttribute');
199 25
        $isFilledId = $saveFileId && is_numeric($value) && $value;
200
201 25
        if (($isFilledPath || $isFilledId) && $file === null) {
202 8
            $file = $this->file($attribute);
203 8
        }
204
205 25
        if ($file !== null) {
206 25
            $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
207 25
            return $handlerTemplatePath($file);
208
        }
209 1
        return $value;
210
    }
211
212
    /**
213
     * Get relation
214
     *
215
     * @param string $attribute The attribute name
216
     * @return \ActiveQuery
217
     */
218 22
    public function fileRelation($attribute)
219
    {
220 22
        if ($this->relation === null) {
221 22
            $this->relation = $this->owner->getRelation($this->fileOption($attribute, 'relation'));
222 22
        }
223 22
        return $this->relation;
224
    }
225
226
    /**
227
     * Get file option
228
     *
229
     * @param string $attribute The attribute name
230
     * @param string $option Option name
231
     * @param mixed $defaultValue Default value
232
     * @return mixed
233
     */
234 30
    public function fileOption($attribute, $option, $defaultValue = null)
235
    {
236 30
        return ArrayHelper::getValue($this->attributes[$attribute], $option, $defaultValue);
237
    }
238
239
    /**
240
     * Get file storage
241
     *
242
     * @param string $attribute The attribute name
243
     * @return \Flysystem
244
     */
245 26
    public function fileStorage($attribute)
246
    {
247 26
        return Yii::$app->get($this->fileOption($attribute, 'storage'));
248
    }
249
250
    /**
251
     * Get file path
252
     *
253
     * @param string $attribute The attribute name
254
     * @param ActiveRecord $file Use this file model
255
     * @return string The file path
256
     */
257 11
    public function filePath($attribute, $file = null)
258
    {
259 11
        $path = $this->templatePath($attribute, $file);
260 11
        return $this->fileStorage($attribute)->path . $path;
261
    }
262
263
    /**
264
     * Get file url
265
     *
266
     * @param string $attribute The attribute name
267
     * @param ActiveRecord $file Use this file model
268
     * @return string The file url
269
     */
270 25
    public function fileUrl($attribute, $file = null)
271
    {
272 25
        $path = $this->templatePath($attribute, $file);
273 25
        return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $path;
274
    }
275
276
    /**
277
     * Get extra fields of file
278
     *
279
     * @param string $attribute The attribute name
280
     * @return array
281
     */
282 2
    public function fileExtraFields($attribute)
283
    {
284 2
        $fields = $this->fileBind->relations($this->owner, $attribute);
285 2
        if (!$this->fileOption($attribute, 'multiple')) {
286 1
            return array_shift($fields);
287
        }
288 1
        return $fields;
289
    }
290
291
    /**
292
     * Get files
293
     *
294
     * @param string $attribute The attribute name
295
     * @return \ActiveRecord[] The file models
296
     */
297 9
    public function allFiles($attribute)
298
    {
299 9
        return $this->fileBind->files($this->owner, $attribute);
300
    }
301
302
    /**
303
     * Get the file
304
     *
305
     * @param string $attribute The attribute name
306
     * @return \ActiveRecord The file model
307
     */
308 12
    public function file($attribute)
309
    {
310 12
        return $this->fileBind->file($this->owner, $attribute);
311
    }
312
313
    /**
314
     * Get rules
315
     *
316
     * @param string $attribute The attribute name
317
     * @param bool $onlyCoreValidators Only core validators
318
     * @return array
319
     */
320 29
    public function fileRules($attribute, $onlyCoreValidators = false)
321
    {
322 29
        $rules = $this->fileOption($attribute, 'rules', []);
323 29
        if ($onlyCoreValidators && isset($rules['imageSize'])) {
324 28
            $rules = array_merge($rules, $rules['imageSize']);
325 28
            unset($rules['imageSize']);
326 28
        }
327 29
        return $rules;
328
    }
329
330
    /**
331
     * Get file state
332
     *
333
     * @param string $attribute The attribute name
334
     * @return array
335
     */
336 20
    public function fileState($attribute)
337
    {
338 20
        $state = Yii::$app->session->get(Yii::$app->fileManager->sessionName);
339 20
        return ArrayHelper::getValue($state === null ? [] : $state, $attribute, []);
340
    }
341
342
    /**
343
     * Get the presets of the file for apply after upload
344
     *
345
     * @param string $attribute The attribute name
346
     * @return array
347
     */
348 25
    public function filePresetAfterUpload($attribute)
349
    {
350 25
        $preset = $this->fileOption($attribute, 'applyPresetAfterUpload', []);
351 25
        if (is_string($preset) && $preset === '*') {
352 1
            return array_keys($this->fileOption($attribute, 'preset', []));
353
        }
354 24
        return $preset;
355
    }
356
357
    /**
358
     * Create a thumb and return url
359
     *
360
     * @param string $attribute The attribute name
361
     * @param string $preset The preset name
362
     * @param ActiveRecord $file Use this file model
363
     * @return string The file url
364
     */
365 25
    public function thumbUrl($attribute, $preset, $file = null)
366
    {
367 25
        $path = $this->templatePath($attribute, $file);
368 25
        $thumbPath = $this->generateThumb($attribute, $preset, $path);
369
370 25
        return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $thumbPath;
371
    }
372
373
    /**
374
     * Create a thumb and return full path
375
     *
376
     * @param string $attribute The attribute name
377
     * @param string $preset The preset name
378
     * @param ActiveRecord $file Use this file model
379
     * @return string The file path
380
     */
381 6
    public function thumbPath($attribute, $preset, $file = null)
382
    {
383 6
        $path = $this->templatePath($attribute, $file);
384 6
        $thumbPath = $this->generateThumb($attribute, $preset, $path);
385
386 6
        return $this->fileStorage($attribute)->path . $thumbPath;
387
    }
388
389
    /**
390
     * Create a file
391
     *
392
     * @param string $attribute The attribute name
393
     * @param string $path The file path
394
     * @param string $name The file name
395
     * @return \ActiveRecord The file model
396
     */
397 25
    public function createFile($attribute, $path, $name)
398
    {
399 25
        $handlerCreateFile = $this->fileOption($attribute, 'createFile');
400 25
        $file = $handlerCreateFile($path, $name);
401 25
        if ($file) {
402 25
            $storage = $this->fileStorage($attribute);
403 25
            $contents = file_get_contents($path);
404 25
            $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
405 25
            if ($storage->write($handlerTemplatePath($file), $contents)) {
406 25
                $this->setState($attribute, $file);
407 25
                $this->owner->{$attribute} = $file->id;
408 25
                return $file;
409
            }
410
        } // @codeCoverageIgnore
411
        return false; // @codeCoverageIgnore
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by rkit\filemanager\behavio...ileBehavior::createFile of type ActiveRecord.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
412
    }
413
}
414