1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the Zemit Framework. |
4
|
|
|
* |
5
|
|
|
* (c) Zemit Team <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE.txt |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Zemit\Models; |
12
|
|
|
|
13
|
|
|
use Phalcon\Mvc\Model\Relation; |
14
|
|
|
use Phalcon\Validation\Validator\Between; |
15
|
|
|
use Phalcon\Validation\Validator\Date; |
16
|
|
|
use Phalcon\Validation\Validator\Numericality; |
17
|
|
|
use Phalcon\Validation\Validator\PresenceOf; |
18
|
|
|
use Phalcon\Validation\Validator\Uniqueness; |
19
|
|
|
use Zemit\Locale; |
20
|
|
|
use Zemit\Validation; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Class Base |
24
|
|
|
* |
25
|
|
|
* @package Zemit\Models |
26
|
|
|
*/ |
27
|
|
|
abstract class AbstractModel extends \Zemit\Mvc\Model |
28
|
|
|
{ |
29
|
|
|
const LANG_FR = 'fr'; |
30
|
|
|
const LANG_EN = 'en'; |
31
|
|
|
const LANG_SP = 'sp'; |
32
|
|
|
const MAX_UNSIGNED_INT = 4294967295; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Initialize method for model. |
36
|
|
|
*/ |
37
|
|
|
public function initialize() |
38
|
|
|
{ |
39
|
|
|
parent::initialize(); |
40
|
|
|
$this->addDefaultRelationships(); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Set the default relationships |
45
|
|
|
* |
46
|
|
|
* @return void |
47
|
|
|
*/ |
48
|
|
|
public function addDefaultRelationships(): void |
49
|
|
|
{ |
50
|
|
|
$this->addUserRelationship('userId', 'UserEntity'); |
51
|
|
|
$this->addUserRelationship('createdBy', 'CreatedByEntity'); |
52
|
|
|
$this->addUserRelationship('updatedBy', 'UpdatedByEntity'); |
53
|
|
|
$this->addUserRelationship('deletedBy', 'DeletedByEntity'); |
54
|
|
|
$this->addUserRelationship('restoredBy', 'RestoredByEntity'); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @param ?string $class |
59
|
|
|
* @param string $field |
60
|
|
|
* @param string $ref |
61
|
|
|
* @param string $alias |
62
|
|
|
* @param string $type |
63
|
|
|
* @param array $params |
64
|
|
|
* @return ?Relation |
65
|
|
|
*/ |
66
|
|
|
public function addUserRelationship( |
67
|
|
|
string $field = 'userId', |
68
|
|
|
string $alias = 'UserEntity', |
69
|
|
|
array $params = [], |
70
|
|
|
string $ref = 'id', |
71
|
|
|
string $type = 'belongsTo', |
72
|
|
|
?string $class = null |
73
|
|
|
): ?Relation { |
74
|
|
|
if (property_exists($this, $field)) { |
75
|
|
|
$class ??= $this->getIdentity()->getUserClass() ?: $this->getDI()->get('config')->getModelClass(User::class); |
76
|
|
|
return $this->$type($field, $class, $ref, ['alias' => $alias, 'params ' => $params]); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
return null; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Add default basic validations |
84
|
|
|
* - Position |
85
|
|
|
* - Soft delete |
86
|
|
|
* - Create |
87
|
|
|
* - Update |
88
|
|
|
* - Delete |
89
|
|
|
* - Restore |
90
|
|
|
* - Uuid |
91
|
|
|
* - Uid |
92
|
|
|
* - Guid |
93
|
|
|
* |
94
|
|
|
* @param Validation|null $validator |
95
|
|
|
* @return Validation |
96
|
|
|
*/ |
97
|
|
|
public function genericValidation(?Validation $validator = null) |
98
|
|
|
{ |
99
|
|
|
$validator ??= new Validation(); |
100
|
|
|
|
101
|
|
|
$this->addPositionValidation($validator); |
102
|
|
|
$this->addSoftDeleteValidation($validator); |
103
|
|
|
$this->addCreatedValidation($validator); |
104
|
|
|
$this->addUpdatedValidation($validator); |
105
|
|
|
$this->addDeletedValidation($validator); |
106
|
|
|
$this->addRestoredValidation($validator); |
107
|
|
|
$this->addUuidValidation($validator, 'uid'); |
108
|
|
|
$this->addUuidValidation($validator, 'uuid'); |
109
|
|
|
$this->addUuidValidation($validator, 'guid'); |
110
|
|
|
|
111
|
|
|
return $validator; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Add basic validations for the position field to the validator |
116
|
|
|
* - Must be numeric |
117
|
|
|
* - Must be an unsigned integer |
118
|
|
|
* |
119
|
|
|
* @param Validation $validator |
120
|
|
|
* @param string $field |
121
|
|
|
* @param bool $allowEmpty |
122
|
|
|
* @return Validation |
123
|
|
|
*/ |
124
|
|
|
public function addPositionValidation(Validation $validator, string $field = 'position', bool $allowEmpty = true): Validation |
125
|
|
|
{ |
126
|
|
|
if (property_exists($this, $field)) { |
127
|
|
|
|
128
|
|
|
// Must be numeric |
129
|
|
|
$validator->add($field, new Numericality([ |
130
|
|
|
'message' => $this->_('not numeric'), |
131
|
|
|
'allowEmpty' => $allowEmpty, |
132
|
|
|
])); |
133
|
|
|
|
134
|
|
|
// Must be an unsigned integer |
135
|
|
|
$validator->add($field, new Between([ |
136
|
|
|
'minimum' => 0, |
137
|
|
|
'maximum' => self::MAX_UNSIGNED_INT, |
138
|
|
|
'message' => $this->_('not an unsigned integer'), |
139
|
|
|
'allowEmpty' => $allowEmpty, |
140
|
|
|
])); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
return $validator; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Add basic validations for the position field to the validator |
148
|
|
|
* - Must be 0 or 1 |
149
|
|
|
* - Must be numeric |
150
|
|
|
* |
151
|
|
|
* @param Validation $validator |
152
|
|
|
* @param string $field |
153
|
|
|
* @param bool $allowEmpty |
154
|
|
|
* @return void |
155
|
|
|
*/ |
156
|
|
|
public function addSoftDeleteValidation(Validation $validator, string $field = 'deleted', bool $allowEmpty = true) |
157
|
|
|
{ |
158
|
|
|
if (property_exists($this, $field)) { |
159
|
|
|
|
160
|
|
|
// Must be 0 or 1 |
161
|
|
|
$validator->add($field, new Between([ |
162
|
|
|
'minimum' => 0, |
163
|
|
|
'maximum' => 1, |
164
|
|
|
'message' => $this->_('not 0 or 1'), |
165
|
|
|
])); |
166
|
|
|
|
167
|
|
|
// Must be numeric |
168
|
|
|
$validator->add($field, new Numericality([ |
169
|
|
|
'message' => $this->_('not numeric'), |
170
|
|
|
'allowEmpty' => $allowEmpty, |
171
|
|
|
])); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Add basic validations for the $field to the validator |
177
|
|
|
* - Must be unique |
178
|
|
|
* - Field is required |
179
|
|
|
* |
180
|
|
|
* @param Validation $validator |
181
|
|
|
* @param string $field uuid field to validate |
182
|
|
|
* @param bool $required set to true to add the PresenceOf validation |
183
|
|
|
* @return Validation |
184
|
|
|
*/ |
185
|
|
|
public function addUuidValidation(Validation $validator, string $field = 'uuid', bool $required = true) |
186
|
|
|
{ |
187
|
|
|
if (property_exists($this, $field) && $this->getModelsMetaData()->hasAttribute($this, $field)) { |
188
|
|
|
|
189
|
|
|
// If field is required |
190
|
|
|
if ($required) { |
191
|
|
|
$validator->add($field, new PresenceOf(['message' => $this->_('required')])); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
// Must be unique |
195
|
|
|
$validator->add($field, new Uniqueness(['message' => $this->_('not unique')])); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
return $validator; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Add basic validations for the $userIdField and $dateField field to the validator |
203
|
|
|
* - $userIdField: Must be numeric |
204
|
|
|
* - $userIdField: Must be an unsigned integer |
205
|
|
|
* - $dateField: Must be a valid date |
206
|
|
|
* |
207
|
|
|
* @param Validation $validator |
208
|
|
|
* @param string $userIdField user id field to validate |
209
|
|
|
* @param string $dateField date field to validate |
210
|
|
|
* @param bool $allowEmpty set true to allow empty values in user and date field |
211
|
|
|
* @return Validation |
212
|
|
|
*/ |
213
|
|
|
public function addCrudValidation(Validation $validator, string $userIdField, string $dateField, bool $allowEmpty = true): Validation |
214
|
|
|
{ |
215
|
|
|
if (property_exists($this, $userIdField)) { |
216
|
|
|
|
217
|
|
|
// Must be numeric |
218
|
|
|
$validator->add($userIdField, new Numericality([ |
219
|
|
|
'message' => $this->_('not numeric'), |
220
|
|
|
'allowEmpty' => $allowEmpty, |
221
|
|
|
])); |
222
|
|
|
|
223
|
|
|
// Must be an unsigned integer |
224
|
|
|
$validator->add($userIdField, new Between([ |
225
|
|
|
'minimum' => 0, |
226
|
|
|
'maximum' => self::MAX_UNSIGNED_INT, |
227
|
|
|
'message' => $this->_('not an unsigned integer'), |
228
|
|
|
'allowEmpty' => $allowEmpty, |
229
|
|
|
])); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
if (property_exists($this, $dateField)) { |
233
|
|
|
|
234
|
|
|
// Must be a valid date format |
235
|
|
|
$validator->add($dateField, new Date([ |
236
|
|
|
'format' => self::DATETIME_FORMAT, |
237
|
|
|
'message' => $this->_('invalid date format'), |
238
|
|
|
'allowEmpty' => $allowEmpty, |
239
|
|
|
])); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return $validator; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Add crud validation to the user id and date field |
247
|
|
|
* |
248
|
|
|
* @param Validation $validator |
249
|
|
|
* @param string $createdByField user id field to validate |
250
|
|
|
* @param string $createdAtField date field to validate |
251
|
|
|
* @param bool $allowEmpty set true to allow empty values in user and date field |
252
|
|
|
* @return Validation |
253
|
|
|
*/ |
254
|
|
|
public function addCreatedValidation(Validation $validator, string $createdByField = 'createdBy', string $createdAtField = 'createdAt', bool $allowEmpty = true): Validation |
255
|
|
|
{ |
256
|
|
|
return $this->addCrudValidation($validator, $createdByField, $createdAtField, $allowEmpty); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Add crud validation to the user id and date field |
261
|
|
|
* |
262
|
|
|
* @param Validation $validator |
263
|
|
|
* @param string $updatedByField user id field to validate |
264
|
|
|
* @param string $updatedAtField date field to validate |
265
|
|
|
* @param bool $allowEmpty set true to allow empty values in user and date field |
266
|
|
|
* @return Validation |
267
|
|
|
*/ |
268
|
|
|
public function addUpdatedValidation(Validation $validator, string $updatedByField = 'updatedBy', string $updatedAtField = 'updatedAt', bool $allowEmpty = true): Validation |
269
|
|
|
{ |
270
|
|
|
return $this->addCrudValidation($validator, $updatedByField, $updatedAtField, $allowEmpty); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Add crud validation to the user id and date field |
275
|
|
|
* |
276
|
|
|
* @param Validation $validator |
277
|
|
|
* @param string $deletedField user id field to validate |
278
|
|
|
* @param string $dateField date field to validate |
279
|
|
|
* @param bool $allowEmpty set true to allow empty values in user and date field |
280
|
|
|
* @return Validation |
281
|
|
|
*/ |
282
|
|
|
public function addDeletedValidation(Validation $validator, string $deletedField = 'deletedBy', string $dateField = 'deletedAt', bool $allowEmpty = true): Validation |
283
|
|
|
{ |
284
|
|
|
return $this->addCrudValidation($validator, $deletedField, $dateField, $allowEmpty); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Add crud validation to the user id and date field |
289
|
|
|
* |
290
|
|
|
* @param Validation $validator |
291
|
|
|
* @param string $restoredByField user id field to validate |
292
|
|
|
* @param string $restoredAtField date field to validate |
293
|
|
|
* @param bool $allowEmpty set true to allow empty values in user and date field |
294
|
|
|
* @return Validation |
295
|
|
|
*/ |
296
|
|
|
public function addRestoredValidation(Validation $validator, string $restoredByField = 'restoredBy', string $restoredAtField = 'restoredAt', bool $allowEmpty = true): Validation |
297
|
|
|
{ |
298
|
|
|
return $this->addCrudValidation($validator, $restoredByField, $restoredAtField, $allowEmpty); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Returns the translation string of the given key |
303
|
|
|
* |
304
|
|
|
* @param array $placeholders |
305
|
|
|
* @param string $translateKey |
306
|
|
|
* @return string |
307
|
|
|
*/ |
308
|
|
|
public function _(string $translateKey, array $placeholders = []): string |
309
|
|
|
{ |
310
|
|
|
/** @var \Phalcon\Translate\Adapter\AbstractAdapter $translate */ |
311
|
|
|
$translate = $this->getDI()->get('translate'); |
312
|
|
|
|
313
|
|
|
return $translate->_($translateKey, $placeholders); |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Magic caller to set or get localed named field automagically using the current locale |
318
|
|
|
* - Allow to call $this->getName{Fr|En|Sp|...} |
319
|
|
|
* - Allow to call $this->setName{Fr|En|Sp|...} |
320
|
|
|
* |
321
|
|
|
* @param string $method method name |
322
|
|
|
* @param array $arguments method arguments |
323
|
|
|
* @return mixed |
324
|
|
|
* @throws \Phalcon\Mvc\Model\Exception |
325
|
|
|
*/ |
326
|
|
|
public function __call(string $method, array $arguments) |
327
|
|
|
{ |
328
|
|
|
/** @var Locale $locale */ |
329
|
|
|
$locale = $this->getDI()->get('locale'); |
330
|
|
|
|
331
|
|
|
$lang = $locale->getLocale() ?: 'en'; |
332
|
|
|
|
333
|
|
|
if (mb_strrpos($method, ucfirst($lang)) !== mb_strlen($method) - mb_strlen($lang)) { |
334
|
|
|
$call = $method . ucfirst($lang); |
335
|
|
|
if (method_exists($this, $call)) { |
336
|
|
|
return $this->$call(...$arguments); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
return parent::__call($method, $arguments); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Magic setter to set localed named field automatically using the current locale |
345
|
|
|
* - Allow to set $this->name{Fr|En|Sp|...} from missing name property |
346
|
|
|
* |
347
|
|
|
* @param string $property property name |
348
|
|
|
* @param mixed $value value to set |
349
|
|
|
* @return void |
350
|
|
|
*/ |
351
|
|
|
public function __set(string $property, $value) |
352
|
|
|
{ |
353
|
|
|
/** @var Locale $locale */ |
354
|
|
|
$locale = $this->getDI()->get('locale'); |
355
|
|
|
|
356
|
|
|
$lang = $locale->getLocale(); |
357
|
|
|
|
358
|
|
|
if (mb_strrpos($property, ucfirst($lang)) !== mb_strlen($property) - 2) { |
|
|
|
|
359
|
|
|
$set = $property . ucfirst($lang); |
360
|
|
|
|
361
|
|
|
if (property_exists($this, $set)) { |
362
|
|
|
$this->writeAttribute($set, $value); |
363
|
|
|
|
364
|
|
|
return; |
365
|
|
|
} |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
parent::__set($property, $value); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Magic getter to get localed named field automatically using the current locale |
373
|
|
|
* - Allow to get $this->name{Fr|En|Sp|...} from missing name property |
374
|
|
|
* |
375
|
|
|
* @param string $property property name |
376
|
|
|
* @return mixed|null |
377
|
|
|
*/ |
378
|
|
|
public function __get(string $property) |
379
|
|
|
{ |
380
|
|
|
/** @var Locale $locale */ |
381
|
|
|
$locale = $this->getDI()->get('locale'); |
382
|
|
|
|
383
|
|
|
$lang = $locale->getLocale(); |
384
|
|
|
|
385
|
|
|
if (mb_strrpos($property, ucfirst($lang)) !== mb_strlen($property) - 2) { |
|
|
|
|
386
|
|
|
$set = $property . ucfirst($lang); |
387
|
|
|
|
388
|
|
|
if (property_exists($this, $set)) { |
389
|
|
|
return $this->readAttribute($set); |
390
|
|
|
} |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
return parent::__get($property); |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* @TODO Language support for isset as well |
398
|
|
|
*/ |
399
|
|
|
// public function __isset() { |
400
|
|
|
// |
401
|
|
|
// } |
402
|
|
|
} |
403
|
|
|
|