Behavior::deleteFiles()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
3
namespace maxmirazh33\file;
4
5
use yii\base\Exception;
6
use yii\base\InvalidArgumentException;
7
use yii\db\ActiveRecord;
8
use yii\web\UploadedFile;
9
use Yii;
10
use yii\helpers\FileHelper;
11
12
/**
13
 * Class model behavior for uploadable files
14
 *
15
 * Usage in your model:
16
 * ```
17
 * ...
18
 * public function behaviors()
19
 * {
20
 *     return [
21
 *         [
22
 *              'class' => \maxmirazh33\file\Behavior::className(),
23
 *              'savePathAlias' => '@web/files/',
24
 *              'urlPrefix' => '/files/',
25
 *              'attributes' => [
26
 *                  'image' => [
27
 *                      'savePathAlias' => '@web/images/',
28
 *                      'urlPrefix' => '/images/',
29
 *                  ],
30
 *                  'file',
31
 *              ],
32
 *         ],
33
 *     //other behaviors
34
 *     ];
35
 * }
36
 * ...
37
 * ```
38
 * @property ActiveRecord $owner
39
 */
40
class Behavior extends \yii\base\Behavior
41
{
42
    /**
43
     * @var array list of attribute as attributeName => options. Options:
44
     *  $savePathAlias @see \maxmirazh33\file\Behavior::$savePathAlias
45
     *  $urlPrefix @see \maxmirazh33\file\Behavior::$urlPrefix
46
     */
47
    public $attributes = [];
48
49
    /**
50
     * @var string. Default '@frontend/web/files/%className%/' or '@app/web/files/%className%/'
51
     */
52
    public $savePathAlias;
53
54
    /**
55
     * @var string part of url for file without hostname. Default '/files/%className%/'
56
     */
57
    public $urlPrefix;
58
59
    /**
60
     * @inheritdoc
61
     */
62
    public function events()
63
    {
64
        return [
65
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
66
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
67
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
68
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
69
        ];
70
    }
71
72
    /**
73
     * function for EVENT_BEFORE_VALIDATE
74
     */
75
    public function beforeValidate()
76
    {
77
        $model = $this->owner;
78
        foreach ($this->attributes as $attr => $options) {
79
            self::ensureAttributes($attr, $options);
80
            if ($file = UploadedFile::getInstance($model, $attr)) {
81
                $model->{$attr} = $file;
82
            }
83
        }
84
    }
85
86
    /**
87
     * function for EVENT_BEFORE_INSERT and EVENT_BEFORE_UPDATE
88
     */
89
    public function beforeSave()
90
    {
91
        $model = $this->owner;
92
        foreach ($this->attributes as $attr => $options) {
93
            self::ensureAttributes($attr, $options);
94
            if ($file = UploadedFile::getInstance($model, $attr)) {
95
                $this->createDirIfNotExists($attr);
96
                if (!$model->isNewRecord) {
97
                    $this->deleteFiles($attr);
98
                }
99
                $fileName = uniqid('', true) . '.' . $file->extension;
100
                $model->{$attr} = $fileName;
101
                $file->saveAs($this->getSavePath($attr) . $fileName);
102
            } elseif (isset($model->oldAttributes[$attr])) {
103
                $model->{$attr} = $model->oldAttributes[$attr];
104
            }
105
        }
106
    }
107
108
    /**
109
     * @param ActiveRecord $object
110
     * @return string
111
     * @throws \ReflectionException
112
     */
113
    private function getShortClassName($object)
114
    {
115
        $obj = new \ReflectionClass($object);
116
        return mb_strtolower($obj->getShortName());
117
    }
118
119
    /**
120
     * function for EVENT_BEFORE_DELETE
121
     */
122
    public function beforeDelete()
123
    {
124
        foreach ($this->attributes as $attr => $options) {
125
            self::ensureAttributes($attr, $options);
126
            $this->deleteFiles($attr);
127
        }
128
    }
129
130
    /**
131
     * @param string $attr name of attribute
132
     * @param ActiveRecord $object that keep attribute. Default $this->owner
133
     * @return string url to image
134
     */
135
    public function getFileUrl($attr, $object = null)
136
    {
137
        $this->checkAttrExists($attr);
138
        $prefix = $this->getUrlPrefix($attr);
139
        $object = isset($object) ? $object : $this->owner;
140
        return $prefix . $object->{$attr};
141
    }
142
143
    /**
144
     * @param string $attr name of attribute
145
     * @throws Exception
146
     */
147
    private function createDirIfNotExists($attr)
148
    {
149
        $dir = $this->getSavePath($attr);
150
        if (!is_dir($dir)) {
151
            FileHelper::createDirectory($dir);
152
        }
153
    }
154
155
    /**
156
     * @param string $attr name of attribute
157
     * @return string save path
158
     * @throws \ReflectionException
159
     */
160
    private function getSavePath($attr)
161
    {
162
        if (isset($this->attributes[$attr]['savePathAlias'])) {
163
            return rtrim(Yii::getAlias($this->attributes[$attr]['savePathAlias']), '\/') . DIRECTORY_SEPARATOR;
164
        }
165
        if (isset($this->savePathAlias)) {
166
            return rtrim(Yii::getAlias($this->savePathAlias), '\/') . DIRECTORY_SEPARATOR;
167
        }
168
169
        if (isset(Yii::$aliases['@frontend'])) {
170
            return Yii::getAlias('@frontend/web/files/' . $this->getShortClassName($this->owner)) . DIRECTORY_SEPARATOR;
171
        }
172
173
        return Yii::getAlias('@app/web/files/' . $this->getShortClassName($this->owner)) . DIRECTORY_SEPARATOR;
174
    }
175
176
    /**
177
     * @param string $attr name of attribute
178
     * @param ActiveRecord $object for default prefix
179
     * @return string url prefix
180
     * @throws \ReflectionException
181
     */
182
    private function getUrlPrefix($attr, $object = null)
183
    {
184
        if (isset($this->attributes[$attr]['urlPrefix'])) {
185
            return '/' . trim($this->attributes[$attr]['urlPrefix'], '/') . '/';
186
        }
187
        if (isset($this->urlPrefix)) {
188
            return '/' . trim($this->urlPrefix, '/') . '/';
189
        }
190
191
        $object = isset($object) ? $object : $this->owner;
192
        return '/files/' . $this->getShortClassName($object) . '/';
193
    }
194
195
    /**
196
     * Delete images
197
     * @param string $attr name of attribute
198
     * @throws \ReflectionException
199
     */
200
    private function deleteFiles($attr)
201
    {
202
        $base = $this->getSavePath($attr);
203
        $model = $this->owner;
204
        if ($model->isNewRecord) {
205
            $value = $model->{$attr};
206
        } else {
207
            $value = $model->oldAttributes[$attr];
208
        }
209
        $file = $base . $value;
210
211
        if (is_file($file)) {
212
            unlink($file);
213
        }
214
    }
215
216
    /**
217
     * Check isset attribute or not
218
     * @param string $attribute name of attribute
219
     * @throws InvalidArgumentException
220
     */
221
    private function checkAttrExists($attribute)
222
    {
223
        foreach ($this->attributes as $attr => $options) {
224
            self::ensureAttributes($attr, $options);
225
            if ($attr == $attribute) {
226
                return;
227
            }
228
        }
229
230
        throw new InvalidArgumentException('checkAttrExists failed');
231
    }
232
233
    /**
234
     * @param $attr
235
     * @param $options
236
     */
237
    public static function ensureAttributes(&$attr, &$options)
238
    {
239
        if (!is_array($options)) {
240
            $attr = $options;
241
            $options = [];
242
        }
243
    }
244
}
245