Passed
Push — php82 ( 5ec03f...5e1ff9 )
by Alexander
16:33 queued 08:21
created

DynamicModel::defineAttribute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\base;
9
10
use yii\validators\Validator;
11
12
/**
13
 * DynamicModel is a model class that supports defining attributes at run-time (the so-called
14
 * "dynamic attributes") using its constructor or [[defineAttribute()]]. DynamicModel can be used
15
 * to support ad hoc data validation.
16
 *
17
 * The typical usage of DynamicModel is as follows,
18
 *
19
 * ```php
20
 * public function actionSearch($name, $email)
21
 * {
22
 *     $model = DynamicModel::validateData(compact('name', 'email'), [
23
 *         [['name', 'email'], 'string', 'max' => 128],
24
 *         ['email', 'email'],
25
 *     ]);
26
 *     if ($model->hasErrors()) {
27
 *         // validation fails
28
 *     } else {
29
 *         // validation succeeds
30
 *     }
31
 * }
32
 * ```
33
 *
34
 * The above example shows how to validate `$name` and `$email` with the help of DynamicModel.
35
 * The [[validateData()]] method creates an instance of DynamicModel, defines the attributes
36
 * using the given data (`name` and `email` in this example), and then calls [[Model::validate()]].
37
 *
38
 * You can check the validation result using [[hasErrors()]], like you do with a normal model.
39
 * You may also access the dynamic attributes defined through the model instance, e.g.,
40
 * `$model->name` and `$model->email`.
41
 *
42
 * Alternatively, you may use the following more "classic" syntax to perform ad-hoc data validation:
43
 *
44
 * ```php
45
 * $model = new DynamicModel(compact('name', 'email'));
46
 * $model->addRule(['name', 'email'], 'string', ['max' => 128])
47
 *     ->addRule('email', 'email')
48
 *     ->validate();
49
 * ```
50
 *
51
 * @author Qiang Xue <[email protected]>
52
 * @since 2.0
53
 */
