-
Notifications
You must be signed in to change notification settings - Fork 645
/
Copy pathModel.php
330 lines (281 loc) · 9.16 KB
/
Model.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/
namespace craft\base;
use Closure;
use craft\events\DefineBehaviorsEvent;
use craft\events\DefineFieldsEvent;
use craft\events\DefineRulesEvent;
use craft\helpers\App;
use craft\helpers\Component;
use craft\helpers\DateTimeHelper;
use craft\helpers\StringHelper;
use craft\helpers\Typecast;
use yii\validators\Validator;
/**
* Model base class.
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.0.0
*/
abstract class Model extends \yii\base\Model implements ModelInterface
{
use ClonefixTrait;
/**
* @event \yii\base\Event The event that is triggered after the model's init cycle
* @see init()
*/
public const EVENT_INIT = 'init';
/**
* @event DefineBehaviorsEvent The event that is triggered when defining the class behaviors
* @see behaviors()
*/
public const EVENT_DEFINE_BEHAVIORS = 'defineBehaviors';
/**
* @event DefineRulesEvent The event that is triggered when defining the model rules
* @see rules()
* @since 3.1.0
*/
public const EVENT_DEFINE_RULES = 'defineRules';
/**
* @event DefineFieldsEvent The event that is triggered when defining the arrayable fields
* @see fields()
* @since 3.5.0
*/
public const EVENT_DEFINE_FIELDS = 'defineFields';
/**
* @event DefineFieldsEvent The event that is triggered when defining the extra arrayable fields
* @see extraFields()
* @since 3.5.0
*/
public const EVENT_DEFINE_EXTRA_FIELDS = 'defineExtraFields';
public function __construct($config = [])
{
// Typecast the properties
Typecast::properties(static::class, $config);
// Normalize the DateTime attributes
foreach ($this->datetimeAttributes() as $attribute) {
if (array_key_exists($attribute, $config) && $config[$attribute] !== null) {
$config[$attribute] = DateTimeHelper::toDateTime($config[$attribute]);
}
}
// Call App::configure() rather than BaseYii::configure() (via BaseObject::__construct()),
// in case \Yii isn't loaded yet. (Mainly an issue for GeneralConfig/DbConfig, if config/general.php
// or config/db.php return an array.)
// Note that inlining the foreach loop is no good, because then private/protected properties will be
// set directly rather than going through __set().
App::configure($this, $config);
// Intentionally not passing $config along
parent::__construct();
}
/**
* @inheritdoc
*/
public function init(): void
{
parent::init();
if ($this->hasEventHandlers(self::EVENT_INIT)) {
$this->trigger(self::EVENT_INIT);
}
}
/**
* @inheritdoc
*/
public function behaviors(): array
{
$behaviors = $this->defineBehaviors();
// Fire a 'defineBehaviors' event
if ($this->hasEventHandlers(self::EVENT_DEFINE_BEHAVIORS)) {
$event = new DefineBehaviorsEvent(['behaviors' => $behaviors]);
$this->trigger(self::EVENT_DEFINE_BEHAVIORS, $event);
return $event->behaviors;
}
return $behaviors;
}
/**
* @inheritdoc
*/
public function rules(): array
{
$rules = $this->defineRules();
// Fire a 'defineRules' event
if ($this->hasEventHandlers(self::EVENT_DEFINE_RULES)) {
$event = new DefineRulesEvent(['rules' => $rules]);
$this->trigger(self::EVENT_DEFINE_RULES, $event);
$rules = $event->rules;
}
foreach ($rules as &$rule) {
$this->_normalizeRule($rule);
}
return $rules;
}
/**
* Normalizes a validation rule.
*
* @param array|Validator $rule
*/
private function _normalizeRule(array|Validator &$rule): void
{
if (is_array($rule) && isset($rule[1]) && $rule[1] instanceof Closure) {
// Wrap the closure in another one, so InlineValidator doesn’t bind it to the model
$method = $rule[1];
$rule[1] = function($attribute, $params, $validator, $current) use ($method) {
$method($attribute, $params, $validator, $current);
};
}
}
/**
* Returns the behaviors to attach to this class.
*
* See [[behaviors()]] for details about what should be returned.
*
* Models should override this method instead of [[behaviors()]] so [[EVENT_DEFINE_BEHAVIORS]] handlers can modify the
* class-defined behaviors.
*
* @return array
* @since 4.0.0
*/
protected function defineBehaviors(): array
{
return [];
}
/**
* Returns the validation rules for attributes.
*
* See [[rules()]] for details about what should be returned.
*
* Models should override this method instead of [[rules()]] so [[EVENT_DEFINE_RULES]] handlers can modify the
* class-defined rules.
*
* @return array
* @since 3.4.0
*/
protected function defineRules(): array
{
return [];
}
/**
* Returns the names of any attributes that should hold [[\DateTime]] values.
*
* @return string[]
* @see init()
* @see fields()
* @deprecated in 4.0.0. Use [[\DateTime]] type declarations instead.
*/
public function datetimeAttributes(): array
{
$attributes = [];
if (property_exists($this, 'dateCreated')) {
$attributes[] = 'dateCreated';
}
if (property_exists($this, 'dateAdded')) {
$attributes[] = 'dateAdded';
}
if (property_exists($this, 'dateUpdated')) {
$attributes[] = 'dateUpdated';
}
if (property_exists($this, 'dateDeleted')) {
$attributes[] = 'dateDeleted';
}
return $attributes;
}
/**
* @inheritdoc
* @since 4.0.0
*/
public function setAttributes($values, $safeOnly = true): void
{
// Typecast them
Typecast::properties(static::class, $values);
// Normalize the date/time attributes
foreach ($this->datetimeAttributes() as $name) {
if (isset($values[$name])) {
$values[$name] = DateTimeHelper::toDateTime($values[$name]) ?: null;
}
}
parent::setAttributes($values, $safeOnly);
}
/**
* @inheritdoc
*/
public function fields(): array
{
$fields = parent::fields();
$datetimeAttributes = Component::datetimeAttributes($this);
// Have all DateTime attributes converted to ISO-8601 strings
foreach ($datetimeAttributes as $attribute) {
$fields[$attribute] = function($model, $attribute) {
$date = $model->$attribute;
if ($date) {
return DateTimeHelper::toIso8601($date, true);
}
return $model->$attribute;
};
}
// Fire a 'defineFields' event
if ($this->hasEventHandlers(self::EVENT_DEFINE_FIELDS)) {
$event = new DefineFieldsEvent(['fields' => $fields]);
$this->trigger(self::EVENT_DEFINE_FIELDS, $event);
return $event->fields;
}
return $fields;
}
/**
* @inheritdoc
*/
public function extraFields(): array
{
$fields = parent::extraFields();
// Fire a 'defineExtraFields' event
if ($this->hasEventHandlers(self::EVENT_DEFINE_EXTRA_FIELDS)) {
$event = new DefineFieldsEvent(['fields' => $fields]);
$this->trigger(self::EVENT_DEFINE_EXTRA_FIELDS, $event);
return $event->fields;
}
return $fields;
}
/**
* Adds errors from another model, with a given attribute name prefix.
*
* @param \yii\base\Model $model The other model
* @param string $attrPrefix The prefix that should be added to error attributes when adding them to this model
*/
public function addModelErrors(\yii\base\Model $model, string $attrPrefix = ''): void
{
if ($attrPrefix !== '') {
$attrPrefix = rtrim($attrPrefix, '.') . '.';
}
foreach ($model->getErrors() as $attribute => $errors) {
foreach ($errors as $error) {
$this->addError($attrPrefix . $attribute, $error);
}
}
}
/**
* @inheritdoc
*/
public function hasErrors($attribute = null): bool
{
$includeNested = $attribute !== null && StringHelper::endsWith($attribute, '.*');
if ($includeNested) {
$attribute = StringHelper::removeRight($attribute, '.*');
}
if (parent::hasErrors($attribute)) {
return true;
}
if ($includeNested) {
foreach ($this->getErrors() as $attr => $errors) {
if (str_starts_with($attr, $attribute . '.')) {
return true;
}
if (str_starts_with($attr, $attribute . '[')) {
return true;
}
}
}
return false;
}
}