1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace As3\Modlr\Models; |
4
|
|
|
|
5
|
|
|
use As3\Modlr\Metadata\Interfaces\AttributeInterface; |
6
|
|
|
use As3\Modlr\Store\Store; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Represents a record from a persistence (database) layer. |
10
|
|
|
* Can either be a root record, or an embedded fragment of a root record. |
11
|
|
|
* |
12
|
|
|
* @author Jacob Bare <[email protected]> |
13
|
|
|
*/ |
14
|
|
|
abstract class AbstractModel |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* The model's attributes |
18
|
|
|
* |
19
|
|
|
* @var Attributes |
20
|
|
|
*/ |
21
|
|
|
protected $attributes; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* The Model's has-one embeds |
25
|
|
|
* |
26
|
|
|
* @var Embeds\HasOne |
27
|
|
|
*/ |
28
|
|
|
protected $hasOneEmbeds; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* The Model's has-many embeds |
32
|
|
|
* |
33
|
|
|
* @var Embeds\HasMany |
34
|
|
|
*/ |
35
|
|
|
protected $hasManyEmbeds; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* The metadata that defines this Model. |
39
|
|
|
* |
40
|
|
|
* @var AttributeInterface |
41
|
|
|
*/ |
42
|
|
|
protected $metadata; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* The model state. |
46
|
|
|
* |
47
|
|
|
* @var State |
48
|
|
|
*/ |
49
|
|
|
protected $state; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* The Model Store for handling lifecycle operations. |
53
|
|
|
* |
54
|
|
|
* @var Store |
55
|
|
|
*/ |
56
|
|
|
protected $store; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Constructor. |
60
|
|
|
* |
61
|
|
|
* @param AttributeInterface $metadata |
62
|
|
|
* @param Store $store |
63
|
|
|
* @param array|null $properties |
64
|
|
|
*/ |
65
|
|
|
public function __construct(AttributeInterface $metadata, Store $store, array $properties = null) |
66
|
|
|
{ |
67
|
|
|
$this->state = new State(); |
68
|
|
|
$this->metadata = $metadata; |
69
|
|
|
$this->store = $store; |
70
|
|
|
$this->initialize($properties); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Cloner. |
75
|
|
|
* Ensures sub objects are also cloned. |
76
|
|
|
* |
77
|
|
|
*/ |
78
|
|
|
public function __clone() |
79
|
|
|
{ |
80
|
|
|
$this->attributes = clone $this->attributes; |
81
|
|
|
$this->hasOneEmbeds = clone $this->hasOneEmbeds; |
82
|
|
|
$this->hasManyEmbeds = clone $this->hasManyEmbeds; |
83
|
|
|
$this->state = clone $this->state; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Applies an array of raw model properties to the model instance. |
88
|
|
|
* |
89
|
|
|
* @todo Confirm that we want this method. It's currently used for creating and updating via the API adapter. Also see initialize() |
90
|
|
|
* @param array $properties The properties to apply. |
91
|
|
|
* @return self |
92
|
|
|
*/ |
93
|
|
|
public function apply(array $properties) |
94
|
|
|
{ |
95
|
|
|
$properties = $this->applyDefaultAttrValues($properties); |
96
|
|
|
foreach ($properties as $key => $value) { |
97
|
|
|
if (true === $this->isAttribute($key)) { |
98
|
|
|
$this->set($key, $value); |
99
|
|
|
continue; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
if (true === $this->isEmbedHasOne($key)) { |
103
|
|
|
if (empty($value)) { |
104
|
|
|
$this->clear($key); |
105
|
|
|
continue; |
106
|
|
|
} |
107
|
|
|
$embed = $this->get($key) ?: $this->createEmbedFor($key, $value); |
|
|
|
|
108
|
|
|
if (!is_array($value)) { |
109
|
|
|
continue; |
110
|
|
|
} |
111
|
|
|
$embed->apply($value); |
112
|
|
|
$this->set($key, $embed); |
113
|
|
|
continue; |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
foreach ($this->getMetadata()->getEmbeds() as $key => $embeddedPropMeta) { |
118
|
|
|
if (true === $embeddedPropMeta->isOne() || !isset($properties[$key])) { |
119
|
|
|
continue; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
// @todo This will always mark the model as dirty, even if the applied embed values are the same as the original. |
123
|
|
|
$this->clear($key); |
124
|
|
|
$collection = $this->getStore()->createEmbedCollection($embeddedPropMeta, $properties[$key]); |
125
|
|
|
foreach ($collection as $value) { |
126
|
|
|
$this->pushEmbed($key, $value); |
|
|
|
|
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$this->doDirtyCheck(); |
131
|
|
|
return $this; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Clears a property value. |
136
|
|
|
* For an attribute, will set the value to null. |
137
|
|
|
* For collections, will clear the collection contents. |
138
|
|
|
* |
139
|
|
|
* @api |
140
|
|
|
* @param string $key The property key. |
141
|
|
|
* @return self |
142
|
|
|
*/ |
143
|
|
|
public function clear($key) |
144
|
|
|
{ |
145
|
|
|
if (true === $this->isAttribute($key)) { |
146
|
|
|
return $this->setAttribute($key, null); |
147
|
|
|
} |
148
|
|
|
if (true === $this->isEmbedHasOne($key)) { |
149
|
|
|
return $this->setEmbedHasOne($key, null); |
150
|
|
|
} |
151
|
|
|
if (true === $this->isEmbedHasMany($key)) { |
152
|
|
|
$collection = $this->hasManyEmbeds->get($key); |
153
|
|
|
$collection->clear(); |
154
|
|
|
$this->doDirtyCheck(); |
155
|
|
|
return $this; |
156
|
|
|
} |
157
|
|
|
return $this; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Creates a new Embed model instance for the provided property key. |
162
|
|
|
* |
163
|
|
|
* @param string $key |
164
|
|
|
* @return Embed |
165
|
|
|
* @throws \RuntimeException |
166
|
|
|
*/ |
167
|
|
|
public function createEmbedFor($key) |
168
|
|
|
{ |
169
|
|
|
if (false === $this->isEmbed($key)) { |
170
|
|
|
throw new \RuntimeException(sprintf('Unable to create an Embed instance for property key "%s" - the property is not an embed.', $key)); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
$embedMeta = $this->getMetadata()->getEmbed($key)->embedMeta; |
174
|
|
|
$embed = $this->getStore()->loadEmbed($embedMeta, []); |
175
|
|
|
$embed->getState()->setNew(); |
176
|
|
|
return $embed; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Gets a model property. |
181
|
|
|
* Returns null if the property does not exist on the model or is not set. |
182
|
|
|
* |
183
|
|
|
* @api |
184
|
|
|
* @param string $key The property field key. |
185
|
|
|
* @return Model|Model[]|Embed|Collections\EmbedCollection|null|mixed |
186
|
|
|
*/ |
187
|
|
|
public function get($key) |
188
|
|
|
{ |
189
|
|
|
if (true === $this->isAttribute($key)) { |
190
|
|
|
return $this->getAttribute($key); |
191
|
|
|
} |
192
|
|
|
if (true === $this->isEmbed($key)) { |
193
|
|
|
return $this->getEmbed($key); |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Gets the current change set of properties. |
199
|
|
|
* |
200
|
|
|
* @api |
201
|
|
|
* @return array |
202
|
|
|
*/ |
203
|
|
|
public function getChangeSet() |
204
|
|
|
{ |
205
|
|
|
return [ |
206
|
|
|
'attributes' => $this->filterNotSavedProperties($this->attributes->calculateChangeSet()), |
207
|
|
|
'embedOne' => $this->hasOneEmbeds->calculateChangeSet(), |
208
|
|
|
'embedMany' => $this->hasManyEmbeds->calculateChangeSet(), |
209
|
|
|
]; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Gets the composite key of the model. |
214
|
|
|
* |
215
|
|
|
* @api |
216
|
|
|
* @return string |
217
|
|
|
*/ |
218
|
|
|
abstract public function getCompositeKey(); |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Gets the metadata for this model. |
222
|
|
|
* |
223
|
|
|
* @api |
224
|
|
|
* @return AttributeInterface |
225
|
|
|
*/ |
226
|
|
|
public function getMetadata() |
227
|
|
|
{ |
228
|
|
|
return $this->metadata; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Gets the model state object. |
233
|
|
|
* |
234
|
|
|
* @todo Should this be public? State setting should likely be locked from the outside world. |
235
|
|
|
* @return State |
236
|
|
|
*/ |
237
|
|
|
public function getState() |
238
|
|
|
{ |
239
|
|
|
return $this->state; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Gets the model store. |
244
|
|
|
* |
245
|
|
|
* @api |
246
|
|
|
* @return Store |
247
|
|
|
*/ |
248
|
|
|
public function getStore() |
249
|
|
|
{ |
250
|
|
|
return $this->store; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Initializes the model and loads its attributes and relationships. |
255
|
|
|
* |
256
|
|
|
* @todo Made public so collections can initialize models. Not sure if we want this?? |
257
|
|
|
* @param array|null $properties The db properties to apply. |
258
|
|
|
* @return self |
259
|
|
|
*/ |
260
|
|
|
public function initialize(array $properties = null) |
261
|
|
|
{ |
262
|
|
|
$attributes = []; |
263
|
|
|
$embedOne = []; |
264
|
|
|
$embedMany = []; |
265
|
|
|
|
266
|
|
|
if (null !== $properties) { |
267
|
|
|
$attributes = $this->applyDefaultAttrValues($attributes); |
268
|
|
|
foreach ($properties as $key => $value) { |
269
|
|
|
if (true === $this->isAttribute($key)) { |
270
|
|
|
// Load attribute. |
271
|
|
|
$attributes[$key] = $this->convertAttributeValue($key, $value); |
272
|
|
|
} else if (true === $this->isEmbedHasOne($key) && is_array($value)) { |
273
|
|
|
// Load embed one. |
274
|
|
|
$embedOne[$key] = $this->getStore()->loadEmbed($this->getMetadata()->getEmbed($key)->embedMeta, $value); |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
foreach ($this->getMetadata()->getEmbeds() as $key => $embeddedPropMeta) { |
280
|
|
|
// Always load embedded collections, regardless if data is set. |
281
|
|
|
if (true === $embeddedPropMeta->isOne()) { |
282
|
|
|
continue; |
283
|
|
|
} |
284
|
|
|
$embeds = !isset($properties[$key]) ? [] : $properties[$key]; |
285
|
|
|
$embedMany[$key] = $this->getStore()->createEmbedCollection($embeddedPropMeta, $embeds); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
$this->attributes = (null === $this->attributes) ? new Attributes($attributes) : $this->attributes->replace($attributes); |
289
|
|
|
$this->hasOneEmbeds = (null === $this->hasOneEmbeds) ? new Embeds\HasOne($embedOne) : $this->hasOneEmbeds->replace($embedOne); |
290
|
|
|
$this->hasManyEmbeds = (null === $this->hasManyEmbeds) ? new Embeds\HasMany($embedMany) : $this->hasManyEmbeds->replace($embedMany); |
291
|
|
|
|
292
|
|
|
if (true === $this->getState()->is('new')) { |
293
|
|
|
// Ensure default values are applied to new models. |
294
|
|
|
$this->apply([]); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
$this->doDirtyCheck(); |
298
|
|
|
return $this; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Determines if a property key is an attribute. |
303
|
|
|
* |
304
|
|
|
* @api |
305
|
|
|
* @param string $key The property key. |
306
|
|
|
* @return bool |
307
|
|
|
*/ |
308
|
|
|
public function isAttribute($key) |
309
|
|
|
{ |
310
|
|
|
return $this->getMetadata()->hasAttribute($key); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Determines if the model is currently dirty. |
315
|
|
|
* |
316
|
|
|
* @api |
317
|
|
|
* @return bool |
318
|
|
|
*/ |
319
|
|
|
public function isDirty() |
320
|
|
|
{ |
321
|
|
|
return true === $this->attributes->areDirty() |
322
|
|
|
|| true === $this->hasOneEmbeds->areDirty() |
323
|
|
|
|| true === $this->hasManyEmbeds->areDirty() |
324
|
|
|
; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Determines if a property key is an embedded property. |
329
|
|
|
* |
330
|
|
|
* @api |
331
|
|
|
* @param string $key The property key. |
332
|
|
|
* @return bool |
333
|
|
|
*/ |
334
|
|
|
public function isEmbed($key) |
335
|
|
|
{ |
336
|
|
|
return $this->getMetadata()->hasEmbed($key); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Determines if a property key is a has-many embed. |
341
|
|
|
* |
342
|
|
|
* @api |
343
|
|
|
* @param string $key The property key. |
344
|
|
|
* @return bool |
345
|
|
|
*/ |
346
|
|
|
public function isEmbedHasMany($key) |
347
|
|
|
{ |
348
|
|
|
if (false === $this->isEmbed($key)) { |
349
|
|
|
return false; |
350
|
|
|
} |
351
|
|
|
return $this->getMetadata()->getEmbed($key)->isMany(); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Determines if a property key is a has-one embed. |
356
|
|
|
* |
357
|
|
|
* @api |
358
|
|
|
* @param string $key The property key. |
359
|
|
|
* @return bool |
360
|
|
|
*/ |
361
|
|
|
public function isEmbedHasOne($key) |
362
|
|
|
{ |
363
|
|
|
if (false === $this->isEmbed($key)) { |
364
|
|
|
return false; |
365
|
|
|
} |
366
|
|
|
return $this->getMetadata()->getEmbed($key)->isOne(); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Pushes an Embed into a has-many embed collection. |
371
|
|
|
* This method must be used for has-many embeds. Direct set is not supported. |
372
|
|
|
* To completely replace call clear() first and then pushEmbed() the new Embeds. |
373
|
|
|
* |
374
|
|
|
* @api |
375
|
|
|
* @param string $key |
376
|
|
|
* @param Embed $embed |
377
|
|
|
* @return self |
378
|
|
|
*/ |
379
|
|
|
public function pushEmbed($key, Embed $embed) |
380
|
|
|
{ |
381
|
|
|
if (true === $this->isEmbedHasOne($key)) { |
382
|
|
|
return $this->setEmbedHasOne($key, $embed); |
383
|
|
|
} |
384
|
|
|
if (false === $this->isEmbedHasMany($key)) { |
385
|
|
|
return $this; |
386
|
|
|
} |
387
|
|
|
$this->touch(); |
388
|
|
|
$collection = $this->hasManyEmbeds->get($key); |
389
|
|
|
$collection->push($embed); |
390
|
|
|
$this->doDirtyCheck(); |
391
|
|
|
return $this; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Removes a specific Embed from a has-many embed collection. |
396
|
|
|
* |
397
|
|
|
* @api |
398
|
|
|
* @param string $key The has-many embed key. |
399
|
|
|
* @param Embed $embed The embed to remove from the collection. |
400
|
|
|
* @return self |
401
|
|
|
*/ |
402
|
|
View Code Duplication |
public function removeEmbed($key, Embed $embed) |
|
|
|
|
403
|
|
|
{ |
404
|
|
|
if (false === $this->isEmbedHasMany($key)) { |
405
|
|
|
return $this; |
406
|
|
|
} |
407
|
|
|
$this->touch(); |
408
|
|
|
$collection = $this->hasManyEmbeds->get($key); |
409
|
|
|
$collection->remove($embed); |
410
|
|
|
$this->doDirtyCheck(); |
411
|
|
|
return $this; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Rolls back a model to its original values. |
416
|
|
|
* |
417
|
|
|
* @api |
418
|
|
|
* @return self |
419
|
|
|
*/ |
420
|
|
|
public function rollback() |
421
|
|
|
{ |
422
|
|
|
$this->attributes->rollback(); |
423
|
|
|
$this->hasOneEmbeds->rollback(); |
424
|
|
|
$this->hasManyEmbeds->rollback(); |
425
|
|
|
$this->doDirtyCheck(); |
426
|
|
|
return $this; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Sets a model property. |
431
|
|
|
* |
432
|
|
|
* @api |
433
|
|
|
* @param string $key The property field key. |
434
|
|
|
* @param Model|Embed|null|mixed The value to set. |
435
|
|
|
* @return self. |
|
|
|
|
436
|
|
|
*/ |
437
|
|
|
public function set($key, $value) |
438
|
|
|
{ |
439
|
|
|
if (true === $this->isAttribute($key)) { |
440
|
|
|
return $this->setAttribute($key, $value); |
441
|
|
|
} |
442
|
|
|
if (true === $this->isEmbed($key)) { |
443
|
|
|
return $this->setEmbed($key, $value); |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Determines if the model uses a particlar mixin. |
449
|
|
|
* |
450
|
|
|
* @api |
451
|
|
|
* @param string $name |
452
|
|
|
* @return bool |
453
|
|
|
*/ |
454
|
|
|
public function usesMixin($name) |
455
|
|
|
{ |
456
|
|
|
return $this->getMetadata()->hasMixin($name); |
|
|
|
|
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Applies default attribute values from metadata, if set. |
461
|
|
|
* |
462
|
|
|
* @param array $attributes The attributes to apply the defaults to. |
463
|
|
|
* @return array |
464
|
|
|
*/ |
465
|
|
|
protected function applyDefaultAttrValues(array $attributes = []) |
466
|
|
|
{ |
467
|
|
|
// Set defaults for each attribute. |
468
|
|
|
foreach ($this->getMetadata()->getAttributes() as $key => $attrMeta) { |
469
|
|
|
if (!isset($attrMeta->defaultValue) || isset($attributes[$key])) { |
470
|
|
|
continue; |
471
|
|
|
} |
472
|
|
|
$attributes[$key] = $this->convertAttributeValue($key, $attrMeta->defaultValue); |
473
|
|
|
} |
474
|
|
|
return $attributes; |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
/** |
478
|
|
|
* Converts an attribute value to the appropriate data type. |
479
|
|
|
* |
480
|
|
|
* @param string $key |
481
|
|
|
* @param mixed $value |
482
|
|
|
* @return mixed |
483
|
|
|
*/ |
484
|
|
|
protected function convertAttributeValue($key, $value) |
485
|
|
|
{ |
486
|
|
|
return $this->store->convertAttributeValue($this->getDataType($key), $value); |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
/** |
490
|
|
|
* Does a dirty check and sets the state to this model. |
491
|
|
|
* |
492
|
|
|
* @return self |
493
|
|
|
*/ |
494
|
|
|
protected function doDirtyCheck() |
495
|
|
|
{ |
496
|
|
|
$this->getState()->setDirty($this->isDirty()); |
497
|
|
|
return $this; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* Removes properties marked as non-saved. |
502
|
|
|
* |
503
|
|
|
* @param array $properties |
504
|
|
|
* @return array |
505
|
|
|
*/ |
506
|
|
|
protected function filterNotSavedProperties(array $properties) |
507
|
|
|
{ |
508
|
|
View Code Duplication |
foreach ($this->getMetadata()->getAttributes() as $fieldKey => $propMeta) { |
|
|
|
|
509
|
|
|
if (true === $propMeta->shouldSave() || !isset($properties[$fieldKey])) { |
510
|
|
|
continue; |
511
|
|
|
} |
512
|
|
|
unset($properties[$fieldKey]); |
513
|
|
|
} |
514
|
|
|
return $properties; |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
/** |
518
|
|
|
* Gets an attribute value. |
519
|
|
|
* |
520
|
|
|
* @param string $key The attribute key (field) name. |
521
|
|
|
* @return mixed |
522
|
|
|
*/ |
523
|
|
|
protected function getAttribute($key) |
524
|
|
|
{ |
525
|
|
|
if (true === $this->isCalculatedAttribute($key)) { |
526
|
|
|
return $this->getCalculatedAttribute($key); |
527
|
|
|
} |
528
|
|
|
$this->touch(); |
529
|
|
|
return $this->attributes->get($key); |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
/** |
533
|
|
|
* Gets a calculated attribute value. |
534
|
|
|
* |
535
|
|
|
* @param string $key The attribute key (field) name. |
536
|
|
|
* @return mixed |
537
|
|
|
*/ |
538
|
|
|
protected function getCalculatedAttribute($key) |
539
|
|
|
{ |
540
|
|
|
$attrMeta = $this->getMetadata()->getAttribute($key); |
541
|
|
|
$class = $attrMeta->calculated['class']; |
542
|
|
|
$method = $attrMeta->calculated['method']; |
543
|
|
|
|
544
|
|
|
$value = $class::$method($this); |
545
|
|
|
return $this->convertAttributeValue($key, $value); |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
/** |
549
|
|
|
* Gets a data type from an attribute key. |
550
|
|
|
* |
551
|
|
|
* @param string $key The attribute key. |
552
|
|
|
* @return string |
553
|
|
|
*/ |
554
|
|
|
protected function getDataType($key) |
555
|
|
|
{ |
556
|
|
|
return $this->getMetadata()->getAttribute($key)->dataType; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Gets an embed value. |
561
|
|
|
* |
562
|
|
|
* @param string $key The embed key (field) name. |
563
|
|
|
* @return Embed|Collections\EmbedCollection|null |
564
|
|
|
*/ |
565
|
|
|
protected function getEmbed($key) |
566
|
|
|
{ |
567
|
|
|
if (true === $this->isEmbedHasOne($key)) { |
568
|
|
|
$this->touch(); |
569
|
|
|
return $this->hasOneEmbeds->get($key); |
570
|
|
|
} |
571
|
|
|
if (true === $this->isEmbedHasMany($key)) { |
572
|
|
|
$this->touch(); |
573
|
|
|
return $this->hasManyEmbeds->get($key); |
574
|
|
|
} |
575
|
|
|
return null; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
/** |
579
|
|
|
* Determines if an attribute key is calculated. |
580
|
|
|
* |
581
|
|
|
* @param string $key The attribute key. |
582
|
|
|
* @return bool |
583
|
|
|
*/ |
584
|
|
|
protected function isCalculatedAttribute($key) |
585
|
|
|
{ |
586
|
|
|
if (false === $this->isAttribute($key)) { |
587
|
|
|
return false; |
588
|
|
|
} |
589
|
|
|
return $this->getMetadata()->getAttribute($key)->isCalculated(); |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
/** |
593
|
|
|
* Sets an attribute value. |
594
|
|
|
* Will convert the value to the proper, internal PHP/Modlr data type. |
595
|
|
|
* Will do a dirty check immediately after setting. |
596
|
|
|
* |
597
|
|
|
* @param string $key The attribute key (field) name. |
598
|
|
|
* @param mixed $value The value to apply. |
599
|
|
|
* @return self |
600
|
|
|
*/ |
601
|
|
|
protected function setAttribute($key, $value) |
602
|
|
|
{ |
603
|
|
|
if (true === $this->isCalculatedAttribute($key)) { |
604
|
|
|
return $this; |
605
|
|
|
} |
606
|
|
|
$this->touch(); |
607
|
|
|
$value = $this->convertAttributeValue($key, $value); |
608
|
|
|
$this->attributes->set($key, $value); |
609
|
|
|
$this->doDirtyCheck(); |
610
|
|
|
return $this; |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* Sets an embed value. |
615
|
|
|
* |
616
|
|
|
* @param string $key |
617
|
|
|
* @param Embed|null $value |
618
|
|
|
* @return self |
619
|
|
|
*/ |
620
|
|
|
protected function setEmbed($key, $value) |
621
|
|
|
{ |
622
|
|
|
if (true === $this->isEmbedHasOne($key)) { |
623
|
|
|
return $this->setEmbedHasOne($key, $value); |
624
|
|
|
} |
625
|
|
|
if (true === $this->isEmbedHasMany($key)) { |
626
|
|
|
throw new \RuntimeException('You cannot set a hasMany embed directly. Please access using pushEmbed(), clear(), and/or remove()'); |
627
|
|
|
} |
628
|
|
|
return $this; |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
/** |
632
|
|
|
* Sets a has-one embed. |
633
|
|
|
* |
634
|
|
|
* @param string $key The embed key (field) name. |
635
|
|
|
* @param Embed|null $embed The embed to relate. |
636
|
|
|
* @return self |
637
|
|
|
*/ |
638
|
|
View Code Duplication |
protected function setEmbedHasOne($key, Embed $embed = null) |
|
|
|
|
639
|
|
|
{ |
640
|
|
|
if (null !== $embed) { |
641
|
|
|
$this->validateEmbedSet($key, $embed->getName()); |
642
|
|
|
} |
643
|
|
|
$this->touch(); |
644
|
|
|
$this->hasOneEmbeds->set($key, $embed); |
645
|
|
|
$this->doDirtyCheck(); |
646
|
|
|
return $this; |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
/** |
650
|
|
|
* Touches the model. |
651
|
|
|
* Must be handled the the extending class. |
652
|
|
|
* |
653
|
|
|
* @param bool $force Whether to force the load, even if the model is currently loaded. |
654
|
|
|
* @return self |
655
|
|
|
*/ |
656
|
|
|
protected function touch($force = false) |
657
|
|
|
{ |
658
|
|
|
return $this; |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* Validates that the model type (from a Model or Collection instance) can be set to the relationship field. |
663
|
|
|
* |
664
|
|
|
* @param string $embedKey The embed field key. |
665
|
|
|
* @param string $embedName The embed name that is being set. |
666
|
|
|
* @return self |
667
|
|
|
*/ |
668
|
|
|
protected function validateEmbedSet($embedKey, $embedName) |
669
|
|
|
{ |
670
|
|
|
$embededPropMeta = $this->getMetadata()->getEmbed($embedKey); |
671
|
|
|
$this->getStore()->validateEmbedSet($embededPropMeta->embedMeta, $embedName); |
672
|
|
|
return $this; |
673
|
|
|
} |
674
|
|
|
} |
675
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.