54
class DynamicModel extends Model
55
{
56
    /**
57
     * @var mixed[] dynamic attribute values (name => value).
58
     */
59
    private $_attributes = [];
60
    /**
61
     * @var string[] dynamic attribute labels (name => label).
62
     * Used as form field labels and in validation error messages.
63
     * @since 2.0.35
64
     */
65
    private $_attributeLabels = [];
66
67
68
    /**
69
     * Constructor.
70
     * @param array $attributes the attributes (name-value pairs, or names) being defined.
71
     * @param array $config the configuration array to be applied to this object.
72
     */
73 169
    public function __construct(array $attributes = [], $config = [])
74
    {
75 169
        foreach ($attributes as $name => $value) {
76 102
            if (is_int($name)) {
77 42
                $this->_attributes[$value] = null;
78
            } else {
79 60
                $this->_attributes[$name] = $value;
80
            }
81
        }
82 169
        parent::__construct($config);
83 169
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88 106
    public function __get($name)
89
    {
90 106
        if ($this->hasAttribute($name)) {
91 105
            return $this->_attributes[$name];
92
        }
93
94 2
        return parent::__get($name);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 61
    public function __set($name, $value)
101
    {
102 61
        if ($this->hasAttribute($name)) {
103 61
            $this->_attributes[$name] = $value;
104
        } else {
105
            parent::__set($name, $value);
106
        }
107 61
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function __isset($name)
113
    {
114
        if ($this->hasAttribute($name)) {
115
            return isset($this->_attributes[$name]);
116
        }
117
118
        return parent::__isset($name);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function __unset($name)
125
    {
126
        if ($this->hasAttribute($name)) {
127
            unset($this->_attributes[$name]);
128
        } else {
129
            parent::__unset($name);
130
        }
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 1
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
137
    {
138 1
        return parent::canGetProperty($name, $checkVars, $checkBehaviors) || $this->hasAttribute($name);
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 1
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
145
    {
146 1
        return parent::canSetProperty($name, $checkVars, $checkBehaviors) || $this->hasAttribute($name);
147
    }
148
149
    /**
150
     * Returns a value indicating whether the model has an attribute with the specified name.
151
     * @param string $name the name of the attribute.
152
     * @return bool whether the model has an attribute with the specified name.
153
     * @since 2.0.16
154
     */
155 137
    public function hasAttribute($name)
156
    {
157 137
        return array_key_exists($name, $this->_attributes);
158
    }
159
160
    /**
161
     * Defines an attribute.
162
     * @param string $name the attribute name.
163
     * @param mixed $value the attribute value.
164
     */
165 80
    public function defineAttribute($name, $value = null)
166
    {
167 80
        $this->_attributes[$name] = $value;
168 80
    }
169
170
    /**
171
     * Undefines an attribute.
172
     * @param string $name the attribute name.
173
     */
174
    public function undefineAttribute($name)
175
    {
176
        unset($this->_attributes[$name]);
177
    }
178
179
    /**
180
     * Adds a validation rule to this model.
181
     * You can also directly manipulate [[validators]] to add or remove validation rules.
182
     * This method provides a shortcut.
183
     * @param string|array $attributes the attribute(s) to be validated by the rule.
184
     * @param string|Validator|\Closure $validator the validator. This can be either:
185
     *  * a built-in validator name listed in [[builtInValidators]];
186
     *  * a method name of the model class;
187
     *  * an anonymous function;
188
     *  * a validator class name.
189
     *  * a Validator.
190
     * @param array $options the options (name-value pairs) to be applied to the validator.
191
     * @return $this
192
     */
193 68
    public function addRule($attributes, $validator, $options = [])
194
    {
195 68
        $validators = $this->getValidators();
196
197 68
        if ($validator instanceof Validator) {
198 12
            $validator->attributes = (array)$attributes;
199
        } else {
200 57
            $validator = Validator::createValidator($validator, $this, (array)$attributes, $options);
201
        }
202
203 68
        $validators->append($validator);
204 68
        $this->defineAttributesByValidator($validator);
205
206 68
        return $this;
207
    }
208
209
    /**
210
     * Validates the given data with the specified validation rules.
211
     * This method will create a DynamicModel instance, populate it with the data to be validated,
212
     * create the specified validation rules, and then validate the data using these rules.
213
     * @param array $data the data (name-value pairs) to be validated.
214
     * @param array $rules the validation rules. Please refer to [[Model::rules()]] on the format of this parameter.
215
     * @return static the model instance that contains the data being validated.
216
     * @throws InvalidConfigException if a validation rule is not specified correctly.
217
     */
218 2
    public static function validateData(array $data, $rules = [])
219
    {
220
        /* @var $model DynamicModel */
221 2
        $model = new static($data);
222 2
        if (!empty($rules)) {
223 2
            $validators = $model->getValidators();
224 2
            foreach ($rules as $rule) {
225 2
                if ($rule instanceof Validator) {
226
                    $validators->append($rule);
227
                    $model->defineAttributesByValidator($rule);
228 2
                } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
229 2
                    $validator = Validator::createValidator($rule[1], $model, (array)$rule[0], array_slice($rule, 2));
230 2
                    $validators->append($validator);
231 2
                    $model->defineAttributesByValidator($validator);
232
                } else {
233
                    throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
234
                }
235
            }
236
        }
237
238 2
        $model->validate();
239
240 2
        return $model;
241
    }
242
243
    /**
244
     * Define the attributes that applies to the specified Validator.
245
     * @param Validator $validator the validator whose attributes are to be defined.
246
     */
247 70
    private function defineAttributesByValidator($validator)
248
    {
249 70
        foreach ($validator->getAttributeNames() as $attribute) {
250 70
            if (!$this->hasAttribute($attribute)) {
251 2
                $this->defineAttribute($attribute);
252
            }
253
        }
254 70
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259 4
    public function attributes()
260
    {
261 4
        return array_keys($this->_attributes);
262
    }
263
264
    /**
265
     * Sets the labels for all attributes.
266
     * @param string[] $labels attribute labels.
267
     * @return $this
268
     * @since 2.0.35
269
     */
270 12
    public function setAttributeLabels(array $labels = [])
271
    {
272 12
        $this->_attributeLabels = $labels;
273
274 12
        return $this;
275
    }
276
277
    /**
278
     * Sets a label for a single attribute.
279
     * @param string $attribute attribute name.
280
     * @param string $label attribute label value.
281
     * @return $this
282
     * @since 2.0.35
283
     */
284
    public function setAttributeLabel($attribute, $label)
285
    {
286
        $this->_attributeLabels[$attribute] = $label;
287
288
        return $this;
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 39
    public function attributeLabels()
295
    {
296 39
        return $this->_attributeLabels;
297
    }
298
}
299