Passed
Push — master ( 9dbdd9...d5a428 )
by Alexander
04:15
created

framework/behaviors/AttributesBehavior.php (1 issue)

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\behaviors;
9
10
use Closure;
11
use yii\base\Behavior;
12
use yii\base\Event;
13
use yii\db\ActiveRecord;
14
15
/**
16
 * AttributesBehavior automatically assigns values specified to one or multiple attributes of an ActiveRecord
17
 * object when certain events happen.
18
 *
19
 * To use AttributesBehavior, configure the [[attributes]] property which should specify the list of attributes
20
 * that need to be updated and the corresponding events that should trigger the update. Then configure the
21
 * value of enclosed arrays with a PHP callable whose return value will be used to assign to the current attribute.
22
 * For example,
23
 *
24
 * ```php
25
 * use yii\behaviors\AttributesBehavior;
26
 *
27
 * public function behaviors()
28
 * {
29
 *     return [
30
 *         [
31
 *             'class' => AttributesBehavior::class,
32
 *             'attributes' => [
33
 *                 'attribute1' => [
34
 *                     ActiveRecord::EVENT_BEFORE_INSERT => new Expression('NOW()'),
35
 *                     ActiveRecord::EVENT_BEFORE_UPDATE => \Yii::$app->formatter->asDatetime('2017-07-13'),
36
 *                 ],
37
 *                 'attribute2' => [
38
 *                     ActiveRecord::EVENT_BEFORE_VALIDATE => [$this, 'storeAttributes'],
39
 *                     ActiveRecord::EVENT_AFTER_VALIDATE => [$this, 'restoreAttributes'],
40
 *                 ],
41
 *                 'attribute3' => [
42
 *                     ActiveRecord::EVENT_BEFORE_VALIDATE => $fn2 = [$this, 'getAttribute2'],
43
 *                     ActiveRecord::EVENT_AFTER_VALIDATE => $fn2,
44
 *                 ],
45
 *                 'attribute4' => [
46
 *                     ActiveRecord::EVENT_BEFORE_DELETE => function ($event, $attribute) {
47
 *                         static::disabled() || $event->isValid = false;
48
 *                     },
49
 *                 ],
50
 *             ],
51
 *         ],
52
 *     ];
53
 * }
54
 * ```
55
 *
56
 * Because attribute values will be set automatically by this behavior, they are usually not user input and should therefore
57
 * not be validated, i.e. they should not appear in the [[\yii\base\Model::rules()|rules()]] method of the model.
58
 *
59
 * @author Luciano Baraglia <[email protected]>
60
 * @author Qiang Xue <[email protected]>
61
 * @author Bogdan Stepanenko <[email protected]>
62
 * @since 2.0.13
63
 */
