1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @author Jared King <[email protected]> |
5
|
|
|
* |
6
|
|
|
* @link http://jaredtking.com |
7
|
|
|
* |
8
|
|
|
* @copyright 2015 Jared King |
9
|
|
|
* @license MIT |
10
|
|
|
*/ |
11
|
|
|
namespace Pulsar; |
12
|
|
|
|
13
|
|
|
use BadMethodCallException; |
14
|
|
|
use ICanBoogie\Inflector; |
15
|
|
|
use Infuse\Locale; |
16
|
|
|
use InvalidArgumentException; |
17
|
|
|
use Pulsar\Driver\DriverInterface; |
18
|
|
|
use Pulsar\Exception\DriverMissingException; |
19
|
|
|
use Pulsar\Exception\NotFoundException; |
20
|
|
|
use Pulsar\Relation\HasOne; |
21
|
|
|
use Pulsar\Relation\BelongsTo; |
22
|
|
|
use Pulsar\Relation\HasMany; |
23
|
|
|
use Pulsar\Relation\BelongsToMany; |
24
|
|
|
use Pimple\Container; |
25
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
26
|
|
|
|
27
|
|
|
abstract class Model implements \ArrayAccess |
28
|
|
|
{ |
29
|
|
|
const IMMUTABLE = 0; |
30
|
|
|
const MUTABLE_CREATE_ONLY = 1; |
31
|
|
|
const MUTABLE = 2; |
32
|
|
|
|
33
|
|
|
const TYPE_STRING = 'string'; |
34
|
|
|
const TYPE_NUMBER = 'number'; |
35
|
|
|
const TYPE_BOOLEAN = 'boolean'; |
36
|
|
|
const TYPE_DATE = 'date'; |
37
|
|
|
const TYPE_OBJECT = 'object'; |
38
|
|
|
const TYPE_ARRAY = 'array'; |
39
|
|
|
|
40
|
|
|
const DEFAULT_ID_PROPERTY = 'id'; |
41
|
|
|
|
42
|
|
|
///////////////////////////// |
43
|
|
|
// Model visible variables |
44
|
|
|
///////////////////////////// |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* List of model ID property names. |
48
|
|
|
* |
49
|
|
|
* @staticvar array |
50
|
|
|
*/ |
51
|
|
|
protected static $ids = [self::DEFAULT_ID_PROPERTY]; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Property definitions expressed as a key-value map with |
55
|
|
|
* property names as the keys. |
56
|
|
|
* i.e. ['enabled' => ['type' => Model::TYPE_BOOLEAN]]. |
57
|
|
|
* |
58
|
|
|
* @staticvar array |
59
|
|
|
*/ |
60
|
|
|
protected static $properties = []; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Validation rules expressed as a key-value map with |
64
|
|
|
* property names as the keys. |
65
|
|
|
* i.e. ['name' => 'string:2']. |
66
|
|
|
* |
67
|
|
|
* @staticvar array |
68
|
|
|
*/ |
69
|
|
|
protected static $validations = []; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @staticvar array |
73
|
|
|
*/ |
74
|
|
|
protected static $relationships = []; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @staticvar \Pimple\Container |
78
|
|
|
*/ |
79
|
|
|
protected static $injectedApp; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @staticvar array |
83
|
|
|
*/ |
84
|
|
|
protected static $dispatchers; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @var \Pimple\Container |
88
|
|
|
*/ |
89
|
|
|
protected $app; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @var array |
93
|
|
|
*/ |
94
|
|
|
protected $_values = []; |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @var array |
98
|
|
|
*/ |
99
|
|
|
protected $_unsaved = []; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* @var bool |
103
|
|
|
*/ |
104
|
|
|
protected $_persisted = false; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @var Errors |
108
|
|
|
*/ |
109
|
|
|
protected $_errors; |
110
|
|
|
|
111
|
|
|
///////////////////////////// |
112
|
|
|
// Base model variables |
113
|
|
|
///////////////////////////// |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @staticvar array |
117
|
|
|
*/ |
118
|
|
|
private static $propertyDefinitionBase = [ |
119
|
|
|
'type' => self::TYPE_STRING, |
120
|
|
|
'mutable' => self::MUTABLE, |
121
|
|
|
]; |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @staticvar array |
125
|
|
|
*/ |
126
|
|
|
private static $defaultIDProperty = [ |
127
|
|
|
'type' => self::TYPE_NUMBER, |
128
|
|
|
'mutable' => self::IMMUTABLE, |
129
|
|
|
]; |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @staticvar array |
133
|
|
|
*/ |
134
|
|
|
private static $timestampProperties = [ |
135
|
|
|
'created_at' => [ |
136
|
|
|
'type' => self::TYPE_DATE, |
137
|
|
|
'default' => null, |
138
|
|
|
], |
139
|
|
|
'updated_at' => [ |
140
|
|
|
'type' => self::TYPE_DATE, |
141
|
|
|
], |
142
|
|
|
]; |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @staticvar array |
146
|
|
|
*/ |
147
|
|
|
private static $timestampValidations = [ |
148
|
|
|
'created_at' => 'timestamp|db_timestamp', |
149
|
|
|
'updated_at' => 'timestamp|db_timestamp', |
150
|
|
|
]; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @staticvar array |
154
|
|
|
*/ |
155
|
|
|
private static $initialized = []; |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @staticvar DriverInterface |
159
|
|
|
*/ |
160
|
|
|
private static $driver; |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @staticvar Locale |
164
|
|
|
*/ |
165
|
|
|
private static $locale; |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @staticvar array |
169
|
|
|
*/ |
170
|
|
|
private static $accessors = []; |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* @staticvar array |
174
|
|
|
*/ |
175
|
|
|
private static $mutators = []; |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* @var bool |
179
|
|
|
*/ |
180
|
|
|
private $_ignoreUnsaved; |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Creates a new model object. |
184
|
|
|
* |
185
|
|
|
* @param array $values values to fill model with |
186
|
|
|
*/ |
187
|
|
|
public function __construct(array $values = []) |
188
|
|
|
{ |
189
|
|
|
$this->_values = $values; |
190
|
|
|
$this->app = self::$injectedApp; |
191
|
|
|
|
192
|
|
|
// ensure the initialize function is called only once |
193
|
|
|
$k = get_called_class(); |
194
|
|
|
if (!isset(self::$initialized[$k])) { |
195
|
|
|
$this->initialize(); |
196
|
|
|
self::$initialized[$k] = true; |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* The initialize() method is called once per model. It's used |
202
|
|
|
* to perform any one-off tasks before the model gets |
203
|
|
|
* constructed. This is a great place to add any model |
204
|
|
|
* properties. When extending this method be sure to call |
205
|
|
|
* parent::initialize() as some important stuff happens here. |
206
|
|
|
* If extending this method to add properties then you should |
207
|
|
|
* call parent::initialize() after adding any properties. |
208
|
|
|
*/ |
209
|
|
|
protected function initialize() |
210
|
|
|
{ |
211
|
|
|
// add in the default ID property |
212
|
|
|
if (static::$ids == [self::DEFAULT_ID_PROPERTY] && !isset(static::$properties[self::DEFAULT_ID_PROPERTY])) { |
213
|
|
|
static::$properties[self::DEFAULT_ID_PROPERTY] = self::$defaultIDProperty; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
// add in the auto timestamp properties |
217
|
|
|
if (property_exists(get_called_class(), 'autoTimestamps')) { |
218
|
|
|
static::$properties = array_replace(self::$timestampProperties, static::$properties); |
219
|
|
|
|
220
|
|
|
static::$validations = array_replace(self::$timestampValidations, static::$validations); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
// fill in each property by extending the property |
224
|
|
|
// definition base |
225
|
|
|
foreach (static::$properties as &$property) { |
226
|
|
|
$property = array_replace(self::$propertyDefinitionBase, $property); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
// order the properties array by name for consistency |
230
|
|
|
// since it is constructed in a random order |
231
|
|
|
ksort(static::$properties); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Injects a DI container. |
236
|
|
|
* |
237
|
|
|
* @param \Pimple\Container $app |
238
|
|
|
*/ |
239
|
|
|
public static function inject(Container $app) |
240
|
|
|
{ |
241
|
|
|
self::$injectedApp = $app; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Gets the DI container used for this model. |
246
|
|
|
* |
247
|
|
|
* @return \Pimple\Container |
248
|
|
|
*/ |
249
|
|
|
public function getApp() |
250
|
|
|
{ |
251
|
|
|
return $this->app; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Sets the driver for all models. |
256
|
|
|
* |
257
|
|
|
* @param DriverInterface $driver |
258
|
|
|
*/ |
259
|
|
|
public static function setDriver(DriverInterface $driver) |
260
|
|
|
{ |
261
|
|
|
self::$driver = $driver; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Gets the driver for all models. |
266
|
|
|
* |
267
|
|
|
* @return DriverInterface |
268
|
|
|
* |
269
|
|
|
* @throws DriverMissingException |
270
|
|
|
*/ |
271
|
|
|
public static function getDriver() |
272
|
|
|
{ |
273
|
|
|
if (!self::$driver) { |
274
|
|
|
throw new DriverMissingException('A model driver has not been set yet.'); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
return self::$driver; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Clears the driver for all models. |
282
|
|
|
*/ |
283
|
|
|
public static function clearDriver() |
284
|
|
|
{ |
285
|
|
|
self::$driver = null; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Sets the locale instance for all models. |
290
|
|
|
* |
291
|
|
|
* @param Locale $locale |
292
|
|
|
*/ |
293
|
|
|
public static function setLocale(Locale $locale) |
294
|
|
|
{ |
295
|
|
|
self::$locale = $locale; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Gets the name of the model without namespacing. |
300
|
|
|
* |
301
|
|
|
* @return string |
302
|
|
|
*/ |
303
|
|
|
public static function modelName() |
304
|
|
|
{ |
305
|
|
|
return explode('\\', get_called_class())[0]; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Gets the model ID. |
310
|
|
|
* |
311
|
|
|
* @return string|number|null ID |
312
|
|
|
*/ |
313
|
|
|
public function id() |
314
|
|
|
{ |
315
|
|
|
$ids = $this->ids(); |
316
|
|
|
|
317
|
|
|
// if a single ID then return it |
318
|
|
|
if (count($ids) === 1) { |
319
|
|
|
return reset($ids); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
// if multiple IDs then return a comma-separated list |
323
|
|
|
return implode(',', $ids); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Gets a key-value map of the model ID. |
328
|
|
|
* |
329
|
|
|
* @return array ID map |
330
|
|
|
*/ |
331
|
|
|
public function ids() |
332
|
|
|
{ |
333
|
|
|
return $this->get(static::$ids); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
///////////////////////////// |
337
|
|
|
// Magic Methods |
338
|
|
|
///////////////////////////// |
339
|
|
|
|
340
|
|
|
public function __toString() |
341
|
|
|
{ |
342
|
|
|
return get_called_class().'('.$this->id().')'; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
public function __get($name) |
346
|
|
|
{ |
347
|
|
|
return array_values($this->get([$name]))[0]; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
public function __set($name, $value) |
351
|
|
|
{ |
352
|
|
|
$this->setValue($name, $value); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
public function __isset($name) |
356
|
|
|
{ |
357
|
|
|
return array_key_exists($name, $this->_unsaved) || static::hasProperty($name); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
public function __unset($name) |
361
|
|
|
{ |
362
|
|
|
if (static::isRelationship($name)) { |
363
|
|
|
throw new BadMethodCallException("Cannot unset the `$name` property because it is a relationship"); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
if (array_key_exists($name, $this->_unsaved)) { |
367
|
|
|
unset($this->_unsaved[$name]); |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
public static function __callStatic($name, $parameters) |
372
|
|
|
{ |
373
|
|
|
// Any calls to unkown static methods should be deferred to |
374
|
|
|
// the query. This allows calls like User::where() |
375
|
|
|
// to replace User::query()->where(). |
|
|
|
|
376
|
|
|
return call_user_func_array([static::query(), $name], $parameters); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
///////////////////////////// |
380
|
|
|
// ArrayAccess Interface |
381
|
|
|
///////////////////////////// |
382
|
|
|
|
383
|
|
|
public function offsetExists($offset) |
384
|
|
|
{ |
385
|
|
|
return isset($this->$offset); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
public function offsetGet($offset) |
389
|
|
|
{ |
390
|
|
|
return $this->$offset; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
public function offsetSet($offset, $value) |
394
|
|
|
{ |
395
|
|
|
$this->$offset = $value; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
public function offsetUnset($offset) |
399
|
|
|
{ |
400
|
|
|
unset($this->$offset); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
///////////////////////////// |
404
|
|
|
// Property Definitions |
405
|
|
|
///////////////////////////// |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Gets all the property definitions for the model. |
409
|
|
|
* |
410
|
|
|
* @return array key-value map of properties |
411
|
|
|
*/ |
412
|
|
|
public static function getProperties() |
413
|
|
|
{ |
414
|
|
|
return static::$properties; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Gets a property defition for the model. |
419
|
|
|
* |
420
|
|
|
* @param string $property property to lookup |
421
|
|
|
* |
422
|
|
|
* @return array|null property |
423
|
|
|
*/ |
424
|
|
|
public static function getProperty($property) |
425
|
|
|
{ |
426
|
|
|
return array_value(static::$properties, $property); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Gets the names of the model ID properties. |
431
|
|
|
* |
432
|
|
|
* @return array |
433
|
|
|
*/ |
434
|
|
|
public static function getIdProperties() |
435
|
|
|
{ |
436
|
|
|
return static::$ids; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* Builds an existing model instance given a single ID value or |
441
|
|
|
* ordered array of ID values. |
442
|
|
|
* |
443
|
|
|
* @param mixed $id |
444
|
|
|
* |
445
|
|
|
* @return Model |
446
|
|
|
*/ |
447
|
|
|
public static function buildFromId($id) |
448
|
|
|
{ |
449
|
|
|
$ids = []; |
450
|
|
|
$id = (array) $id; |
451
|
|
|
foreach (static::$ids as $j => $k) { |
452
|
|
|
$ids[$k] = $id[$j]; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
$model = new static($ids); |
456
|
|
|
|
457
|
|
|
return $model; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Checks if the model has a property. |
462
|
|
|
* |
463
|
|
|
* @param string $property property |
464
|
|
|
* |
465
|
|
|
* @return bool has property |
466
|
|
|
*/ |
467
|
|
|
public static function hasProperty($property) |
468
|
|
|
{ |
469
|
|
|
return isset(static::$properties[$property]); |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
/** |
473
|
|
|
* Gets the mutator method name for a given proeprty name. |
474
|
|
|
* Looks for methods in the form of `setPropertyValue`. |
475
|
|
|
* i.e. the mutator for `last_name` would be `setLastNameValue`. |
476
|
|
|
* |
477
|
|
|
* @param string $property property |
478
|
|
|
* |
479
|
|
|
* @return string|false method name if it exists |
480
|
|
|
*/ |
481
|
|
View Code Duplication |
public static function getMutator($property) |
|
|
|
|
482
|
|
|
{ |
483
|
|
|
$class = get_called_class(); |
484
|
|
|
|
485
|
|
|
$k = $class.':'.$property; |
486
|
|
|
if (!array_key_exists($k, self::$mutators)) { |
487
|
|
|
$inflector = Inflector::get(); |
488
|
|
|
$method = 'set'.$inflector->camelize($property).'Value'; |
489
|
|
|
|
490
|
|
|
if (!method_exists($class, $method)) { |
491
|
|
|
$method = false; |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
self::$mutators[$k] = $method; |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
return self::$mutators[$k]; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* Gets the accessor method name for a given proeprty name. |
502
|
|
|
* Looks for methods in the form of `getPropertyValue`. |
503
|
|
|
* i.e. the accessor for `last_name` would be `getLastNameValue`. |
504
|
|
|
* |
505
|
|
|
* @param string $property property |
506
|
|
|
* |
507
|
|
|
* @return string|false method name if it exists |
508
|
|
|
*/ |
509
|
|
View Code Duplication |
public static function getAccessor($property) |
|
|
|
|
510
|
|
|
{ |
511
|
|
|
$class = get_called_class(); |
512
|
|
|
|
513
|
|
|
$k = $class.':'.$property; |
514
|
|
|
if (!array_key_exists($k, self::$accessors)) { |
515
|
|
|
$inflector = Inflector::get(); |
516
|
|
|
$method = 'get'.$inflector->camelize($property).'Value'; |
517
|
|
|
|
518
|
|
|
if (!method_exists($class, $method)) { |
519
|
|
|
$method = false; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
self::$accessors[$k] = $method; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
return self::$accessors[$k]; |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
/** |
529
|
|
|
* Checks if a given property is a relationship. |
530
|
|
|
* |
531
|
|
|
* @param string $property |
532
|
|
|
* |
533
|
|
|
* @return bool |
534
|
|
|
*/ |
535
|
|
|
public static function isRelationship($property) |
536
|
|
|
{ |
537
|
|
|
return in_array($property, static::$relationships); |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
/** |
541
|
|
|
* Gets the title of a property. |
542
|
|
|
* |
543
|
|
|
* @param string $name |
544
|
|
|
* |
545
|
|
|
* @return string |
546
|
|
|
*/ |
547
|
|
|
public static function getPropertyTitle($name) |
548
|
|
|
{ |
549
|
|
|
// TODO the property title should be fetched from |
550
|
|
|
// the pulsar.properties.$name value in locale |
551
|
|
|
$property = static::getProperty($name); |
552
|
|
|
if ($property && isset($property['title'])) { |
553
|
|
|
return $property['title']; |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
return Inflector::get()->humanize($name); |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Gets the default value for a property. |
561
|
|
|
* |
562
|
|
|
* @param string|array $property |
563
|
|
|
* |
564
|
|
|
* @return mixed |
565
|
|
|
*/ |
566
|
|
|
public static function getDefaultValueFor($property) |
567
|
|
|
{ |
568
|
|
|
if (!is_array($property)) { |
569
|
|
|
$property = static::getProperty($property); |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
return $property ? array_value($property, 'default') : null; |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
///////////////////////////// |
576
|
|
|
// Values |
577
|
|
|
///////////////////////////// |
578
|
|
|
|
579
|
|
|
/** |
580
|
|
|
* Sets an unsaved value. |
581
|
|
|
* |
582
|
|
|
* @param string $name |
583
|
|
|
* @param mixed $value |
584
|
|
|
* |
585
|
|
|
* @throws BadMethodCallException when setting a relationship |
586
|
|
|
*/ |
587
|
|
|
public function setValue($name, $value) |
588
|
|
|
{ |
589
|
|
|
if (static::isRelationship($name)) { |
590
|
|
|
throw new BadMethodCallException("Cannot set the `$name` property because it is a relationship"); |
591
|
|
|
} |
592
|
|
|
|
593
|
|
|
// set using any mutators |
594
|
|
|
if ($mutator = self::getMutator($name)) { |
595
|
|
|
$this->_unsaved[$name] = $this->$mutator($value); |
596
|
|
|
} else { |
597
|
|
|
$this->_unsaved[$name] = $value; |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
return $this; |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
/** |
604
|
|
|
* Ignores unsaved values when fetching the next value. |
605
|
|
|
* |
606
|
|
|
* @return self |
607
|
|
|
*/ |
608
|
|
|
public function ignoreUnsaved() |
609
|
|
|
{ |
610
|
|
|
$this->_ignoreUnsaved = true; |
611
|
|
|
|
612
|
|
|
return $this; |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* Gets property values from the model. |
617
|
|
|
* |
618
|
|
|
* This method looks up values from these locations in this |
619
|
|
|
* precedence order (least important to most important): |
620
|
|
|
* 1. defaults |
621
|
|
|
* 2. local values |
622
|
|
|
* 3. unsaved values |
623
|
|
|
* |
624
|
|
|
* @param array $properties list of property names to fetch values of |
625
|
|
|
* |
626
|
|
|
* @return array |
627
|
|
|
* |
628
|
|
|
* @throws InvalidArgumentException when a property was requested not present in the values |
629
|
|
|
*/ |
630
|
|
|
public function get(array $properties) |
631
|
|
|
{ |
632
|
|
|
// load the values from the local model cache |
633
|
|
|
$values = $this->_values; |
634
|
|
|
|
635
|
|
|
// unless specified, use any unsaved values |
636
|
|
|
$ignoreUnsaved = $this->_ignoreUnsaved; |
637
|
|
|
$this->_ignoreUnsaved = false; |
638
|
|
|
if (!$ignoreUnsaved) { |
639
|
|
|
$values = array_replace($values, $this->_unsaved); |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
// build the response |
643
|
|
|
$result = []; |
644
|
|
|
foreach ($properties as $k) { |
645
|
|
|
$accessor = self::getAccessor($k); |
646
|
|
|
|
647
|
|
|
// use the supplied value if it's available |
648
|
|
|
if (array_key_exists($k, $values)) { |
649
|
|
|
$result[$k] = $values[$k]; |
650
|
|
|
// get relationship values |
651
|
|
|
} elseif (static::isRelationship($k)) { |
652
|
|
|
$result[$k] = $this->loadRelationship($k); |
653
|
|
|
// set any missing values to the default value |
654
|
|
|
} elseif ($property = static::getProperty($k)) { |
655
|
|
|
$result[$k] = $this->_values[$k] = self::getDefaultValueFor($property); |
656
|
|
|
// throw an exception for non-properties that do not |
657
|
|
|
// have an accessor |
658
|
|
|
} elseif (!$accessor) { |
|
|
|
|
659
|
|
|
throw new InvalidArgumentException(static::modelName().' does not have a `'.$k.'` property.'); |
660
|
|
|
// otherwise the value is considered null |
661
|
|
|
} else { |
662
|
|
|
$result[$k] = null; |
663
|
|
|
} |
664
|
|
|
|
665
|
|
|
// call any accessors |
666
|
|
|
if ($accessor) { |
|
|
|
|
667
|
|
|
$result[$k] = $this->$accessor($result[$k]); |
668
|
|
|
} |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
return $result; |
672
|
|
|
} |
673
|
|
|
|
674
|
|
|
/** |
675
|
|
|
* Converts the model to an array. |
676
|
|
|
* |
677
|
|
|
* @return array model array |
678
|
|
|
*/ |
679
|
|
|
public function toArray() |
680
|
|
|
{ |
681
|
|
|
// build the list of properties to retrieve |
682
|
|
|
$properties = array_keys(static::$properties); |
683
|
|
|
|
684
|
|
|
// remove any hidden properties |
685
|
|
|
$hide = (property_exists($this, 'hidden')) ? static::$hidden : []; |
686
|
|
|
$properties = array_diff($properties, $hide); |
687
|
|
|
|
688
|
|
|
// add any appended properties |
689
|
|
|
$append = (property_exists($this, 'appended')) ? static::$appended : []; |
690
|
|
|
$properties = array_merge($properties, $append); |
691
|
|
|
|
692
|
|
|
// get the values for the properties |
693
|
|
|
$result = $this->get($properties); |
694
|
|
|
|
695
|
|
|
// convert any models to arrays |
696
|
|
|
foreach ($result as &$value) { |
697
|
|
|
if ($value instanceof self) { |
698
|
|
|
$value = $value->toArray(); |
699
|
|
|
} |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
return $result; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
///////////////////////////// |
706
|
|
|
// Persistence |
707
|
|
|
///////////////////////////// |
708
|
|
|
|
709
|
|
|
/** |
710
|
|
|
* Saves the model. |
711
|
|
|
* |
712
|
|
|
* @return bool |
713
|
|
|
*/ |
714
|
|
|
public function save() |
715
|
|
|
{ |
716
|
|
|
if (!$this->_persisted) { |
717
|
|
|
return $this->create(); |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
return $this->set($this->_unsaved); |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
/** |
724
|
|
|
* Creates a new model. |
725
|
|
|
* |
726
|
|
|
* @param array $data optional key-value properties to set |
727
|
|
|
* |
728
|
|
|
* @return bool |
729
|
|
|
* |
730
|
|
|
* @throws BadMethodCallException when called on an existing model |
731
|
|
|
*/ |
732
|
|
|
public function create(array $data = []) |
733
|
|
|
{ |
734
|
|
|
if ($this->_persisted) { |
735
|
|
|
throw new BadMethodCallException('Cannot call create() on an existing model'); |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
if (!empty($data)) { |
739
|
|
|
foreach ($data as $k => $value) { |
740
|
|
|
$this->$k = $value; |
741
|
|
|
} |
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
// dispatch the model.creating event |
745
|
|
|
$event = $this->dispatch(ModelEvent::CREATING); |
746
|
|
|
if ($event->isPropagationStopped()) { |
747
|
|
|
return false; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
foreach (static::$properties as $name => $property) { |
751
|
|
|
// add in default values |
752
|
|
|
if (!array_key_exists($name, $this->_unsaved) && array_key_exists('default', $property)) { |
753
|
|
|
$this->_unsaved[$name] = $property['default']; |
754
|
|
|
} |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
// validate the model |
758
|
|
|
if (!$this->valid()) { |
759
|
|
|
return false; |
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
// build the insert array |
763
|
|
|
$insertValues = []; |
764
|
|
|
foreach ($this->_unsaved as $k => $value) { |
765
|
|
|
// remove any non-existent or immutable properties |
766
|
|
|
$property = static::getProperty($k); |
767
|
|
|
if ($property === null || ($property['mutable'] == self::IMMUTABLE && $value !== self::getDefaultValueFor($property))) { |
768
|
|
|
continue; |
769
|
|
|
} |
770
|
|
|
|
771
|
|
|
$insertValues[$k] = $value; |
772
|
|
|
} |
773
|
|
|
|
774
|
|
|
if (!self::getDriver()->createModel($this, $insertValues)) { |
775
|
|
|
return false; |
776
|
|
|
} |
777
|
|
|
|
778
|
|
|
// determine the model's new ID |
779
|
|
|
$ids = $this->getNewIds(); |
780
|
|
|
|
781
|
|
|
// NOTE clear the local cache before the model.created |
782
|
|
|
// event so that fetching values forces a reload |
783
|
|
|
// from the data layer |
784
|
|
|
$this->clearCache(); |
785
|
|
|
$this->_values = $ids; |
786
|
|
|
|
787
|
|
|
// dispatch the model.created event |
788
|
|
|
$event = $this->dispatch(ModelEvent::CREATED); |
789
|
|
|
if ($event->isPropagationStopped()) { |
790
|
|
|
return false; |
791
|
|
|
} |
792
|
|
|
|
793
|
|
|
return true; |
794
|
|
|
} |
795
|
|
|
|
796
|
|
|
/** |
797
|
|
|
* Gets the IDs for a newly created model. |
798
|
|
|
* |
799
|
|
|
* @return string |
800
|
|
|
*/ |
801
|
|
|
protected function getNewIds() |
802
|
|
|
{ |
803
|
|
|
$ids = []; |
804
|
|
|
foreach (static::$ids as $k) { |
805
|
|
|
// attempt use the supplied value if the ID property is mutable |
806
|
|
|
$property = static::getProperty($k); |
807
|
|
|
if (in_array($property['mutable'], [self::MUTABLE, self::MUTABLE_CREATE_ONLY]) && isset($this->_unsaved[$k])) { |
808
|
|
|
$ids[$k] = $this->_unsaved[$k]; |
809
|
|
|
} else { |
810
|
|
|
$ids[$k] = self::getDriver()->getCreatedID($this, $k); |
811
|
|
|
} |
812
|
|
|
} |
813
|
|
|
|
814
|
|
|
return $ids; |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
/** |
818
|
|
|
* Updates the model. |
819
|
|
|
* |
820
|
|
|
* @param array $data optional key-value properties to set |
821
|
|
|
* |
822
|
|
|
* @return bool |
823
|
|
|
* |
824
|
|
|
* @throws BadMethodCallException when not called on an existing model |
825
|
|
|
*/ |
826
|
|
|
public function set(array $data = []) |
827
|
|
|
{ |
828
|
|
|
if (!$this->_persisted) { |
829
|
|
|
throw new BadMethodCallException('Can only call set() on an existing model'); |
830
|
|
|
} |
831
|
|
|
|
832
|
|
|
if (!empty($data)) { |
833
|
|
|
foreach ($data as $k => $value) { |
834
|
|
|
$this->$k = $value; |
835
|
|
|
} |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
// not updating anything? |
839
|
|
|
if (count($this->_unsaved) === 0) { |
840
|
|
|
return true; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
// dispatch the model.updating event |
844
|
|
|
$event = $this->dispatch(ModelEvent::UPDATING); |
845
|
|
|
if ($event->isPropagationStopped()) { |
846
|
|
|
return false; |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
// validate the model |
850
|
|
|
if (!$this->valid()) { |
851
|
|
|
return false; |
852
|
|
|
} |
853
|
|
|
|
854
|
|
|
// build the update array |
855
|
|
|
$updateValues = []; |
856
|
|
|
foreach ($this->_unsaved as $k => $value) { |
857
|
|
|
// remove any non-existent or immutable properties |
858
|
|
|
$property = static::getProperty($k); |
859
|
|
|
if ($property === null || $property['mutable'] != self::MUTABLE) { |
860
|
|
|
continue; |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
$updateValues[$k] = $value; |
864
|
|
|
} |
865
|
|
|
|
866
|
|
|
if (!self::getDriver()->updateModel($this, $updateValues)) { |
867
|
|
|
return false; |
868
|
|
|
} |
869
|
|
|
|
870
|
|
|
// clear the local cache before the model.updated |
871
|
|
|
// event so that fetching values forces a reload |
872
|
|
|
// from the data layer |
873
|
|
|
$this->clearCache(); |
874
|
|
|
|
875
|
|
|
// dispatch the model.updated event |
876
|
|
|
$event = $this->dispatch(ModelEvent::UPDATED); |
877
|
|
|
if ($event->isPropagationStopped()) { |
878
|
|
|
return false; |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
return true; |
882
|
|
|
} |
883
|
|
|
|
884
|
|
|
/** |
885
|
|
|
* Delete the model. |
886
|
|
|
* |
887
|
|
|
* @return bool success |
888
|
|
|
*/ |
889
|
|
|
public function delete() |
890
|
|
|
{ |
891
|
|
|
if (!$this->_persisted) { |
892
|
|
|
throw new BadMethodCallException('Can only call delete() on an existing model'); |
893
|
|
|
} |
894
|
|
|
|
895
|
|
|
// dispatch the model.deleting event |
896
|
|
|
$event = $this->dispatch(ModelEvent::DELETING); |
897
|
|
|
if ($event->isPropagationStopped()) { |
898
|
|
|
return false; |
899
|
|
|
} |
900
|
|
|
|
901
|
|
|
$deleted = self::getDriver()->deleteModel($this); |
902
|
|
|
|
903
|
|
|
if ($deleted) { |
904
|
|
|
// dispatch the model.deleted event |
905
|
|
|
$event = $this->dispatch(ModelEvent::DELETED); |
906
|
|
|
if ($event->isPropagationStopped()) { |
907
|
|
|
return false; |
908
|
|
|
} |
909
|
|
|
|
910
|
|
|
// NOTE clear the local cache before the model.deleted |
911
|
|
|
// event so that fetching values forces a reload |
912
|
|
|
// from the data layer |
913
|
|
|
$this->clearCache(); |
914
|
|
|
} |
915
|
|
|
|
916
|
|
|
return $deleted; |
917
|
|
|
} |
918
|
|
|
|
919
|
|
|
/** |
920
|
|
|
* Tells if the model has been persisted. |
921
|
|
|
* |
922
|
|
|
* @return bool |
923
|
|
|
*/ |
924
|
|
|
public function persisted() |
925
|
|
|
{ |
926
|
|
|
return $this->_persisted; |
927
|
|
|
} |
928
|
|
|
|
929
|
|
|
/** |
930
|
|
|
* Loads the model from the data layer. |
931
|
|
|
* |
932
|
|
|
* @return self |
933
|
|
|
*/ |
934
|
|
|
public function refresh() |
935
|
|
|
{ |
936
|
|
|
if (!$this->_persisted) { |
937
|
|
|
return $this; |
938
|
|
|
} |
939
|
|
|
|
940
|
|
|
$query = static::query(); |
941
|
|
|
$query->where($this->ids()); |
942
|
|
|
|
943
|
|
|
$values = self::getDriver()->queryModels($query); |
944
|
|
|
|
945
|
|
|
if (count($values) === 0) { |
946
|
|
|
return $this; |
947
|
|
|
} |
948
|
|
|
|
949
|
|
|
return $this->refreshWith($values[0]); |
950
|
|
|
} |
951
|
|
|
|
952
|
|
|
/** |
953
|
|
|
* Loads values into the model retrieved from the data layer. |
954
|
|
|
* |
955
|
|
|
* @param array $values values |
956
|
|
|
* |
957
|
|
|
* @return self |
958
|
|
|
*/ |
959
|
|
|
public function refreshWith(array $values) |
960
|
|
|
{ |
961
|
|
|
$this->_persisted = true; |
962
|
|
|
$this->_values = $values; |
963
|
|
|
|
964
|
|
|
return $this; |
965
|
|
|
} |
966
|
|
|
|
967
|
|
|
/** |
968
|
|
|
* Clears the cache for this model. |
969
|
|
|
* |
970
|
|
|
* @return self |
971
|
|
|
*/ |
972
|
|
|
public function clearCache() |
973
|
|
|
{ |
974
|
|
|
$this->_unsaved = []; |
975
|
|
|
$this->_values = []; |
976
|
|
|
|
977
|
|
|
return $this; |
978
|
|
|
} |
979
|
|
|
|
980
|
|
|
///////////////////////////// |
981
|
|
|
// Queries |
982
|
|
|
///////////////////////////// |
983
|
|
|
|
984
|
|
|
/** |
985
|
|
|
* Generates a new query instance. |
986
|
|
|
* |
987
|
|
|
* @return Query |
988
|
|
|
*/ |
989
|
|
|
public static function query() |
990
|
|
|
{ |
991
|
|
|
// Create a new model instance for the query to ensure |
992
|
|
|
// that the model's initialize() method gets called. |
993
|
|
|
// Otherwise, the property definitions will be incomplete. |
994
|
|
|
$model = new static(); |
995
|
|
|
|
996
|
|
|
return new Query($model); |
997
|
|
|
} |
998
|
|
|
|
999
|
|
|
/** |
1000
|
|
|
* Finds a single instance of a model given it's ID. |
1001
|
|
|
* |
1002
|
|
|
* @param mixed $id |
1003
|
|
|
* |
1004
|
|
|
* @return Model|null |
1005
|
|
|
*/ |
1006
|
|
|
public static function find($id) |
1007
|
|
|
{ |
1008
|
|
|
$model = static::buildFromId($id); |
1009
|
|
|
|
1010
|
|
|
return static::query()->where($model->ids())->first(); |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
|
|
/** |
1014
|
|
|
* Finds a single instance of a model given it's ID or throws an exception. |
1015
|
|
|
* |
1016
|
|
|
* @param mixed $id |
1017
|
|
|
* |
1018
|
|
|
* @return Model|false |
1019
|
|
|
* |
1020
|
|
|
* @throws NotFoundException when a model could not be found |
1021
|
|
|
*/ |
1022
|
|
|
public static function findOrFail($id) |
1023
|
|
|
{ |
1024
|
|
|
$model = static::find($id); |
1025
|
|
|
if (!$model) { |
1026
|
|
|
throw new NotFoundException('Could not find the requested '.static::modelName()); |
1027
|
|
|
} |
1028
|
|
|
|
1029
|
|
|
return $model; |
1030
|
|
|
} |
1031
|
|
|
|
1032
|
|
|
/** |
1033
|
|
|
* Gets the toal number of records matching an optional criteria. |
1034
|
|
|
* |
1035
|
|
|
* @param array $where criteria |
1036
|
|
|
* |
1037
|
|
|
* @return int total |
1038
|
|
|
*/ |
1039
|
|
|
public static function totalRecords(array $where = []) |
1040
|
|
|
{ |
1041
|
|
|
$query = static::query(); |
1042
|
|
|
$query->where($where); |
1043
|
|
|
|
1044
|
|
|
return self::getDriver()->totalRecords($query); |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
///////////////////////////// |
1048
|
|
|
// Relationships |
1049
|
|
|
///////////////////////////// |
1050
|
|
|
|
1051
|
|
|
/** |
1052
|
|
|
* Creates the parent side of a One-To-One relationship. |
1053
|
|
|
* |
1054
|
|
|
* @param string $model foreign model class |
1055
|
|
|
* @param string $foreignKey identifying key on foreign model |
1056
|
|
|
* @param string $localKey identifying key on local model |
1057
|
|
|
* |
1058
|
|
|
* @return \Pulsar\Relation\Relation |
1059
|
|
|
*/ |
1060
|
|
View Code Duplication |
public function hasOne($model, $foreignKey = '', $localKey = '') |
|
|
|
|
1061
|
|
|
{ |
1062
|
|
|
// the default local key would look like `user_id` |
1063
|
|
|
// for a model named User |
1064
|
|
|
if (!$foreignKey) { |
1065
|
|
|
$inflector = Inflector::get(); |
1066
|
|
|
$foreignKey = strtolower($inflector->underscore(static::modelName())).'_id'; |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
|
|
if (!$localKey) { |
1070
|
|
|
$localKey = self::DEFAULT_ID_PROPERTY; |
1071
|
|
|
} |
1072
|
|
|
|
1073
|
|
|
return new HasOne($model, $foreignKey, $localKey, $this); |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
|
|
/** |
1077
|
|
|
* Creates the child side of a One-To-One or One-To-Many relationship. |
1078
|
|
|
* |
1079
|
|
|
* @param string $model foreign model class |
1080
|
|
|
* @param string $foreignKey identifying key on foreign model |
1081
|
|
|
* @param string $localKey identifying key on local model |
1082
|
|
|
* |
1083
|
|
|
* @return \Pulsar\Relation\Relation |
1084
|
|
|
*/ |
1085
|
|
View Code Duplication |
public function belongsTo($model, $foreignKey = '', $localKey = '') |
|
|
|
|
1086
|
|
|
{ |
1087
|
|
|
if (!$foreignKey) { |
1088
|
|
|
$foreignKey = self::DEFAULT_ID_PROPERTY; |
1089
|
|
|
} |
1090
|
|
|
|
1091
|
|
|
// the default local key would look like `user_id` |
1092
|
|
|
// for a model named User |
1093
|
|
|
if (!$localKey) { |
1094
|
|
|
$inflector = Inflector::get(); |
1095
|
|
|
$localKey = strtolower($inflector->underscore($model::modelName())).'_id'; |
1096
|
|
|
} |
1097
|
|
|
|
1098
|
|
|
return new BelongsTo($model, $foreignKey, $localKey, $this); |
1099
|
|
|
} |
1100
|
|
|
|
1101
|
|
|
/** |
1102
|
|
|
* Creates the parent side of a Many-To-One or Many-To-Many relationship. |
1103
|
|
|
* |
1104
|
|
|
* @param string $model foreign model class |
1105
|
|
|
* @param string $foreignKey identifying key on foreign model |
1106
|
|
|
* @param string $localKey identifying key on local model |
1107
|
|
|
* |
1108
|
|
|
* @return \Pulsar\Relation\Relation |
1109
|
|
|
*/ |
1110
|
|
View Code Duplication |
public function hasMany($model, $foreignKey = '', $localKey = '') |
|
|
|
|
1111
|
|
|
{ |
1112
|
|
|
// the default local key would look like `user_id` |
1113
|
|
|
// for a model named User |
1114
|
|
|
if (!$foreignKey) { |
1115
|
|
|
$inflector = Inflector::get(); |
1116
|
|
|
$foreignKey = strtolower($inflector->underscore(static::modelName())).'_id'; |
1117
|
|
|
} |
1118
|
|
|
|
1119
|
|
|
if (!$localKey) { |
1120
|
|
|
$localKey = self::DEFAULT_ID_PROPERTY; |
1121
|
|
|
} |
1122
|
|
|
|
1123
|
|
|
return new HasMany($model, $foreignKey, $localKey, $this); |
1124
|
|
|
} |
1125
|
|
|
|
1126
|
|
|
/** |
1127
|
|
|
* Creates the child side of a Many-To-Many relationship. |
1128
|
|
|
* |
1129
|
|
|
* @param string $model foreign model class |
1130
|
|
|
* @param string $foreignKey identifying key on foreign model |
1131
|
|
|
* @param string $localKey identifying key on local model |
1132
|
|
|
* |
1133
|
|
|
* @return \Pulsar\Relation\Relation |
1134
|
|
|
*/ |
1135
|
|
View Code Duplication |
public function belongsToMany($model, $foreignKey = '', $localKey = '') |
|
|
|
|
1136
|
|
|
{ |
1137
|
|
|
if (!$foreignKey) { |
1138
|
|
|
$foreignKey = self::DEFAULT_ID_PROPERTY; |
1139
|
|
|
} |
1140
|
|
|
|
1141
|
|
|
// the default local key would look like `user_id` |
1142
|
|
|
// for a model named User |
1143
|
|
|
if (!$localKey) { |
1144
|
|
|
$inflector = Inflector::get(); |
1145
|
|
|
$localKey = strtolower($inflector->underscore($model::modelName())).'_id'; |
1146
|
|
|
} |
1147
|
|
|
|
1148
|
|
|
return new BelongsToMany($model, $foreignKey, $localKey, $this); |
1149
|
|
|
} |
1150
|
|
|
|
1151
|
|
|
/** |
1152
|
|
|
* Loads a given relationship (if not already) and returns |
1153
|
|
|
* its results. |
1154
|
|
|
* |
1155
|
|
|
* @param string $name |
1156
|
|
|
* |
1157
|
|
|
* @return mixed |
1158
|
|
|
*/ |
1159
|
|
|
protected function loadRelationship($name) |
1160
|
|
|
{ |
1161
|
|
|
if (!isset($this->_values[$name])) { |
1162
|
|
|
$relationship = $this->$name(); |
1163
|
|
|
$this->_values[$name] = $relationship->getResults(); |
1164
|
|
|
} |
1165
|
|
|
|
1166
|
|
|
return $this->_values[$name]; |
1167
|
|
|
} |
1168
|
|
|
|
1169
|
|
|
///////////////////////////// |
1170
|
|
|
// Events |
1171
|
|
|
///////////////////////////// |
1172
|
|
|
|
1173
|
|
|
/** |
1174
|
|
|
* Gets the event dispatcher. |
1175
|
|
|
* |
1176
|
|
|
* @return \Symfony\Component\EventDispatcher\EventDispatcher |
1177
|
|
|
*/ |
1178
|
|
|
public static function getDispatcher($ignoreCache = false) |
1179
|
|
|
{ |
1180
|
|
|
$class = get_called_class(); |
1181
|
|
|
if ($ignoreCache || !isset(self::$dispatchers[$class])) { |
1182
|
|
|
self::$dispatchers[$class] = new EventDispatcher(); |
1183
|
|
|
} |
1184
|
|
|
|
1185
|
|
|
return self::$dispatchers[$class]; |
1186
|
|
|
} |
1187
|
|
|
|
1188
|
|
|
/** |
1189
|
|
|
* Subscribes to a listener to an event. |
1190
|
|
|
* |
1191
|
|
|
* @param string $event event name |
1192
|
|
|
* @param callable $listener |
1193
|
|
|
* @param int $priority optional priority, higher #s get called first |
1194
|
|
|
*/ |
1195
|
|
|
public static function listen($event, callable $listener, $priority = 0) |
1196
|
|
|
{ |
1197
|
|
|
static::getDispatcher()->addListener($event, $listener, $priority); |
1198
|
|
|
} |
1199
|
|
|
|
1200
|
|
|
/** |
1201
|
|
|
* Adds a listener to the model.creating event. |
1202
|
|
|
* |
1203
|
|
|
* @param callable $listener |
1204
|
|
|
* @param int $priority |
1205
|
|
|
*/ |
1206
|
|
|
public static function creating(callable $listener, $priority = 0) |
1207
|
|
|
{ |
1208
|
|
|
static::listen(ModelEvent::CREATING, $listener, $priority); |
1209
|
|
|
} |
1210
|
|
|
|
1211
|
|
|
/** |
1212
|
|
|
* Adds a listener to the model.created event. |
1213
|
|
|
* |
1214
|
|
|
* @param callable $listener |
1215
|
|
|
* @param int $priority |
1216
|
|
|
*/ |
1217
|
|
|
public static function created(callable $listener, $priority = 0) |
1218
|
|
|
{ |
1219
|
|
|
static::listen(ModelEvent::CREATED, $listener, $priority); |
1220
|
|
|
} |
1221
|
|
|
|
1222
|
|
|
/** |
1223
|
|
|
* Adds a listener to the model.updating event. |
1224
|
|
|
* |
1225
|
|
|
* @param callable $listener |
1226
|
|
|
* @param int $priority |
1227
|
|
|
*/ |
1228
|
|
|
public static function updating(callable $listener, $priority = 0) |
1229
|
|
|
{ |
1230
|
|
|
static::listen(ModelEvent::UPDATING, $listener, $priority); |
1231
|
|
|
} |
1232
|
|
|
|
1233
|
|
|
/** |
1234
|
|
|
* Adds a listener to the model.updated event. |
1235
|
|
|
* |
1236
|
|
|
* @param callable $listener |
1237
|
|
|
* @param int $priority |
1238
|
|
|
*/ |
1239
|
|
|
public static function updated(callable $listener, $priority = 0) |
1240
|
|
|
{ |
1241
|
|
|
static::listen(ModelEvent::UPDATED, $listener, $priority); |
1242
|
|
|
} |
1243
|
|
|
|
1244
|
|
|
/** |
1245
|
|
|
* Adds a listener to the model.deleting event. |
1246
|
|
|
* |
1247
|
|
|
* @param callable $listener |
1248
|
|
|
* @param int $priority |
1249
|
|
|
*/ |
1250
|
|
|
public static function deleting(callable $listener, $priority = 0) |
1251
|
|
|
{ |
1252
|
|
|
static::listen(ModelEvent::DELETING, $listener, $priority); |
1253
|
|
|
} |
1254
|
|
|
|
1255
|
|
|
/** |
1256
|
|
|
* Adds a listener to the model.deleted event. |
1257
|
|
|
* |
1258
|
|
|
* @param callable $listener |
1259
|
|
|
* @param int $priority |
1260
|
|
|
*/ |
1261
|
|
|
public static function deleted(callable $listener, $priority = 0) |
1262
|
|
|
{ |
1263
|
|
|
static::listen(ModelEvent::DELETED, $listener, $priority); |
1264
|
|
|
} |
1265
|
|
|
|
1266
|
|
|
/** |
1267
|
|
|
* Dispatches an event. |
1268
|
|
|
* |
1269
|
|
|
* @param string $eventName |
1270
|
|
|
* |
1271
|
|
|
* @return ModelEvent |
1272
|
|
|
*/ |
1273
|
|
|
protected function dispatch($eventName) |
1274
|
|
|
{ |
1275
|
|
|
$event = new ModelEvent($this); |
1276
|
|
|
|
1277
|
|
|
return static::getDispatcher()->dispatch($eventName, $event); |
1278
|
|
|
} |
1279
|
|
|
|
1280
|
|
|
///////////////////////////// |
1281
|
|
|
// Validation |
1282
|
|
|
///////////////////////////// |
1283
|
|
|
|
1284
|
|
|
/** |
1285
|
|
|
* Gets the error stack for this model instance. Used to |
1286
|
|
|
* keep track of validation errors. |
1287
|
|
|
* |
1288
|
|
|
* @return Errors |
1289
|
|
|
*/ |
1290
|
|
|
public function errors() |
1291
|
|
|
{ |
1292
|
|
|
if (!$this->_errors) { |
1293
|
|
|
$this->_errors = new Errors($this, self::$locale); |
1294
|
|
|
} |
1295
|
|
|
|
1296
|
|
|
return $this->_errors; |
1297
|
|
|
} |
1298
|
|
|
|
1299
|
|
|
/** |
1300
|
|
|
* Checks if the model is valid in its current state. |
1301
|
|
|
* |
1302
|
|
|
* @return bool |
1303
|
|
|
*/ |
1304
|
|
|
public function valid() |
1305
|
|
|
{ |
1306
|
|
|
// clear any previous errors |
1307
|
|
|
$this->errors()->clear(); |
1308
|
|
|
|
1309
|
|
|
// run the validator against the model values |
1310
|
|
|
$validator = $this->getValidator(); |
1311
|
|
|
$values = $this->_values + $this->_unsaved; |
1312
|
|
|
$validated = $validator->validate($values); |
1313
|
|
|
|
1314
|
|
|
// add back any modified unsaved values |
1315
|
|
|
foreach (array_keys($this->_unsaved) as $k) { |
1316
|
|
|
$this->_unsaved[$k] = $values[$k]; |
1317
|
|
|
} |
1318
|
|
|
|
1319
|
|
|
return $validated; |
1320
|
|
|
} |
1321
|
|
|
|
1322
|
|
|
/** |
1323
|
|
|
* Gets a new validator instance for this model. |
1324
|
|
|
* |
1325
|
|
|
* @return Validator |
1326
|
|
|
*/ |
1327
|
|
|
public function getValidator() |
1328
|
|
|
{ |
1329
|
|
|
return new Validator(static::$validations, $this->errors()); |
1330
|
|
|
} |
1331
|
|
|
} |
1332
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.