|
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
|
|
|
|
|
12
|
|
|
namespace Pulsar; |
|
13
|
|
|
|
|
14
|
|
|
use ArrayAccess; |
|
15
|
|
|
use BadMethodCallException; |
|
16
|
|
|
use Carbon\Carbon; |
|
17
|
|
|
use ICanBoogie\Inflector; |
|
18
|
|
|
use Infuse\Locale; |
|
19
|
|
|
use InvalidArgumentException; |
|
20
|
|
|
use Pulsar\Adapter\AdapterInterface; |
|
21
|
|
|
use Pulsar\Exception\AdapterMissingException; |
|
22
|
|
|
use Pulsar\Exception\MassAssignmentException; |
|
23
|
|
|
use Pulsar\Exception\NotFoundException; |
|
24
|
|
|
use Pulsar\Relation\HasOne; |
|
25
|
|
|
use Pulsar\Relation\BelongsTo; |
|
26
|
|
|
use Pulsar\Relation\HasMany; |
|
27
|
|
|
use Pulsar\Relation\BelongsToMany; |
|
28
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
|
29
|
|
|
|
|
30
|
|
|
abstract class Model implements ArrayAccess |
|
31
|
|
|
{ |
|
32
|
|
|
const TYPE_STRING = 'string'; |
|
33
|
|
|
const TYPE_INTEGER = 'integer'; |
|
34
|
|
|
const TYPE_FLOAT = 'float'; |
|
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
|
|
|
const DEFAULT_DATE_FORMAT = 'U'; // unix timestamps |
|
43
|
|
|
|
|
44
|
|
|
// DEPRECATED |
|
45
|
|
|
const TYPE_NUMBER = 'float'; |
|
46
|
|
|
const IMMUTABLE = 0; |
|
47
|
|
|
const MUTABLE_CREATE_ONLY = 1; |
|
48
|
|
|
const MUTABLE = 2; |
|
49
|
|
|
|
|
50
|
|
|
///////////////////////////// |
|
51
|
|
|
// Model visible variables |
|
52
|
|
|
///////////////////////////// |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* List of model ID property names. |
|
56
|
|
|
* |
|
57
|
|
|
* @staticvar array |
|
58
|
|
|
*/ |
|
59
|
|
|
protected static $ids = [self::DEFAULT_ID_PROPERTY]; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* Validation rules expressed as a key-value map with |
|
63
|
|
|
* property names as the keys. |
|
64
|
|
|
* i.e. ['name' => 'string:2']. |
|
65
|
|
|
* |
|
66
|
|
|
* @staticvar array |
|
67
|
|
|
*/ |
|
68
|
|
|
protected static $validations = []; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @staticvar array |
|
72
|
|
|
*/ |
|
73
|
|
|
protected static $relationships = []; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* @staticvar array |
|
77
|
|
|
*/ |
|
78
|
|
|
protected static $relationshipsDeprecated = []; |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @staticvar array |
|
82
|
|
|
*/ |
|
83
|
|
|
protected static $dates = []; |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* @staticvar array |
|
87
|
|
|
*/ |
|
88
|
|
|
protected static $dispatchers = []; |
|
89
|
|
|
|
|
90
|
|
|
/** |
|
91
|
|
|
* @var array |
|
92
|
|
|
*/ |
|
93
|
|
|
protected $_values = []; |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* @var array |
|
97
|
|
|
*/ |
|
98
|
|
|
protected $_unsaved = []; |
|
99
|
|
|
|
|
100
|
|
|
/** |
|
101
|
|
|
* @var bool |
|
102
|
|
|
*/ |
|
103
|
|
|
protected $_persisted = false; |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* @var Errors |
|
107
|
|
|
*/ |
|
108
|
|
|
protected $_errors; |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* @deprecated |
|
112
|
|
|
* |
|
113
|
|
|
* @var array |
|
114
|
|
|
*/ |
|
115
|
|
|
protected $_relationships = []; |
|
116
|
|
|
|
|
117
|
|
|
///////////////////////////// |
|
118
|
|
|
// Base model variables |
|
119
|
|
|
///////////////////////////// |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* @staticvar array |
|
123
|
|
|
*/ |
|
124
|
|
|
private static $initialized = []; |
|
125
|
|
|
|
|
126
|
|
|
/** |
|
127
|
|
|
* @staticvar AdapterInterface |
|
128
|
|
|
*/ |
|
129
|
|
|
private static $adapter; |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* @staticvar Locale |
|
133
|
|
|
*/ |
|
134
|
|
|
private static $locale; |
|
135
|
|
|
|
|
136
|
|
|
/** |
|
137
|
|
|
* @staticvar array |
|
138
|
|
|
*/ |
|
139
|
|
|
private static $tablenames = []; |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* @staticvar array |
|
143
|
|
|
*/ |
|
144
|
|
|
private static $accessors = []; |
|
145
|
|
|
|
|
146
|
|
|
/** |
|
147
|
|
|
* @staticvar array |
|
148
|
|
|
*/ |
|
149
|
|
|
private static $mutators = []; |
|
150
|
|
|
|
|
151
|
|
|
/** |
|
152
|
|
|
* @var bool |
|
153
|
|
|
*/ |
|
154
|
|
|
private $_ignoreUnsaved; |
|
155
|
|
|
|
|
156
|
|
|
/** |
|
157
|
|
|
* Creates a new model object. |
|
158
|
|
|
* |
|
159
|
|
|
* @param array $values values to fill model with |
|
160
|
|
|
*/ |
|
161
|
|
|
public function __construct(array $values = []) |
|
162
|
|
|
{ |
|
163
|
|
|
// parse deprecated property definitions |
|
164
|
|
|
if (property_exists($this, 'properties')) { |
|
165
|
|
|
$values = array_replace( |
|
166
|
|
|
$this->defaultValuesDeprecated(), |
|
|
|
|
|
|
167
|
|
|
$values); |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
foreach ($values as $k => $v) { |
|
171
|
|
|
$this->setValue($k, $v, false); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
// ensure the initialize function is called only once |
|
175
|
|
|
$k = get_called_class(); |
|
176
|
|
|
if (!isset(self::$initialized[$k])) { |
|
177
|
|
|
$this->initialize(); |
|
178
|
|
|
self::$initialized[$k] = true; |
|
179
|
|
|
} |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
/** |
|
183
|
|
|
* @deprecated |
|
184
|
|
|
* Sets the default values from a deprecated $properties format |
|
185
|
|
|
* |
|
186
|
|
|
* @return array |
|
187
|
|
|
*/ |
|
188
|
|
|
private function defaultValuesDeprecated() |
|
189
|
|
|
{ |
|
190
|
|
|
$values = []; |
|
191
|
|
|
foreach (static::$properties as $k => $definition) { |
|
192
|
|
|
$values[$k] = array_value($definition, 'default'); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
return $values; |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
/** |
|
199
|
|
|
* The initialize() method is called once per model. It's used |
|
200
|
|
|
* to perform any one-off tasks before the model gets |
|
201
|
|
|
* constructed. This is a great place to add any model |
|
202
|
|
|
* properties. When extending this method be sure to call |
|
203
|
|
|
* parent::initialize() as some important stuff happens here. |
|
204
|
|
|
* If extending this method to add properties then you should |
|
205
|
|
|
* call parent::initialize() after adding any properties. |
|
206
|
|
|
*/ |
|
207
|
|
|
protected function initialize() |
|
208
|
|
|
{ |
|
209
|
|
|
// parse deprecated property definitions |
|
210
|
|
|
if (property_exists($this, 'properties')) { |
|
211
|
|
|
$this->initializeDeprecated(); |
|
|
|
|
|
|
212
|
|
|
} |
|
213
|
|
|
|
|
214
|
|
|
// add in the default ID property |
|
215
|
|
|
if (static::$ids == [self::DEFAULT_ID_PROPERTY]) { |
|
216
|
|
|
if (property_exists($this, 'casts') && !isset(static::$casts[self::DEFAULT_ID_PROPERTY])) { |
|
217
|
|
|
static::$casts[self::DEFAULT_ID_PROPERTY] = self::TYPE_INTEGER; |
|
218
|
|
|
} |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
// generates created_at and updated_at timestamps |
|
222
|
|
|
if (property_exists($this, 'autoTimestamps')) { |
|
223
|
|
|
$this->installAutoTimestamps(); |
|
224
|
|
|
} |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* @deprecated |
|
229
|
|
|
* Parses a deprecated $properties format |
|
230
|
|
|
* |
|
231
|
|
|
* @return self |
|
232
|
|
|
*/ |
|
233
|
|
|
private function initializeDeprecated() |
|
234
|
|
|
{ |
|
235
|
|
|
foreach (static::$properties as $k => $definition) { |
|
236
|
|
|
// parse property types |
|
237
|
|
|
if (isset($definition['type'])) { |
|
238
|
|
|
static::$casts[$k] = $definition['type']; |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
// parse validations |
|
242
|
|
|
$rules = []; |
|
243
|
|
|
if (isset($definition['null'])) { |
|
244
|
|
|
$rules[] = ['skip_empty', []]; |
|
245
|
|
|
} |
|
246
|
|
|
|
|
247
|
|
|
if (isset($definition['required'])) { |
|
248
|
|
|
$rules[] = ['required', []]; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
if (isset($definition['validate'])) { |
|
252
|
|
|
if (is_callable($definition['validate'])) { |
|
253
|
|
|
$rules[] = ['custom', [$definition['validate']]]; |
|
254
|
|
|
} else { |
|
255
|
|
|
// explodes the string into a a list of strings |
|
256
|
|
|
// containing rules and parameters |
|
257
|
|
|
$pieces = explode('|', $definition['validate']); |
|
258
|
|
View Code Duplication |
foreach ($pieces as $piece) { |
|
|
|
|
|
|
259
|
|
|
$exp = explode(':', $piece); |
|
260
|
|
|
// [0] = rule method |
|
261
|
|
|
$method = $exp[0]; |
|
262
|
|
|
// [1] = optional method parameters |
|
263
|
|
|
$parameters = isset($exp[1]) ? explode(',', $exp[1]) : []; |
|
264
|
|
|
|
|
265
|
|
|
$rules[] = [$method, $parameters]; |
|
266
|
|
|
} |
|
267
|
|
|
} |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
if (isset($definition['unique'])) { |
|
271
|
|
|
$rules[] = ['unique', []]; |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
if ($rules) { |
|
|
|
|
|
|
275
|
|
|
static::$validations[$k] = $rules; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
// parse date formats |
|
279
|
|
|
if (property_exists($this, 'autoTimestamps')) { |
|
280
|
|
|
static::$dates['created_at'] = 'Y-m-d H:i:s'; |
|
281
|
|
|
static::$dates['updated_at'] = 'Y-m-d H:i:s'; |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
// parse deprecated relationships |
|
285
|
|
|
if (isset($definition['relation'])) { |
|
286
|
|
|
static::$relationshipsDeprecated[$k] = $definition['relation']; |
|
287
|
|
|
} |
|
288
|
|
|
|
|
289
|
|
|
// parse protected properties |
|
290
|
|
|
if (isset($definition['mutable']) && in_array($definition['mutable'], [self::IMMUTABLE, self::MUTABLE_CREATE_ONLY])) { |
|
291
|
|
|
static::$protected[] = $k; |
|
292
|
|
|
} |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
return $this; |
|
296
|
|
|
} |
|
297
|
|
|
|
|
298
|
|
|
/** |
|
299
|
|
|
* Installs the automatic timestamp properties, |
|
300
|
|
|
* `created_at` and `updated_at`. |
|
301
|
|
|
*/ |
|
302
|
|
|
private function installAutoTimestamps() |
|
303
|
|
|
{ |
|
304
|
|
|
if (property_exists($this, 'casts')) { |
|
305
|
|
|
static::$casts['created_at'] = self::TYPE_DATE; |
|
306
|
|
|
static::$casts['updated_at'] = self::TYPE_DATE; |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
|
|
self::creating(function (ModelEvent $event) { |
|
310
|
|
|
$model = $event->getModel(); |
|
311
|
|
|
$model->created_at = Carbon::now(); |
|
|
|
|
|
|
312
|
|
|
$model->updated_at = Carbon::now(); |
|
|
|
|
|
|
313
|
|
|
}); |
|
314
|
|
|
|
|
315
|
|
|
self::updating(function (ModelEvent $event) { |
|
316
|
|
|
$event->getModel()->updated_at = Carbon::now(); |
|
|
|
|
|
|
317
|
|
|
}); |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
/** |
|
321
|
|
|
* Sets the adapter for all models. |
|
322
|
|
|
* |
|
323
|
|
|
* @param AdapterInterface $adapter |
|
324
|
|
|
*/ |
|
325
|
|
|
public static function setAdapter(AdapterInterface $adapter) |
|
326
|
|
|
{ |
|
327
|
|
|
self::$adapter = $adapter; |
|
328
|
|
|
} |
|
329
|
|
|
|
|
330
|
|
|
/** |
|
331
|
|
|
* Gets the adapter for all models. |
|
332
|
|
|
* |
|
333
|
|
|
* @return AdapterInterface |
|
334
|
|
|
* |
|
335
|
|
|
* @throws AdapterMissingException |
|
336
|
|
|
*/ |
|
337
|
|
|
public static function getAdapter() |
|
338
|
|
|
{ |
|
339
|
|
|
if (!self::$adapter) { |
|
340
|
|
|
throw new AdapterMissingException('A model adapter has not been set yet.'); |
|
341
|
|
|
} |
|
342
|
|
|
|
|
343
|
|
|
return self::$adapter; |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
/** |
|
347
|
|
|
* Clears the adapter for all models. |
|
348
|
|
|
*/ |
|
349
|
|
|
public static function clearAdapter() |
|
350
|
|
|
{ |
|
351
|
|
|
self::$adapter = null; |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
/** |
|
355
|
|
|
* @deprecated |
|
356
|
|
|
*/ |
|
357
|
|
|
public static function setDriver(AdapterInterface $adapter) |
|
358
|
|
|
{ |
|
359
|
|
|
self::$adapter = $adapter; |
|
360
|
|
|
} |
|
361
|
|
|
|
|
362
|
|
|
/** |
|
363
|
|
|
* @deprecated |
|
364
|
|
|
*/ |
|
365
|
|
|
public static function getDriver() |
|
366
|
|
|
{ |
|
367
|
|
|
if (!self::$adapter) { |
|
368
|
|
|
throw new AdapterMissingException('A model adapter has not been set yet.'); |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
|
|
return self::$adapter; |
|
372
|
|
|
} |
|
373
|
|
|
|
|
374
|
|
|
/** |
|
375
|
|
|
* @deprecated |
|
376
|
|
|
*/ |
|
377
|
|
|
public static function clearDriver() |
|
378
|
|
|
{ |
|
379
|
|
|
self::$adapter = null; |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
/** |
|
383
|
|
|
* Sets the locale instance for all models. |
|
384
|
|
|
* |
|
385
|
|
|
* @param Locale $locale |
|
386
|
|
|
*/ |
|
387
|
|
|
public static function setLocale(Locale $locale) |
|
388
|
|
|
{ |
|
389
|
|
|
self::$locale = $locale; |
|
390
|
|
|
} |
|
391
|
|
|
|
|
392
|
|
|
/** |
|
393
|
|
|
* Gets the locale instance for all models. |
|
394
|
|
|
* |
|
395
|
|
|
* @return Locale |
|
396
|
|
|
*/ |
|
397
|
|
|
public static function getLocale() |
|
398
|
|
|
{ |
|
399
|
|
|
return self::$locale; |
|
400
|
|
|
} |
|
401
|
|
|
|
|
402
|
|
|
/** |
|
403
|
|
|
* Clears the locale for all models. |
|
404
|
|
|
*/ |
|
405
|
|
|
public static function clearLocale() |
|
406
|
|
|
{ |
|
407
|
|
|
self::$locale = null; |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
/** |
|
411
|
|
|
* Gets the name of the model without namespacing. |
|
412
|
|
|
* |
|
413
|
|
|
* @return string |
|
414
|
|
|
*/ |
|
415
|
|
|
public static function modelName() |
|
416
|
|
|
{ |
|
417
|
|
|
$namespace = explode('\\', get_called_class()); |
|
418
|
|
|
|
|
419
|
|
|
return end($namespace); |
|
420
|
|
|
} |
|
421
|
|
|
|
|
422
|
|
|
/** |
|
423
|
|
|
* Gets the table name of the model. |
|
424
|
|
|
* |
|
425
|
|
|
* @return string |
|
426
|
|
|
*/ |
|
427
|
|
|
public function getTablename() |
|
428
|
|
|
{ |
|
429
|
|
|
$name = static::modelName(); |
|
430
|
|
|
if (!isset(self::$tablenames[$name])) { |
|
431
|
|
|
$inflector = Inflector::get(); |
|
432
|
|
|
|
|
433
|
|
|
self::$tablenames[$name] = $inflector->camelize($inflector->pluralize($name)); |
|
434
|
|
|
} |
|
435
|
|
|
|
|
436
|
|
|
return self::$tablenames[$name]; |
|
437
|
|
|
} |
|
438
|
|
|
|
|
439
|
|
|
/** |
|
440
|
|
|
* Gets the model ID. |
|
441
|
|
|
* |
|
442
|
|
|
* @return string|number|null ID |
|
443
|
|
|
*/ |
|
444
|
|
|
public function id() |
|
445
|
|
|
{ |
|
446
|
|
|
$ids = $this->ids(); |
|
447
|
|
|
|
|
448
|
|
|
// if a single ID then return it |
|
449
|
|
|
if (count($ids) === 1) { |
|
450
|
|
|
return reset($ids); |
|
451
|
|
|
} |
|
452
|
|
|
|
|
453
|
|
|
// if multiple IDs then return a comma-separated list |
|
454
|
|
|
return implode(',', $ids); |
|
455
|
|
|
} |
|
456
|
|
|
|
|
457
|
|
|
/** |
|
458
|
|
|
* Gets a key-value map of the model ID. |
|
459
|
|
|
* |
|
460
|
|
|
* @return array ID map |
|
461
|
|
|
*/ |
|
462
|
|
|
public function ids() |
|
463
|
|
|
{ |
|
464
|
|
|
return $this->getValues(static::$ids); |
|
465
|
|
|
} |
|
466
|
|
|
|
|
467
|
|
|
///////////////////////////// |
|
468
|
|
|
// Magic Methods |
|
469
|
|
|
///////////////////////////// |
|
470
|
|
|
|
|
471
|
|
|
public function __toString() |
|
472
|
|
|
{ |
|
473
|
|
|
return get_called_class().'('.$this->id().')'; |
|
474
|
|
|
} |
|
475
|
|
|
|
|
476
|
|
|
public function __get($name) |
|
477
|
|
|
{ |
|
478
|
|
|
$value = $this->getValue($name); |
|
479
|
|
|
$this->_ignoreUnsaved = false; |
|
480
|
|
|
|
|
481
|
|
|
return $value; |
|
482
|
|
|
} |
|
483
|
|
|
|
|
484
|
|
|
public function __set($name, $value) |
|
485
|
|
|
{ |
|
486
|
|
|
$this->setValue($name, $value); |
|
487
|
|
|
} |
|
488
|
|
|
|
|
489
|
|
|
public function __isset($name) |
|
490
|
|
|
{ |
|
491
|
|
|
return array_key_exists($name, $this->_unsaved) || $this->hasProperty($name); |
|
492
|
|
|
} |
|
493
|
|
|
|
|
494
|
|
|
public function __unset($name) |
|
495
|
|
|
{ |
|
496
|
|
|
if (static::isRelationship($name)) { |
|
497
|
|
|
throw new BadMethodCallException("Cannot unset the `$name` property because it is a relationship"); |
|
498
|
|
|
} |
|
499
|
|
|
|
|
500
|
|
|
if (array_key_exists($name, $this->_unsaved)) { |
|
501
|
|
|
unset($this->_unsaved[$name]); |
|
502
|
|
|
} |
|
503
|
|
|
|
|
504
|
|
|
// if changing property, remove relation model |
|
505
|
|
|
// DEPRECATED |
|
506
|
|
|
if (isset($this->_relationships[$name])) { |
|
|
|
|
|
|
507
|
|
|
unset($this->_relationships[$name]); |
|
508
|
|
|
} |
|
509
|
|
|
} |
|
510
|
|
|
|
|
511
|
|
|
public static function __callStatic($name, $parameters) |
|
512
|
|
|
{ |
|
513
|
|
|
// Any calls to unkown static methods should be deferred to |
|
514
|
|
|
// the query. This allows calls like User::where() |
|
515
|
|
|
// to replace User::query()->where(). |
|
|
|
|
|
|
516
|
|
|
return call_user_func_array([static::query(), $name], $parameters); |
|
517
|
|
|
} |
|
518
|
|
|
|
|
519
|
|
|
///////////////////////////// |
|
520
|
|
|
// ArrayAccess Interface |
|
521
|
|
|
///////////////////////////// |
|
522
|
|
|
|
|
523
|
|
|
public function offsetExists($offset) |
|
524
|
|
|
{ |
|
525
|
|
|
return isset($this->$offset); |
|
526
|
|
|
} |
|
527
|
|
|
|
|
528
|
|
|
public function offsetGet($offset) |
|
529
|
|
|
{ |
|
530
|
|
|
return $this->$offset; |
|
531
|
|
|
} |
|
532
|
|
|
|
|
533
|
|
|
public function offsetSet($offset, $value) |
|
534
|
|
|
{ |
|
535
|
|
|
$this->$offset = $value; |
|
536
|
|
|
} |
|
537
|
|
|
|
|
538
|
|
|
public function offsetUnset($offset) |
|
539
|
|
|
{ |
|
540
|
|
|
unset($this->$offset); |
|
541
|
|
|
} |
|
542
|
|
|
|
|
543
|
|
|
///////////////////////////// |
|
544
|
|
|
// Property Definitions |
|
545
|
|
|
///////////////////////////// |
|
546
|
|
|
|
|
547
|
|
|
/** |
|
548
|
|
|
* Gets the names of the model ID properties. |
|
549
|
|
|
* |
|
550
|
|
|
* @return array |
|
551
|
|
|
*/ |
|
552
|
|
|
public static function getIdProperties() |
|
553
|
|
|
{ |
|
554
|
|
|
return static::$ids; |
|
555
|
|
|
} |
|
556
|
|
|
|
|
557
|
|
|
/** |
|
558
|
|
|
* Builds an existing model instance given a single ID value or |
|
559
|
|
|
* ordered array of ID values. |
|
560
|
|
|
* |
|
561
|
|
|
* @param mixed $id |
|
562
|
|
|
* |
|
563
|
|
|
* @return Model |
|
564
|
|
|
*/ |
|
565
|
|
|
public static function buildFromId($id) |
|
566
|
|
|
{ |
|
567
|
|
|
$ids = []; |
|
568
|
|
|
$id = (array) $id; |
|
569
|
|
|
foreach (static::$ids as $j => $k) { |
|
570
|
|
|
$ids[$k] = $id[$j]; |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
$model = new static($ids); |
|
574
|
|
|
|
|
575
|
|
|
return $model; |
|
576
|
|
|
} |
|
577
|
|
|
|
|
578
|
|
|
/** |
|
579
|
|
|
* Gets the mutator method name for a given proeprty name. |
|
580
|
|
|
* Looks for methods in the form of `setPropertyValue`. |
|
581
|
|
|
* i.e. the mutator for `last_name` would be `setLastNameValue`. |
|
582
|
|
|
* |
|
583
|
|
|
* @param string $property |
|
584
|
|
|
* |
|
585
|
|
|
* @return string|false method name if it exists |
|
586
|
|
|
*/ |
|
587
|
|
View Code Duplication |
public static function getMutator($property) |
|
|
|
|
|
|
588
|
|
|
{ |
|
589
|
|
|
$class = get_called_class(); |
|
590
|
|
|
|
|
591
|
|
|
$k = $class.':'.$property; |
|
592
|
|
|
if (!array_key_exists($k, self::$mutators)) { |
|
593
|
|
|
$inflector = Inflector::get(); |
|
594
|
|
|
$method = 'set'.$inflector->camelize($property).'Value'; |
|
595
|
|
|
|
|
596
|
|
|
if (!method_exists($class, $method)) { |
|
597
|
|
|
$method = false; |
|
598
|
|
|
} |
|
599
|
|
|
|
|
600
|
|
|
self::$mutators[$k] = $method; |
|
601
|
|
|
} |
|
602
|
|
|
|
|
603
|
|
|
return self::$mutators[$k]; |
|
604
|
|
|
} |
|
605
|
|
|
|
|
606
|
|
|
/** |
|
607
|
|
|
* Gets the accessor method name for a given proeprty name. |
|
608
|
|
|
* Looks for methods in the form of `getPropertyValue`. |
|
609
|
|
|
* i.e. the accessor for `last_name` would be `getLastNameValue`. |
|
610
|
|
|
* |
|
611
|
|
|
* @param string $property |
|
612
|
|
|
* |
|
613
|
|
|
* @return string|false method name if it exists |
|
614
|
|
|
*/ |
|
615
|
|
View Code Duplication |
public static function getAccessor($property) |
|
|
|
|
|
|
616
|
|
|
{ |
|
617
|
|
|
$class = get_called_class(); |
|
618
|
|
|
|
|
619
|
|
|
$k = $class.':'.$property; |
|
620
|
|
|
if (!array_key_exists($k, self::$accessors)) { |
|
621
|
|
|
$inflector = Inflector::get(); |
|
622
|
|
|
$method = 'get'.$inflector->camelize($property).'Value'; |
|
623
|
|
|
|
|
624
|
|
|
if (!method_exists($class, $method)) { |
|
625
|
|
|
$method = false; |
|
626
|
|
|
} |
|
627
|
|
|
|
|
628
|
|
|
self::$accessors[$k] = $method; |
|
629
|
|
|
} |
|
630
|
|
|
|
|
631
|
|
|
return self::$accessors[$k]; |
|
632
|
|
|
} |
|
633
|
|
|
|
|
634
|
|
|
/** |
|
635
|
|
|
* Checks if a given property is a relationship. |
|
636
|
|
|
* |
|
637
|
|
|
* @param string $property |
|
638
|
|
|
* |
|
639
|
|
|
* @return bool |
|
640
|
|
|
*/ |
|
641
|
|
|
public static function isRelationship($property) |
|
642
|
|
|
{ |
|
643
|
|
|
return in_array($property, static::$relationships); |
|
644
|
|
|
} |
|
645
|
|
|
|
|
646
|
|
|
/** |
|
647
|
|
|
* Gets the string date format for a property. Defaults to |
|
648
|
|
|
* UNIX timestamps. |
|
649
|
|
|
* |
|
650
|
|
|
* @param string $property |
|
651
|
|
|
* |
|
652
|
|
|
* @return string |
|
653
|
|
|
*/ |
|
654
|
|
|
public static function getDateFormat($property) |
|
655
|
|
|
{ |
|
656
|
|
|
if (isset(static::$dates[$property])) { |
|
657
|
|
|
return static::$dates[$property]; |
|
658
|
|
|
} |
|
659
|
|
|
|
|
660
|
|
|
return self::DEFAULT_DATE_FORMAT; |
|
661
|
|
|
} |
|
662
|
|
|
|
|
663
|
|
|
/** |
|
664
|
|
|
* Gets the title of a property. |
|
665
|
|
|
* |
|
666
|
|
|
* @param string $name |
|
667
|
|
|
* |
|
668
|
|
|
* @return string |
|
669
|
|
|
*/ |
|
670
|
|
|
public static function getPropertyTitle($name) |
|
671
|
|
|
{ |
|
672
|
|
|
// attmept to fetch the title from the Locale service |
|
673
|
|
|
$k = 'pulsar.properties.'.static::modelName().'.'.$name; |
|
674
|
|
|
if (self::$locale && $title = self::$locale->t($k)) { |
|
675
|
|
|
if ($title != $k) { |
|
676
|
|
|
return $title; |
|
677
|
|
|
} |
|
678
|
|
|
} |
|
679
|
|
|
|
|
680
|
|
|
return Inflector::get()->humanize($name); |
|
681
|
|
|
} |
|
682
|
|
|
|
|
683
|
|
|
/** |
|
684
|
|
|
* Gets the type cast for a property. |
|
685
|
|
|
* |
|
686
|
|
|
* @param string $property |
|
687
|
|
|
* |
|
688
|
|
|
* @return string|null |
|
689
|
|
|
*/ |
|
690
|
|
|
public static function getPropertyType($property) |
|
691
|
|
|
{ |
|
692
|
|
|
if (property_exists(get_called_class(), 'casts')) { |
|
693
|
|
|
return array_value(static::$casts, $property); |
|
694
|
|
|
} |
|
695
|
|
|
} |
|
696
|
|
|
|
|
697
|
|
|
/** |
|
698
|
|
|
* Casts a value to a given type. |
|
699
|
|
|
* |
|
700
|
|
|
* @param string|null $type |
|
701
|
|
|
* @param mixed $value |
|
702
|
|
|
* @param string $property optional property name |
|
703
|
|
|
* |
|
704
|
|
|
* @return mixed casted value |
|
705
|
|
|
*/ |
|
706
|
|
|
public static function cast($type, $value, $property = null) |
|
707
|
|
|
{ |
|
708
|
|
|
if ($value === null) { |
|
709
|
|
|
return; |
|
710
|
|
|
} |
|
711
|
|
|
|
|
712
|
|
|
if ($type == self::TYPE_DATE) { |
|
713
|
|
|
$format = self::getDateFormat($property); |
|
714
|
|
|
|
|
715
|
|
|
return Property::to_date($value, $format); |
|
716
|
|
|
} |
|
717
|
|
|
|
|
718
|
|
|
$m = 'to_'.$type; |
|
719
|
|
|
|
|
720
|
|
|
return Property::$m($value); |
|
721
|
|
|
} |
|
722
|
|
|
|
|
723
|
|
|
/** |
|
724
|
|
|
* Gets the properties of this model. |
|
725
|
|
|
* |
|
726
|
|
|
* @return array |
|
727
|
|
|
*/ |
|
728
|
|
|
public function getProperties() |
|
729
|
|
|
{ |
|
730
|
|
|
return array_unique(array_merge( |
|
731
|
|
|
static::$ids, array_keys($this->_values))); |
|
732
|
|
|
} |
|
733
|
|
|
|
|
734
|
|
|
/** |
|
735
|
|
|
* Checks if the model has a property. |
|
736
|
|
|
* |
|
737
|
|
|
* @param string $property |
|
738
|
|
|
* |
|
739
|
|
|
* @return bool has property |
|
740
|
|
|
*/ |
|
741
|
|
|
public function hasProperty($property) |
|
742
|
|
|
{ |
|
743
|
|
|
return array_key_exists($property, $this->_values) || |
|
744
|
|
|
in_array($property, static::$ids); |
|
745
|
|
|
} |
|
746
|
|
|
|
|
747
|
|
|
///////////////////////////// |
|
748
|
|
|
// Values |
|
749
|
|
|
///////////////////////////// |
|
750
|
|
|
|
|
751
|
|
|
/** |
|
752
|
|
|
* Sets an unsaved value. |
|
753
|
|
|
* |
|
754
|
|
|
* @param string $name |
|
755
|
|
|
* @param mixed $value |
|
756
|
|
|
* @param bool $unsaved when true, sets an unsaved value |
|
757
|
|
|
* |
|
758
|
|
|
* @throws BadMethodCallException when setting a relationship |
|
759
|
|
|
* |
|
760
|
|
|
* @return self |
|
761
|
|
|
*/ |
|
762
|
|
|
public function setValue($name, $value, $unsaved = true) |
|
763
|
|
|
{ |
|
764
|
|
|
if (static::isRelationship($name)) { |
|
765
|
|
|
throw new BadMethodCallException("Cannot set the `$name` property because it is a relationship"); |
|
766
|
|
|
} |
|
767
|
|
|
|
|
768
|
|
|
// cast the value |
|
769
|
|
|
if ($type = static::getPropertyType($name)) { |
|
770
|
|
|
$value = static::cast($type, $value, $name); |
|
771
|
|
|
} |
|
772
|
|
|
|
|
773
|
|
|
// apply any mutators |
|
774
|
|
|
if ($mutator = self::getMutator($name)) { |
|
775
|
|
|
$value = $this->$mutator($value); |
|
776
|
|
|
} |
|
777
|
|
|
|
|
778
|
|
|
// save the value on the model property |
|
779
|
|
|
if ($unsaved) { |
|
780
|
|
|
$this->_unsaved[$name] = $value; |
|
781
|
|
|
} else { |
|
782
|
|
|
$this->_values[$name] = $value; |
|
783
|
|
|
} |
|
784
|
|
|
|
|
785
|
|
|
// if changing property, remove relation model |
|
786
|
|
|
// DEPRECATED |
|
787
|
|
|
if (isset($this->_relationships[$name])) { |
|
|
|
|
|
|
788
|
|
|
unset($this->_relationships[$name]); |
|
789
|
|
|
} |
|
790
|
|
|
|
|
791
|
|
|
return $this; |
|
792
|
|
|
} |
|
793
|
|
|
|
|
794
|
|
|
/** |
|
795
|
|
|
* Sets a collection values on the model from an untrusted |
|
796
|
|
|
* input. Also known as mass assignment. |
|
797
|
|
|
* |
|
798
|
|
|
* @param array $values |
|
799
|
|
|
* |
|
800
|
|
|
* @throws MassAssignmentException when assigning a value that is protected or not whitelisted |
|
801
|
|
|
* |
|
802
|
|
|
* @return self |
|
803
|
|
|
*/ |
|
804
|
|
|
public function setValues($values) |
|
805
|
|
|
{ |
|
806
|
|
|
// check if the model has a mass assignment whitelist |
|
807
|
|
|
$permitted = (property_exists($this, 'permitted')) ? static::$permitted : false; |
|
808
|
|
|
|
|
809
|
|
|
// if no whitelist, then check for a blacklist |
|
810
|
|
|
$protected = (!is_array($permitted) && property_exists($this, 'protected')) ? static::$protected : false; |
|
811
|
|
|
|
|
812
|
|
|
foreach ($values as $k => $value) { |
|
813
|
|
|
// check for mass assignment violations |
|
814
|
|
|
if (($permitted && !in_array($k, $permitted)) || |
|
815
|
|
|
($protected && in_array($k, $protected))) { |
|
816
|
|
|
throw new MassAssignmentException("Mass assignment of $k on ".static::modelName().' is not allowed'); |
|
817
|
|
|
} |
|
818
|
|
|
|
|
819
|
|
|
$this->setValue($k, $value); |
|
820
|
|
|
} |
|
821
|
|
|
|
|
822
|
|
|
return $this; |
|
823
|
|
|
} |
|
824
|
|
|
|
|
825
|
|
|
/** |
|
826
|
|
|
* Ignores unsaved values when fetching the next value. |
|
827
|
|
|
* |
|
828
|
|
|
* @return self |
|
829
|
|
|
*/ |
|
830
|
|
|
public function ignoreUnsaved() |
|
831
|
|
|
{ |
|
832
|
|
|
$this->_ignoreUnsaved = true; |
|
833
|
|
|
|
|
834
|
|
|
return $this; |
|
835
|
|
|
} |
|
836
|
|
|
|
|
837
|
|
|
/** |
|
838
|
|
|
* Gets a list of property values from the model. |
|
839
|
|
|
* |
|
840
|
|
|
* @param array $properties list of property values to fetch |
|
841
|
|
|
* |
|
842
|
|
|
* @return array |
|
843
|
|
|
*/ |
|
844
|
|
|
public function getValues(array $properties) |
|
845
|
|
|
{ |
|
846
|
|
|
$result = []; |
|
847
|
|
|
foreach ($properties as $k) { |
|
848
|
|
|
$result[$k] = $this->getValue($k); |
|
849
|
|
|
} |
|
850
|
|
|
|
|
851
|
|
|
$this->_ignoreUnsaved = false; |
|
852
|
|
|
|
|
853
|
|
|
return $result; |
|
854
|
|
|
} |
|
855
|
|
|
|
|
856
|
|
|
/** |
|
857
|
|
|
* @deprecated |
|
858
|
|
|
*/ |
|
859
|
|
|
public function get(array $properties) |
|
860
|
|
|
{ |
|
861
|
|
|
return $this->getValues($properties); |
|
862
|
|
|
} |
|
863
|
|
|
|
|
864
|
|
|
/** |
|
865
|
|
|
* Gets a property value from the model. |
|
866
|
|
|
* |
|
867
|
|
|
* Values are looked up in this order: |
|
868
|
|
|
* 1. unsaved values |
|
869
|
|
|
* 2. local values |
|
870
|
|
|
* 3. relationships |
|
871
|
|
|
* |
|
872
|
|
|
* @throws InvalidArgumentException when a property was requested not present in the values |
|
873
|
|
|
* |
|
874
|
|
|
* @return mixed |
|
875
|
|
|
*/ |
|
876
|
|
|
private function getValue($property) |
|
877
|
|
|
{ |
|
878
|
|
|
$value = null; |
|
879
|
|
|
$accessor = self::getAccessor($property); |
|
880
|
|
|
|
|
881
|
|
|
// first check for unsaved values |
|
882
|
|
|
if (!$this->_ignoreUnsaved && array_key_exists($property, $this->_unsaved)) { |
|
883
|
|
|
$value = $this->_unsaved[$property]; |
|
884
|
|
|
|
|
885
|
|
|
// then check the normal value store |
|
886
|
|
|
} elseif (array_key_exists($property, $this->_values)) { |
|
887
|
|
|
$value = $this->_values[$property]; |
|
888
|
|
|
|
|
889
|
|
|
// get relationship values |
|
890
|
|
|
} elseif (static::isRelationship($property)) { |
|
891
|
|
|
$value = $this->loadRelationship($property); |
|
892
|
|
|
|
|
893
|
|
|
// throw an exception for non-properties |
|
894
|
|
|
// that do not have an accessor |
|
895
|
|
|
} elseif ($accessor === false && !in_array($property, static::$ids)) { |
|
896
|
|
|
throw new InvalidArgumentException(static::modelName().' does not have a `'.$property.'` property.'); |
|
897
|
|
|
} |
|
898
|
|
|
|
|
899
|
|
|
// call any accessors |
|
900
|
|
|
if ($accessor !== false) { |
|
901
|
|
|
return $this->$accessor($value); |
|
902
|
|
|
} |
|
903
|
|
|
|
|
904
|
|
|
return $value; |
|
905
|
|
|
} |
|
906
|
|
|
|
|
907
|
|
|
/** |
|
908
|
|
|
* Converts the model to an array. |
|
909
|
|
|
* |
|
910
|
|
|
* @return array model array |
|
911
|
|
|
*/ |
|
912
|
|
|
public function toArray() |
|
913
|
|
|
{ |
|
914
|
|
|
// build the list of properties to retrieve |
|
915
|
|
|
$properties = $this->getProperties(); |
|
916
|
|
|
|
|
917
|
|
|
// remove any hidden properties |
|
918
|
|
|
if (property_exists($this, 'hidden')) { |
|
919
|
|
|
$properties = array_diff($properties, static::$hidden); |
|
920
|
|
|
} |
|
921
|
|
|
|
|
922
|
|
|
// include any appended properties |
|
923
|
|
|
if (property_exists($this, 'appended')) { |
|
924
|
|
|
$properties = array_unique(array_merge($properties, static::$appended)); |
|
925
|
|
|
} |
|
926
|
|
|
|
|
927
|
|
|
// get the values for the properties |
|
928
|
|
|
$result = $this->getValues($properties); |
|
929
|
|
|
|
|
930
|
|
|
foreach ($result as $k => &$value) { |
|
931
|
|
|
// convert any models to arrays |
|
932
|
|
|
if ($value instanceof self) { |
|
933
|
|
|
$value = $value->toArray(); |
|
934
|
|
|
// convert any Carbon objects to date strings |
|
935
|
|
|
} elseif ($value instanceof Carbon) { |
|
936
|
|
|
$format = self::getDateFormat($k); |
|
937
|
|
|
$value = $value->format($format); |
|
938
|
|
|
} |
|
939
|
|
|
} |
|
940
|
|
|
|
|
941
|
|
|
return $result; |
|
942
|
|
|
} |
|
943
|
|
|
|
|
944
|
|
|
///////////////////////////// |
|
945
|
|
|
// Persistence |
|
946
|
|
|
///////////////////////////// |
|
947
|
|
|
|
|
948
|
|
|
/** |
|
949
|
|
|
* Saves the model. |
|
950
|
|
|
* |
|
951
|
|
|
* @return bool |
|
952
|
|
|
*/ |
|
953
|
|
|
public function save() |
|
954
|
|
|
{ |
|
955
|
|
|
if (!$this->_persisted) { |
|
956
|
|
|
return $this->create(); |
|
957
|
|
|
} |
|
958
|
|
|
|
|
959
|
|
|
return $this->set(); |
|
960
|
|
|
} |
|
961
|
|
|
|
|
962
|
|
|
/** |
|
963
|
|
|
* Creates a new model. |
|
964
|
|
|
* |
|
965
|
|
|
* @param array $data optional key-value properties to set |
|
966
|
|
|
* |
|
967
|
|
|
* @return bool |
|
968
|
|
|
* |
|
969
|
|
|
* @throws BadMethodCallException when called on an existing model |
|
970
|
|
|
*/ |
|
971
|
|
|
public function create(array $data = []) |
|
972
|
|
|
{ |
|
973
|
|
|
if ($this->_persisted) { |
|
974
|
|
|
throw new BadMethodCallException('Cannot call create() on an existing model'); |
|
975
|
|
|
} |
|
976
|
|
|
|
|
977
|
|
|
// mass assign values passed into create() |
|
978
|
|
|
$this->setValues($data); |
|
979
|
|
|
|
|
980
|
|
|
// add in any preset values |
|
981
|
|
|
$this->_unsaved = array_replace($this->_values, $this->_unsaved); |
|
982
|
|
|
|
|
983
|
|
|
// dispatch the model.creating event |
|
984
|
|
|
if (!$this->dispatch(ModelEvent::CREATING)) { |
|
985
|
|
|
return false; |
|
986
|
|
|
} |
|
987
|
|
|
|
|
988
|
|
|
// validate the model |
|
989
|
|
|
if (!$this->valid()) { |
|
990
|
|
|
return false; |
|
991
|
|
|
} |
|
992
|
|
|
|
|
993
|
|
|
// persist the model in the data layer |
|
994
|
|
|
if (!self::getAdapter()->createModel($this, $this->_unsaved)) { |
|
995
|
|
|
return false; |
|
996
|
|
|
} |
|
997
|
|
|
|
|
998
|
|
|
// update the model with the persisted values and new ID(s) |
|
999
|
|
|
$newValues = array_replace( |
|
1000
|
|
|
$this->_unsaved, |
|
1001
|
|
|
$this->getNewIds()); |
|
1002
|
|
|
$this->refreshWith($newValues); |
|
1003
|
|
|
|
|
1004
|
|
|
// dispatch the model.created event |
|
1005
|
|
|
return $this->dispatch(ModelEvent::CREATED); |
|
1006
|
|
|
} |
|
1007
|
|
|
|
|
1008
|
|
|
/** |
|
1009
|
|
|
* Gets the IDs for a newly created model. |
|
1010
|
|
|
* |
|
1011
|
|
|
* @return string |
|
1012
|
|
|
*/ |
|
1013
|
|
|
protected function getNewIds() |
|
1014
|
|
|
{ |
|
1015
|
|
|
$ids = []; |
|
1016
|
|
|
foreach (static::$ids as $k) { |
|
1017
|
|
|
// check if the ID property was already given, |
|
1018
|
|
|
if (isset($this->_unsaved[$k])) { |
|
1019
|
|
|
$ids[$k] = $this->_unsaved[$k]; |
|
1020
|
|
|
// otherwise, get it from the data layer (i.e. auto-incrementing IDs) |
|
1021
|
|
|
} else { |
|
1022
|
|
|
$ids[$k] = self::getAdapter()->getCreatedID($this, $k); |
|
1023
|
|
|
} |
|
1024
|
|
|
} |
|
1025
|
|
|
|
|
1026
|
|
|
return $ids; |
|
1027
|
|
|
} |
|
1028
|
|
|
|
|
1029
|
|
|
/** |
|
1030
|
|
|
* Updates the model. |
|
1031
|
|
|
* |
|
1032
|
|
|
* @param array $data optional key-value properties to set |
|
1033
|
|
|
* |
|
1034
|
|
|
* @return bool |
|
1035
|
|
|
* |
|
1036
|
|
|
* @throws BadMethodCallException when not called on an existing model |
|
1037
|
|
|
*/ |
|
1038
|
|
|
public function set(array $data = []) |
|
1039
|
|
|
{ |
|
1040
|
|
|
if (!$this->_persisted) { |
|
1041
|
|
|
throw new BadMethodCallException('Can only call set() on an existing model'); |
|
1042
|
|
|
} |
|
1043
|
|
|
|
|
1044
|
|
|
// mass assign values passed into set() |
|
1045
|
|
|
$this->setValues($data); |
|
1046
|
|
|
|
|
1047
|
|
|
// not updating anything? |
|
1048
|
|
|
if (count($this->_unsaved) === 0) { |
|
1049
|
|
|
return true; |
|
1050
|
|
|
} |
|
1051
|
|
|
|
|
1052
|
|
|
// dispatch the model.updating event |
|
1053
|
|
|
if (!$this->dispatch(ModelEvent::UPDATING)) { |
|
1054
|
|
|
return false; |
|
1055
|
|
|
} |
|
1056
|
|
|
|
|
1057
|
|
|
// DEPRECATED |
|
1058
|
|
|
if (method_exists($this, 'preSetHook') && !$this->preSetHook($this->_unsaved)) { |
|
|
|
|
|
|
1059
|
|
|
return false; |
|
1060
|
|
|
} |
|
1061
|
|
|
|
|
1062
|
|
|
// validate the model |
|
1063
|
|
|
if (!$this->valid()) { |
|
1064
|
|
|
return false; |
|
1065
|
|
|
} |
|
1066
|
|
|
|
|
1067
|
|
|
// persist the model in the data layer |
|
1068
|
|
|
if (!self::getAdapter()->updateModel($this, $this->_unsaved)) { |
|
1069
|
|
|
return false; |
|
1070
|
|
|
} |
|
1071
|
|
|
|
|
1072
|
|
|
// update the model with the persisted values |
|
1073
|
|
|
$this->refreshWith($this->_unsaved); |
|
1074
|
|
|
|
|
1075
|
|
|
// dispatch the model.updated event |
|
1076
|
|
|
return $this->dispatch(ModelEvent::UPDATED); |
|
1077
|
|
|
} |
|
1078
|
|
|
|
|
1079
|
|
|
/** |
|
1080
|
|
|
* Delete the model. |
|
1081
|
|
|
* |
|
1082
|
|
|
* @return bool success |
|
1083
|
|
|
*/ |
|
1084
|
|
|
public function delete() |
|
1085
|
|
|
{ |
|
1086
|
|
|
if (!$this->_persisted) { |
|
1087
|
|
|
throw new BadMethodCallException('Can only call delete() on an existing model'); |
|
1088
|
|
|
} |
|
1089
|
|
|
|
|
1090
|
|
|
// dispatch the model.deleting event |
|
1091
|
|
|
if (!$this->dispatch(ModelEvent::DELETING)) { |
|
1092
|
|
|
return false; |
|
1093
|
|
|
} |
|
1094
|
|
|
|
|
1095
|
|
|
// delete the model in the data layer |
|
1096
|
|
|
if (!self::getAdapter()->deleteModel($this)) { |
|
1097
|
|
|
return false; |
|
1098
|
|
|
} |
|
1099
|
|
|
|
|
1100
|
|
|
// dispatch the model.deleted event |
|
1101
|
|
|
if (!$this->dispatch(ModelEvent::DELETED)) { |
|
1102
|
|
|
return false; |
|
1103
|
|
|
} |
|
1104
|
|
|
|
|
1105
|
|
|
$this->_persisted = false; |
|
1106
|
|
|
|
|
1107
|
|
|
return true; |
|
1108
|
|
|
} |
|
1109
|
|
|
|
|
1110
|
|
|
/** |
|
1111
|
|
|
* Tells if the model has been persisted. |
|
1112
|
|
|
* |
|
1113
|
|
|
* @return bool |
|
1114
|
|
|
*/ |
|
1115
|
|
|
public function persisted() |
|
1116
|
|
|
{ |
|
1117
|
|
|
return $this->_persisted; |
|
1118
|
|
|
} |
|
1119
|
|
|
|
|
1120
|
|
|
/** |
|
1121
|
|
|
* Loads the model from the data layer. |
|
1122
|
|
|
* |
|
1123
|
|
|
* @return self |
|
1124
|
|
|
* |
|
1125
|
|
|
* @throws NotFoundException |
|
1126
|
|
|
*/ |
|
1127
|
|
|
public function refresh() |
|
1128
|
|
|
{ |
|
1129
|
|
|
if (!$this->_persisted) { |
|
1130
|
|
|
throw new NotFoundException('Cannot call refresh() before '.static::modelName().' has been persisted'); |
|
1131
|
|
|
} |
|
1132
|
|
|
|
|
1133
|
|
|
$query = static::query(); |
|
1134
|
|
|
$query->where($this->ids()); |
|
1135
|
|
|
|
|
1136
|
|
|
$values = self::getAdapter()->queryModels($query); |
|
1137
|
|
|
|
|
1138
|
|
|
if (count($values) === 0) { |
|
1139
|
|
|
return $this; |
|
1140
|
|
|
} |
|
1141
|
|
|
|
|
1142
|
|
|
// clear any relations |
|
1143
|
|
|
// DEPRECATED |
|
1144
|
|
|
$this->_relationships = []; |
|
|
|
|
|
|
1145
|
|
|
|
|
1146
|
|
|
return $this->refreshWith($values[0]); |
|
1147
|
|
|
} |
|
1148
|
|
|
|
|
1149
|
|
|
/** |
|
1150
|
|
|
* Loads values into the model retrieved from the data layer. |
|
1151
|
|
|
* |
|
1152
|
|
|
* @param array $values values |
|
1153
|
|
|
* |
|
1154
|
|
|
* @return self |
|
1155
|
|
|
*/ |
|
1156
|
|
|
public function refreshWith(array $values) |
|
1157
|
|
|
{ |
|
1158
|
|
|
// cast the values |
|
1159
|
|
|
if (property_exists($this, 'casts')) { |
|
1160
|
|
|
foreach ($values as $k => &$value) { |
|
1161
|
|
|
if ($type = static::getPropertyType($k)) { |
|
1162
|
|
|
$value = static::cast($type, $value, $k); |
|
1163
|
|
|
} |
|
1164
|
|
|
} |
|
1165
|
|
|
} |
|
1166
|
|
|
|
|
1167
|
|
|
$this->_persisted = true; |
|
1168
|
|
|
$this->_values = $values; |
|
1169
|
|
|
$this->_unsaved = []; |
|
1170
|
|
|
|
|
1171
|
|
|
return $this; |
|
1172
|
|
|
} |
|
1173
|
|
|
|
|
1174
|
|
|
///////////////////////////// |
|
1175
|
|
|
// Queries |
|
1176
|
|
|
///////////////////////////// |
|
1177
|
|
|
|
|
1178
|
|
|
/** |
|
1179
|
|
|
* Generates a new query instance. |
|
1180
|
|
|
* |
|
1181
|
|
|
* @return Query |
|
1182
|
|
|
*/ |
|
1183
|
|
|
public static function query() |
|
1184
|
|
|
{ |
|
1185
|
|
|
// Create a new model instance for the query to ensure |
|
1186
|
|
|
// that the model's initialize() method gets called. |
|
1187
|
|
|
// Otherwise, the property definitions will be incomplete. |
|
1188
|
|
|
$model = new static(); |
|
1189
|
|
|
|
|
1190
|
|
|
return new Query($model); |
|
1191
|
|
|
} |
|
1192
|
|
|
|
|
1193
|
|
|
/** |
|
1194
|
|
|
* Finds a single instance of a model given it's ID. |
|
1195
|
|
|
* |
|
1196
|
|
|
* @param mixed $id |
|
1197
|
|
|
* |
|
1198
|
|
|
* @return Model|null |
|
1199
|
|
|
*/ |
|
1200
|
|
|
public static function find($id) |
|
1201
|
|
|
{ |
|
1202
|
|
|
$model = static::buildFromId($id); |
|
1203
|
|
|
|
|
1204
|
|
|
return static::query()->where($model->ids())->first(); |
|
1205
|
|
|
} |
|
1206
|
|
|
|
|
1207
|
|
|
/** |
|
1208
|
|
|
* Finds a single instance of a model given it's ID or throws an exception. |
|
1209
|
|
|
* |
|
1210
|
|
|
* @param mixed $id |
|
1211
|
|
|
* |
|
1212
|
|
|
* @return Model|false |
|
1213
|
|
|
* |
|
1214
|
|
|
* @throws NotFoundException when a model could not be found |
|
1215
|
|
|
*/ |
|
1216
|
|
|
public static function findOrFail($id) |
|
1217
|
|
|
{ |
|
1218
|
|
|
$model = static::find($id); |
|
1219
|
|
|
if (!$model) { |
|
1220
|
|
|
throw new NotFoundException('Could not find the requested '.static::modelName()); |
|
1221
|
|
|
} |
|
1222
|
|
|
|
|
1223
|
|
|
return $model; |
|
1224
|
|
|
} |
|
1225
|
|
|
|
|
1226
|
|
|
/** |
|
1227
|
|
|
* Gets the toal number of records matching an optional criteria. |
|
1228
|
|
|
* |
|
1229
|
|
|
* @param array $where criteria |
|
1230
|
|
|
* |
|
1231
|
|
|
* @return int total |
|
1232
|
|
|
*/ |
|
1233
|
|
|
public static function totalRecords(array $where = []) |
|
1234
|
|
|
{ |
|
1235
|
|
|
$query = static::query(); |
|
1236
|
|
|
$query->where($where); |
|
1237
|
|
|
|
|
1238
|
|
|
return self::getAdapter()->totalRecords($query); |
|
1239
|
|
|
} |
|
1240
|
|
|
|
|
1241
|
|
|
/** |
|
1242
|
|
|
* @deprecated |
|
1243
|
|
|
* Checks if the model exists in the database |
|
1244
|
|
|
* |
|
1245
|
|
|
* @return bool |
|
1246
|
|
|
*/ |
|
1247
|
|
|
public function exists() |
|
1248
|
|
|
{ |
|
1249
|
|
|
return static::totalRecords($this->ids()) == 1; |
|
1250
|
|
|
} |
|
1251
|
|
|
|
|
1252
|
|
|
///////////////////////////// |
|
1253
|
|
|
// Relationships |
|
1254
|
|
|
///////////////////////////// |
|
1255
|
|
|
|
|
1256
|
|
|
/** |
|
1257
|
|
|
* Creates the parent side of a One-To-One relationship. |
|
1258
|
|
|
* |
|
1259
|
|
|
* @param string $model foreign model class |
|
1260
|
|
|
* @param string $foreignKey identifying key on foreign model |
|
1261
|
|
|
* @param string $localKey identifying key on local model |
|
1262
|
|
|
* |
|
1263
|
|
|
* @return \Pulsar\Relation\Relation |
|
1264
|
|
|
*/ |
|
1265
|
|
|
public function hasOne($model, $foreignKey = '', $localKey = '') |
|
1266
|
|
|
{ |
|
1267
|
|
|
return new HasOne($this, $localKey, $model, $foreignKey); |
|
1268
|
|
|
} |
|
1269
|
|
|
|
|
1270
|
|
|
/** |
|
1271
|
|
|
* Creates the child side of a One-To-One or One-To-Many relationship. |
|
1272
|
|
|
* |
|
1273
|
|
|
* @param string $model foreign model class |
|
1274
|
|
|
* @param string $foreignKey identifying key on foreign model |
|
1275
|
|
|
* @param string $localKey identifying key on local model |
|
1276
|
|
|
* |
|
1277
|
|
|
* @return \Pulsar\Relation\Relation |
|
1278
|
|
|
*/ |
|
1279
|
|
|
public function belongsTo($model, $foreignKey = '', $localKey = '') |
|
1280
|
|
|
{ |
|
1281
|
|
|
return new BelongsTo($this, $localKey, $model, $foreignKey); |
|
1282
|
|
|
} |
|
1283
|
|
|
|
|
1284
|
|
|
/** |
|
1285
|
|
|
* Creates the parent side of a Many-To-One or Many-To-Many relationship. |
|
1286
|
|
|
* |
|
1287
|
|
|
* @param string $model foreign model class |
|
1288
|
|
|
* @param string $foreignKey identifying key on foreign model |
|
1289
|
|
|
* @param string $localKey identifying key on local model |
|
1290
|
|
|
* |
|
1291
|
|
|
* @return \Pulsar\Relation\Relation |
|
1292
|
|
|
*/ |
|
1293
|
|
|
public function hasMany($model, $foreignKey = '', $localKey = '') |
|
1294
|
|
|
{ |
|
1295
|
|
|
return new HasMany($this, $localKey, $model, $foreignKey); |
|
1296
|
|
|
} |
|
1297
|
|
|
|
|
1298
|
|
|
/** |
|
1299
|
|
|
* Creates the child side of a Many-To-Many relationship. |
|
1300
|
|
|
* |
|
1301
|
|
|
* @param string $model foreign model class |
|
1302
|
|
|
* @param string $tablename pivot table name |
|
1303
|
|
|
* @param string $foreignKey identifying key on foreign model |
|
1304
|
|
|
* @param string $localKey identifying key on local model |
|
1305
|
|
|
* |
|
1306
|
|
|
* @return \Pulsar\Relation\Relation |
|
1307
|
|
|
*/ |
|
1308
|
|
|
public function belongsToMany($model, $tablename = '', $foreignKey = '', $localKey = '') |
|
1309
|
|
|
{ |
|
1310
|
|
|
return new BelongsToMany($this, $localKey, $tablename, $model, $foreignKey); |
|
1311
|
|
|
} |
|
1312
|
|
|
|
|
1313
|
|
|
/** |
|
1314
|
|
|
* Loads a given relationship (if not already) and |
|
1315
|
|
|
* returns its results. |
|
1316
|
|
|
* |
|
1317
|
|
|
* @param string $name |
|
1318
|
|
|
* |
|
1319
|
|
|
* @return mixed |
|
1320
|
|
|
*/ |
|
1321
|
|
|
protected function loadRelationship($name) |
|
1322
|
|
|
{ |
|
1323
|
|
|
if (!isset($this->_values[$name])) { |
|
1324
|
|
|
$relationship = $this->$name(); |
|
1325
|
|
|
$this->_values[$name] = $relationship->getResults(); |
|
1326
|
|
|
} |
|
1327
|
|
|
|
|
1328
|
|
|
return $this->_values[$name]; |
|
1329
|
|
|
} |
|
1330
|
|
|
|
|
1331
|
|
|
/** |
|
1332
|
|
|
* @deprecated |
|
1333
|
|
|
* Gets a relationship model with a has one relationship |
|
1334
|
|
|
* |
|
1335
|
|
|
* @param string $k property |
|
1336
|
|
|
* |
|
1337
|
|
|
* @return \Pulsar\Model|null |
|
1338
|
|
|
*/ |
|
1339
|
|
|
public function relation($k) |
|
1340
|
|
|
{ |
|
1341
|
|
|
if (!isset(static::$relationshipsDeprecated[$k])) { |
|
1342
|
|
|
return; |
|
1343
|
|
|
} |
|
1344
|
|
|
|
|
1345
|
|
|
if (!isset($this->_relationships[$k])) { |
|
|
|
|
|
|
1346
|
|
|
$model = static::$relationshipsDeprecated[$k]; |
|
1347
|
|
|
$this->_relationships[$k] = $model::find($this->$k); |
|
|
|
|
|
|
1348
|
|
|
} |
|
1349
|
|
|
|
|
1350
|
|
|
return $this->_relationships[$k]; |
|
|
|
|
|
|
1351
|
|
|
} |
|
1352
|
|
|
|
|
1353
|
|
|
///////////////////////////// |
|
1354
|
|
|
// Events |
|
1355
|
|
|
///////////////////////////// |
|
1356
|
|
|
|
|
1357
|
|
|
/** |
|
1358
|
|
|
* Gets the event dispatcher. |
|
1359
|
|
|
* |
|
1360
|
|
|
* @return \Symfony\Component\EventDispatcher\EventDispatcher |
|
1361
|
|
|
*/ |
|
1362
|
|
|
public static function getDispatcher($ignoreCache = false) |
|
1363
|
|
|
{ |
|
1364
|
|
|
$class = get_called_class(); |
|
1365
|
|
|
if ($ignoreCache || !isset(self::$dispatchers[$class])) { |
|
1366
|
|
|
self::$dispatchers[$class] = new EventDispatcher(); |
|
1367
|
|
|
} |
|
1368
|
|
|
|
|
1369
|
|
|
return self::$dispatchers[$class]; |
|
1370
|
|
|
} |
|
1371
|
|
|
|
|
1372
|
|
|
/** |
|
1373
|
|
|
* Subscribes to a listener to an event. |
|
1374
|
|
|
* |
|
1375
|
|
|
* @param string $event event name |
|
1376
|
|
|
* @param callable $listener |
|
1377
|
|
|
* @param int $priority optional priority, higher #s get called first |
|
1378
|
|
|
*/ |
|
1379
|
|
|
public static function listen($event, callable $listener, $priority = 0) |
|
1380
|
|
|
{ |
|
1381
|
|
|
static::getDispatcher()->addListener($event, $listener, $priority); |
|
1382
|
|
|
} |
|
1383
|
|
|
|
|
1384
|
|
|
/** |
|
1385
|
|
|
* Adds a listener to the model.creating event. |
|
1386
|
|
|
* |
|
1387
|
|
|
* @param callable $listener |
|
1388
|
|
|
* @param int $priority |
|
1389
|
|
|
*/ |
|
1390
|
|
|
public static function creating(callable $listener, $priority = 0) |
|
1391
|
|
|
{ |
|
1392
|
|
|
static::listen(ModelEvent::CREATING, $listener, $priority); |
|
1393
|
|
|
} |
|
1394
|
|
|
|
|
1395
|
|
|
/** |
|
1396
|
|
|
* Adds a listener to the model.created event. |
|
1397
|
|
|
* |
|
1398
|
|
|
* @param callable $listener |
|
1399
|
|
|
* @param int $priority |
|
1400
|
|
|
*/ |
|
1401
|
|
|
public static function created(callable $listener, $priority = 0) |
|
1402
|
|
|
{ |
|
1403
|
|
|
static::listen(ModelEvent::CREATED, $listener, $priority); |
|
1404
|
|
|
} |
|
1405
|
|
|
|
|
1406
|
|
|
/** |
|
1407
|
|
|
* Adds a listener to the model.updating event. |
|
1408
|
|
|
* |
|
1409
|
|
|
* @param callable $listener |
|
1410
|
|
|
* @param int $priority |
|
1411
|
|
|
*/ |
|
1412
|
|
|
public static function updating(callable $listener, $priority = 0) |
|
1413
|
|
|
{ |
|
1414
|
|
|
static::listen(ModelEvent::UPDATING, $listener, $priority); |
|
1415
|
|
|
} |
|
1416
|
|
|
|
|
1417
|
|
|
/** |
|
1418
|
|
|
* Adds a listener to the model.updated event. |
|
1419
|
|
|
* |
|
1420
|
|
|
* @param callable $listener |
|
1421
|
|
|
* @param int $priority |
|
1422
|
|
|
*/ |
|
1423
|
|
|
public static function updated(callable $listener, $priority = 0) |
|
1424
|
|
|
{ |
|
1425
|
|
|
static::listen(ModelEvent::UPDATED, $listener, $priority); |
|
1426
|
|
|
} |
|
1427
|
|
|
|
|
1428
|
|
|
/** |
|
1429
|
|
|
* Adds a listener to the model.creating and model.updating events. |
|
1430
|
|
|
* |
|
1431
|
|
|
* @param callable $listener |
|
1432
|
|
|
* @param int $priority |
|
1433
|
|
|
*/ |
|
1434
|
|
|
public static function saving(callable $listener, $priority = 0) |
|
1435
|
|
|
{ |
|
1436
|
|
|
static::listen(ModelEvent::CREATING, $listener, $priority); |
|
1437
|
|
|
static::listen(ModelEvent::UPDATING, $listener, $priority); |
|
1438
|
|
|
} |
|
1439
|
|
|
|
|
1440
|
|
|
/** |
|
1441
|
|
|
* Adds a listener to the model.created and model.updated events. |
|
1442
|
|
|
* |
|
1443
|
|
|
* @param callable $listener |
|
1444
|
|
|
* @param int $priority |
|
1445
|
|
|
*/ |
|
1446
|
|
|
public static function saved(callable $listener, $priority = 0) |
|
1447
|
|
|
{ |
|
1448
|
|
|
static::listen(ModelEvent::CREATED, $listener, $priority); |
|
1449
|
|
|
static::listen(ModelEvent::UPDATED, $listener, $priority); |
|
1450
|
|
|
} |
|
1451
|
|
|
|
|
1452
|
|
|
/** |
|
1453
|
|
|
* Adds a listener to the model.deleting event. |
|
1454
|
|
|
* |
|
1455
|
|
|
* @param callable $listener |
|
1456
|
|
|
* @param int $priority |
|
1457
|
|
|
*/ |
|
1458
|
|
|
public static function deleting(callable $listener, $priority = 0) |
|
1459
|
|
|
{ |
|
1460
|
|
|
static::listen(ModelEvent::DELETING, $listener, $priority); |
|
1461
|
|
|
} |
|
1462
|
|
|
|
|
1463
|
|
|
/** |
|
1464
|
|
|
* Adds a listener to the model.deleted event. |
|
1465
|
|
|
* |
|
1466
|
|
|
* @param callable $listener |
|
1467
|
|
|
* @param int $priority |
|
1468
|
|
|
*/ |
|
1469
|
|
|
public static function deleted(callable $listener, $priority = 0) |
|
1470
|
|
|
{ |
|
1471
|
|
|
static::listen(ModelEvent::DELETED, $listener, $priority); |
|
1472
|
|
|
} |
|
1473
|
|
|
|
|
1474
|
|
|
/** |
|
1475
|
|
|
* Dispatches an event. |
|
1476
|
|
|
* |
|
1477
|
|
|
* @param string $eventName |
|
1478
|
|
|
* |
|
1479
|
|
|
* @return bool true when the event propagated fully without being stopped |
|
1480
|
|
|
*/ |
|
1481
|
|
|
protected function dispatch($eventName) |
|
1482
|
|
|
{ |
|
1483
|
|
|
$event = new ModelEvent($this); |
|
1484
|
|
|
|
|
1485
|
|
|
static::getDispatcher()->dispatch($eventName, $event); |
|
1486
|
|
|
|
|
1487
|
|
|
return !$event->isPropagationStopped(); |
|
1488
|
|
|
} |
|
1489
|
|
|
|
|
1490
|
|
|
///////////////////////////// |
|
1491
|
|
|
// Validation |
|
1492
|
|
|
///////////////////////////// |
|
1493
|
|
|
|
|
1494
|
|
|
/** |
|
1495
|
|
|
* Gets the error stack for this model instance. Used to |
|
1496
|
|
|
* keep track of validation errors. |
|
1497
|
|
|
* |
|
1498
|
|
|
* @return Errors |
|
1499
|
|
|
*/ |
|
1500
|
|
|
public function errors() |
|
1501
|
|
|
{ |
|
1502
|
|
|
if (!$this->_errors) { |
|
1503
|
|
|
$this->_errors = new Errors($this, self::$locale); |
|
1504
|
|
|
} |
|
1505
|
|
|
|
|
1506
|
|
|
return $this->_errors; |
|
1507
|
|
|
} |
|
1508
|
|
|
|
|
1509
|
|
|
/** |
|
1510
|
|
|
* Checks if the model is valid in its current state. |
|
1511
|
|
|
* |
|
1512
|
|
|
* @return bool |
|
1513
|
|
|
*/ |
|
1514
|
|
|
public function valid() |
|
1515
|
|
|
{ |
|
1516
|
|
|
// clear any previous errors |
|
1517
|
|
|
$this->errors()->clear(); |
|
1518
|
|
|
|
|
1519
|
|
|
// run the validator against the model values |
|
1520
|
|
|
$validator = $this->getValidator(); |
|
1521
|
|
|
$values = $this->_unsaved + $this->_values; |
|
1522
|
|
|
$validated = $validator->validate($values); |
|
1523
|
|
|
|
|
1524
|
|
|
// add back any modified unsaved values |
|
1525
|
|
|
foreach (array_keys($this->_unsaved) as $k) { |
|
1526
|
|
|
$this->_unsaved[$k] = $values[$k]; |
|
1527
|
|
|
} |
|
1528
|
|
|
|
|
1529
|
|
|
return $validated; |
|
1530
|
|
|
} |
|
1531
|
|
|
|
|
1532
|
|
|
/** |
|
1533
|
|
|
* Gets a new validator instance for this model. |
|
1534
|
|
|
* |
|
1535
|
|
|
* @return Validator |
|
1536
|
|
|
*/ |
|
1537
|
|
|
public function getValidator() |
|
1538
|
|
|
{ |
|
1539
|
|
|
return new Validator(static::$validations, $this->errors()); |
|
1540
|
|
|
} |
|
1541
|
|
|
} |
|
1542
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.