64
class AttributesBehavior extends Behavior
65
{
66
    /**
67
     * @var array list of attributes that are to be automatically filled with the values specified via enclosed arrays.
68
     * The array keys are the ActiveRecord attributes upon which the events are to be updated,
69
     * and the array values are the array of corresponding events(s). For this enclosed array:
70
     * the array keys are the ActiveRecord events upon which the attributes are to be updated,
71
     * and the array values are the value that will be assigned to the current attributes. This can be an anonymous function,
72
     * callable in array format (e.g. `[$this, 'methodName']`), an [[\yii\db\Expression|Expression]] object representing a DB expression
73
     * (e.g. `new Expression('NOW()')`), scalar, string or an arbitrary value. If the former, the return value of the
74
     * function will be assigned to the attributes.
75
     *
76
     * ```php
77
     * [
78
     *   'attribute1' => [
79
     *       ActiveRecord::EVENT_BEFORE_INSERT => new Expression('NOW()'),
80
     *       ActiveRecord::EVENT_BEFORE_UPDATE => \Yii::$app->formatter->asDatetime('2017-07-13'),
81
     *   ],
82
     *   'attribute2' => [
83
     *       ActiveRecord::EVENT_BEFORE_VALIDATE => [$this, 'storeAttributes'],
84
     *       ActiveRecord::EVENT_AFTER_VALIDATE => [$this, 'restoreAttributes'],
85
     *   ],
86
     *   'attribute3' => [
87
     *       ActiveRecord::EVENT_BEFORE_VALIDATE => $fn2 = [$this, 'getAttribute2'],
88
     *       ActiveRecord::EVENT_AFTER_VALIDATE => $fn2,
89
     *   ],
90
     *   'attribute4' => [
91
     *       ActiveRecord::EVENT_BEFORE_DELETE => function ($event, $attribute) {
92
     *           static::disabled() || $event->isValid = false;
93
     *       },
94
     *   ],
95
     * ]
96
     * ```
97
     */
98
    public $attributes = [];
99
    /**
100
     * @var array list of order of attributes that are to be automatically filled with the event.
101
     * The array keys are the ActiveRecord events upon which the attributes are to be updated,
102
     * and the array values are represent the order corresponding attributes.
103
     * The rest of the attributes are processed at the end.
104
     * If the [[attributes]] for this attribute do not specify this event, it is ignored
105
     *
106
     * ```php
107
     * [
108
     *     ActiveRecord::EVENT_BEFORE_VALIDATE => ['attribute1', 'attribute2'],
109
     *     ActiveRecord::EVENT_AFTER_VALIDATE => ['attribute2', 'attribute1'],
110
     * ]
111
     * ```
112
     */
113
    public $order = [];
114
    /**
115
     * @var bool whether to skip this behavior when the `$owner` has not been modified
116
     */
117
    public $skipUpdateOnClean = true;
118
    /**
119
     * @var bool whether to preserve non-empty attribute values.
120
     */
121
    public $preserveNonEmptyValues = false;
122
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 6
    public function events()
128
    {
129 6
        return array_fill_keys(
130
            array_reduce($this->attributes, function ($carry, $item) {
131 6
                return array_merge($carry, array_keys($item));
132 6
            }, []),
133 6
            'evaluateAttributes'
134
        );
135
    }
136
137
    /**
138
     * Evaluates the attributes values and assigns it to the current attributes.
139
     * @param Event $event
140
     */
141 6
    public function evaluateAttributes($event)
142
    {
143 6
        if ($this->skipUpdateOnClean
144 6
            && $event->name === ActiveRecord::EVENT_BEFORE_UPDATE
145 6
            && empty($this->owner->dirtyAttributes)
0 ignored issues
show
Bug Best Practice introduced by
The property dirtyAttributes does not exist on yii\base\Component. Since you implemented __get, consider adding a @property annotation.
Loading history...
146
        ) {
147
            return;
148
        }
149 6
        $attributes = array_keys(array_filter($this->attributes, function ($carry) use ($event) {
150 6
            return array_key_exists($event->name, $carry);
151 6
        }));
152 6
        if (!empty($this->order[$event->name])) {
153 2
            $attributes = array_merge(
154 2
                array_intersect((array) $this->order[$event->name], $attributes),
155 2
                array_diff($attributes, (array) $this->order[$event->name]));
156
        }
157 6
        foreach ($attributes as $attribute) {
158 6
            if ($this->preserveNonEmptyValues && !empty($this->owner->$attribute)) {
159 2
                continue;
160
            }
161 5
            $this->owner->$attribute = $this->getValue($attribute, $event);
162
        }
163 6
    }
164
165
    /**
166
     * Returns the value for the current attributes.
167
     * This method is called by [[evaluateAttributes()]]. Its return value will be assigned
168
     * to the target attribute corresponding to the triggering event.
169
     * @param string $attribute target attribute name
170
     * @param Event $event the event that triggers the current attribute updating.
171
     * @return mixed the attribute value
172
     */
173 5
    protected function getValue($attribute, $event)
174
    {
175 5
        if (!isset($this->attributes[$attribute][$event->name])) {
176
            return null;
177
        }
178 5
        $value = $this->attributes[$attribute][$event->name];
179 5
        if ($value instanceof Closure || (is_array($value) && is_callable($value))) {
180 5
            return $value($event, $attribute);
181
        }
182
183
        return $value;
184
    }
185
}
186