This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace As3\Modlr\Models; |
||
4 | |||
5 | use As3\Modlr\Models\Relationships; |
||
6 | use As3\Modlr\Store\Store; |
||
7 | use As3\Modlr\Metadata\EntityMetadata; |
||
8 | |||
9 | /** |
||
10 | * Represents a data record from a persistence (database) layer. |
||
11 | * |
||
12 | * @author Jacob Bare <[email protected]> |
||
13 | */ |
||
14 | class Model extends AbstractModel |
||
15 | { |
||
16 | /** |
||
17 | * Enables/disables collection auto-initialization on iteration. |
||
18 | * Will not load/fill the collection from the database if false. |
||
19 | * Is useful for large hasMany iterations where only id and type are required (ala serialization). |
||
20 | * |
||
21 | * @var bool |
||
22 | */ |
||
23 | protected $collectionAutoInit = true; |
||
24 | |||
25 | /** |
||
26 | * The Model's has-one relationships |
||
27 | * |
||
28 | * @var Relationships\HasOne |
||
29 | */ |
||
30 | protected $hasOneRelationships; |
||
31 | |||
32 | /** |
||
33 | * The Model's has-many relationships |
||
34 | * |
||
35 | * @var Relationships\HasMany |
||
36 | */ |
||
37 | protected $hasManyRelationships; |
||
38 | |||
39 | /** |
||
40 | * The id value of this model. |
||
41 | * Always converted to a string when in the model context. |
||
42 | * |
||
43 | * @var string |
||
44 | */ |
||
45 | protected $identifier; |
||
46 | |||
47 | |||
48 | /** |
||
49 | * The metadata that defines this Model. |
||
50 | * |
||
51 | * @var EntityMetadata |
||
52 | */ |
||
53 | protected $metadata; |
||
54 | |||
55 | /** |
||
56 | * Constructor. |
||
57 | * |
||
58 | * @param EntityMetadata $metadata The internal entity metadata that supports this Model. |
||
59 | * @param string $identifier The database identifier. |
||
60 | * @param Store $store The model store service for handling persistence operations. |
||
61 | * @param array|null $properties The model's properties from the db layer to init the model with. New models will constructed with a null record. |
||
62 | */ |
||
63 | public function __construct(EntityMetadata $metadata, $identifier, Store $store, array $properties = null) |
||
64 | { |
||
65 | $this->identifier = $identifier; |
||
66 | parent::__construct($metadata, $store, $properties); |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Cloner. |
||
71 | * Ensures sub objects are also cloned. |
||
72 | * |
||
73 | */ |
||
74 | public function __clone() |
||
75 | { |
||
76 | parent::__clone(); |
||
77 | $this->hasOneRelationships = clone $this->hasOneRelationships; |
||
78 | $this->hasManyRelationships = clone $this->hasManyRelationships; |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * {@inheritdoc} |
||
83 | * |
||
84 | * Overloaded to support relationships. |
||
85 | * |
||
86 | */ |
||
87 | public function apply(array $properties) |
||
88 | { |
||
89 | foreach ($properties as $key => $value) { |
||
90 | if (true === $this->isHasOne($key)) { |
||
91 | if (empty($value)) { |
||
92 | $this->clear($key); |
||
93 | continue; |
||
94 | } |
||
95 | $value = $this->store->loadProxyModel($value['type'], $value['id']); |
||
96 | $this->set($key, $value); |
||
97 | continue; |
||
98 | } |
||
99 | |||
100 | } |
||
101 | |||
102 | foreach ($this->getMetadata()->getRelationships() as $key => $relMeta) { |
||
103 | if (true === $relMeta->isOne()) { |
||
104 | continue; |
||
105 | } |
||
106 | if (!isset($properties[$key]) || true === $relMeta->isInverse) { |
||
107 | continue; |
||
108 | } |
||
109 | $this->clear($key); |
||
110 | $collection = $this->store->createCollection($relMeta, $properties[$key]); |
||
111 | foreach ($collection->allWithoutLoad() as $value) { |
||
112 | $this->push($key, $value); |
||
0 ignored issues
–
show
|
|||
113 | } |
||
114 | } |
||
115 | return parent::apply($properties); |
||
116 | } |
||
117 | |||
118 | /** |
||
119 | * {@inheritdoc} |
||
120 | * |
||
121 | * Overloaded to support relationships. |
||
122 | */ |
||
123 | View Code Duplication | public function clear($key) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
124 | { |
||
125 | if (true === $this->isHasOne($key)) { |
||
126 | return $this->setHasOne($key, null); |
||
127 | } |
||
128 | if (true === $this->isInverse($key)) { |
||
129 | throw ModelException::cannotModifyInverse($this, $key); |
||
130 | } |
||
131 | if (true === $this->isHasMany($key)) { |
||
132 | $collection = $this->hasManyRelationships->get($key); |
||
133 | $collection->clear(); |
||
134 | $this->doDirtyCheck(); |
||
135 | return $this; |
||
136 | } |
||
137 | return parent::clear($key); |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Enables or disables has-many collection auto-initialization from the database. |
||
142 | * |
||
143 | * @param bool $bit Whether to enable/disable. |
||
144 | * @return self |
||
145 | */ |
||
146 | public function enableCollectionAutoInit($bit = true) |
||
147 | { |
||
148 | $this->collectionAutoInit = (Boolean) $bit; |
||
149 | return $this; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Marks the record for deletion. |
||
154 | * Will not remove from the database until $this->save() is called. |
||
155 | * |
||
156 | * @api |
||
157 | * @return self |
||
158 | * @throws \RuntimeException If a new (unsaved) model is deleted. |
||
159 | */ |
||
160 | public function delete() |
||
161 | { |
||
162 | if (true === $this->getState()->is('new')) { |
||
163 | throw new \RuntimeException('You cannot delete a new model'); |
||
164 | } |
||
165 | if (true === $this->getState()->is('deleted')) { |
||
166 | return $this; |
||
167 | } |
||
168 | $this->getState()->setDeleting(); |
||
169 | return $this; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * {@inheritdoc} |
||
174 | * |
||
175 | * Overloaded to support relationships. |
||
176 | * |
||
177 | */ |
||
178 | public function get($key) |
||
179 | { |
||
180 | if (true === $this->isRelationship($key)) { |
||
181 | return $this->getRelationship($key); |
||
182 | } |
||
183 | return parent::get($key); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * {@inheritdoc} |
||
188 | * |
||
189 | * Overloaded to support relationships. |
||
190 | * |
||
191 | */ |
||
192 | public function getChangeSet() |
||
193 | { |
||
194 | $changeset = parent::getChangeSet(); |
||
195 | $changeset['hasOne'] = $this->filterNotSavedProperties($this->hasOneRelationships->calculateChangeSet()); |
||
196 | $changeset['hasMany'] = $this->filterNotSavedProperties($this->hasManyRelationships->calculateChangeSet()); |
||
197 | return $changeset; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * {@inheritdoc} |
||
202 | */ |
||
203 | public function getCompositeKey() |
||
204 | { |
||
205 | return sprintf('%s.%s', $this->getType(), $this->getId()); |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Gets the unique identifier of this model. |
||
210 | * |
||
211 | * @api |
||
212 | * @return string |
||
213 | */ |
||
214 | public function getId() |
||
215 | { |
||
216 | return $this->identifier; |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Gets the metadata for this model. |
||
221 | * |
||
222 | * @api |
||
223 | * @return EntityMetadata |
||
224 | */ |
||
225 | public function getMetadata() |
||
226 | { |
||
227 | return $this->metadata; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Gets the model type. |
||
232 | * |
||
233 | * @api |
||
234 | * @return string |
||
235 | */ |
||
236 | public function getType() |
||
237 | { |
||
238 | return $this->metadata->type; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * {@inheritdoc} |
||
243 | * |
||
244 | * Overloaded to support relationships. |
||
245 | * |
||
246 | */ |
||
247 | public function initialize(array $properties = null) |
||
248 | { |
||
249 | $hasOne = []; |
||
250 | $hasMany = []; |
||
251 | |||
252 | if (null !== $properties) { |
||
253 | foreach ($properties as $key => $value) { |
||
254 | if (true === $this->isHasOne($key)) { |
||
255 | // Load hasOne relationship. |
||
256 | $hasOne[$key] = $this->getStore()->loadProxyModel($value['type'], $value['id']); |
||
257 | continue; |
||
258 | } |
||
259 | } |
||
260 | } |
||
261 | |||
262 | foreach ($this->getMetadata()->getRelationships() as $key => $relMeta) { |
||
263 | if (true === $relMeta->isOne()) { |
||
264 | continue; |
||
265 | } |
||
266 | if (true === $relMeta->isInverse) { |
||
267 | $hasMany[$key] = $this->getStore()->createInverseCollection($relMeta, $this); |
||
268 | } else { |
||
269 | $references = !isset($properties[$key]) ? [] : $properties[$key]; |
||
270 | $hasMany[$key] = $this->getStore()->createCollection($relMeta, $references); |
||
271 | } |
||
272 | } |
||
273 | |||
274 | $this->hasOneRelationships = (null === $this->hasOneRelationships) ? new Relationships\HasOne($hasOne) : $this->hasOneRelationships->replace($hasOne); |
||
275 | $this->hasManyRelationships = (null === $this->hasManyRelationships) ? new Relationships\HasMany($hasMany) : $this->hasManyRelationships->replace($hasMany); |
||
276 | return parent::initialize($properties); |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * {@inheritdoc} |
||
281 | * |
||
282 | * Overloaded to support relationships. |
||
283 | * |
||
284 | */ |
||
285 | public function isDirty() |
||
286 | { |
||
287 | return true === parent::isDirty() |
||
288 | || true === $this->hasOneRelationships->areDirty() |
||
289 | || true === $this->hasManyRelationships->areDirty() |
||
290 | ; |
||
291 | } |
||
292 | |||
293 | /** |
||
294 | * Determines if a property key is a has-many relationship. |
||
295 | * |
||
296 | * @api |
||
297 | * @param string $key The property key. |
||
298 | * @return bool |
||
299 | */ |
||
300 | public function isHasMany($key) |
||
301 | { |
||
302 | if (false === $this->isRelationship($key)) { |
||
303 | return false; |
||
304 | } |
||
305 | return $this->getMetadata()->getRelationship($key)->isMany(); |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Determines if a property key is a has-one relationship. |
||
310 | * |
||
311 | * @api |
||
312 | * @param string $key The property key. |
||
313 | * @return bool |
||
314 | */ |
||
315 | public function isHasOne($key) |
||
316 | { |
||
317 | if (false === $this->isRelationship($key)) { |
||
318 | return false; |
||
319 | } |
||
320 | return $this->getMetadata()->getRelationship($key)->isOne(); |
||
321 | } |
||
322 | |||
323 | /** |
||
324 | * Determines if a property key is a an inverse relationship. |
||
325 | * |
||
326 | * @api |
||
327 | * @param string $key The property key. |
||
328 | * @return bool |
||
329 | */ |
||
330 | public function isInverse($key) |
||
331 | { |
||
332 | if (false === $this->isRelationship($key)) { |
||
333 | return false; |
||
334 | } |
||
335 | return $this->getMetadata()->getRelationship($key)->isInverse; |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * Determines if a property key is a relationship (either has-one or has-many). |
||
340 | * |
||
341 | * @api |
||
342 | * @param string $key The property key. |
||
343 | * @return bool |
||
344 | */ |
||
345 | public function isRelationship($key) |
||
346 | { |
||
347 | return $this->getMetadata()->hasRelationship($key); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Pushes a Model into a has-many relationship collection. |
||
352 | * This method must be used for has-many relationships. Direct set is not supported. |
||
353 | * To completely replace a has-many, call clear() first and then push() the new Models. |
||
354 | * |
||
355 | * @api |
||
356 | * @param string $key |
||
357 | * @param Model $model |
||
358 | * @return self |
||
359 | */ |
||
360 | View Code Duplication | public function push($key, Model $model) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
361 | { |
||
362 | if (true === $this->isHasOne($key)) { |
||
363 | return $this->setHasOne($key, $model); |
||
364 | } |
||
365 | if (false === $this->isHasMany($key)) { |
||
366 | return $this; |
||
367 | } |
||
368 | if (true === $this->isInverse($key)) { |
||
369 | throw ModelException::cannotModifyInverse($this, $key); |
||
370 | } |
||
371 | $this->touch(); |
||
372 | $collection = $this->hasManyRelationships->get($key); |
||
373 | $collection->push($model); |
||
374 | $this->doDirtyCheck(); |
||
375 | return $this; |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * Reloads the model from the database. |
||
380 | * |
||
381 | * @api |
||
382 | * @return self |
||
383 | */ |
||
384 | public function reload() |
||
385 | { |
||
386 | return $this->touch(true); |
||
387 | } |
||
388 | |||
389 | /** |
||
390 | * Removes a specific Model from a has-many relationship collection. |
||
391 | * |
||
392 | * @api |
||
393 | * @param string $key The has-many relationship key. |
||
394 | * @param Model $model The model to remove from the collection. |
||
395 | * @return self |
||
396 | */ |
||
397 | View Code Duplication | public function remove($key, Model $model) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
398 | { |
||
399 | if (false === $this->isHasMany($key)) { |
||
400 | return $this; |
||
401 | } |
||
402 | if (true === $this->isInverse($key)) { |
||
403 | throw ModelException::cannotModifyInverse($this, $key); |
||
404 | } |
||
405 | $this->touch(); |
||
406 | $collection = $this->hasManyRelationships->get($key); |
||
407 | $collection->remove($model); |
||
408 | $this->doDirtyCheck(); |
||
409 | return $this; |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * {@inheritdoc} |
||
414 | * Overloaded to support relationship rollback. |
||
415 | */ |
||
416 | public function rollback() |
||
417 | { |
||
418 | $this->hasOneRelationships->rollback(); |
||
419 | $this->hasManyRelationships->rollback(); |
||
420 | return parent::rollback(); |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * {@inheritdoc} |
||
425 | * |
||
426 | * Overloaded to support relationships. |
||
427 | * Sets a model property: an attribute value, a has-one model, or an entire has-many model collection. |
||
428 | * Note: To push/remove a single Model into a has-many collection, or clear a collection, use @see push(), remove() and clear(). |
||
429 | * |
||
430 | */ |
||
431 | public function set($key, $value) |
||
432 | { |
||
433 | if (true === $this->isRelationship($key)) { |
||
434 | return $this->setRelationship($key, $value); |
||
435 | } |
||
436 | return parent::set($key, $value); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Saves the model. |
||
441 | * |
||
442 | * @api |
||
443 | * @param Implement cascade relationship saves. Or should the store handle this? |
||
444 | * @return self |
||
445 | */ |
||
446 | public function save() |
||
447 | { |
||
448 | if (true === $this->getState()->is('deleted')) { |
||
449 | return $this; |
||
450 | } |
||
451 | $this->store->commit($this); |
||
452 | return $this; |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * {@inheritdoc} |
||
457 | * |
||
458 | * Overloaded to support relationships. |
||
459 | */ |
||
460 | protected function filterNotSavedProperties(array $properties) |
||
461 | { |
||
462 | View Code Duplication | foreach ($this->getMetadata()->getRelationships() as $fieldKey => $propMeta) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
463 | if (true === $propMeta->shouldSave() || !isset($properties[$fieldKey])) { |
||
464 | continue; |
||
465 | } |
||
466 | unset($properties[$fieldKey]); |
||
467 | } |
||
468 | return parent::filterNotSavedProperties($properties); |
||
469 | } |
||
470 | |||
471 | /** |
||
472 | * {@inheritdoc} |
||
473 | * |
||
474 | * Overloaded to support global model defaults. |
||
475 | * |
||
476 | */ |
||
477 | protected function applyDefaultAttrValues(array $attributes = []) |
||
478 | { |
||
479 | $attributes = parent::applyDefaultAttrValues($attributes); |
||
480 | |||
481 | // Set defaults for the entire entity. |
||
482 | foreach ($this->getMetadata()->defaultValues as $key => $value) { |
||
483 | if (isset($attributes[$key])) { |
||
484 | continue; |
||
485 | } |
||
486 | $attributes[$key] = $this->convertAttributeValue($key, $value); |
||
487 | } |
||
488 | return $attributes; |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Gets a relationship value. |
||
493 | * |
||
494 | * @param string $key The relationship key (field) name. |
||
495 | * @return Model|array|null |
||
496 | * @throws \RuntimeException If hasMany relationships are accessed directly. |
||
497 | */ |
||
498 | protected function getRelationship($key) |
||
499 | { |
||
500 | if (true === $this->isHasOne($key)) { |
||
501 | $this->touch(); |
||
502 | return $this->hasOneRelationships->get($key); |
||
503 | } |
||
504 | if (true === $this->isHasMany($key)) { |
||
505 | $this->touch(); |
||
506 | $collection = $this->hasManyRelationships->get($key); |
||
507 | if ($collection->isLoaded($collection)) { |
||
508 | return iterator_to_array($collection); |
||
509 | } |
||
510 | return (true === $this->collectionAutoInit) ? iterator_to_array($collection) : $collection->allWithoutLoad(); |
||
511 | } |
||
512 | return null; |
||
513 | } |
||
514 | |||
515 | /** |
||
516 | * Sets a has-one relationship. |
||
517 | * |
||
518 | * @param string $key The relationship key (field) name. |
||
519 | * @param Model|null $model The model to relate. |
||
520 | * @return self |
||
521 | */ |
||
522 | View Code Duplication | protected function setHasOne($key, Model $model = null) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
523 | { |
||
524 | if (true === $this->isInverse($key)) { |
||
525 | throw ModelException::cannotModifyInverse($this, $key); |
||
526 | } |
||
527 | if (null !== $model) { |
||
528 | $this->validateRelSet($key, $model->getType()); |
||
529 | } |
||
530 | $this->touch(); |
||
531 | $this->hasOneRelationships->set($key, $model); |
||
532 | $this->doDirtyCheck(); |
||
533 | return $this; |
||
534 | } |
||
535 | |||
536 | /** |
||
537 | * Sets a relationship value. |
||
538 | * |
||
539 | * @param string $key |
||
540 | * @param Model|null $value |
||
541 | * @return self |
||
542 | */ |
||
543 | protected function setRelationship($key, $value) |
||
544 | { |
||
545 | if (true === $this->isHasOne($key)) { |
||
546 | return $this->setHasOne($key, $value); |
||
547 | } |
||
548 | if (true === $this->isHasMany($key)) { |
||
549 | throw new \RuntimeException('You cannot set a hasMany relationship directly. Please access using push(), clear(), and/or remove()'); |
||
550 | } |
||
551 | return $this; |
||
552 | } |
||
553 | |||
554 | /** |
||
555 | * {@inheritdoc} |
||
556 | * |
||
557 | * Overloaded to handle loading from the database. |
||
558 | * If the model is currently empty, it will query the database and fill/load the model. |
||
559 | * |
||
560 | */ |
||
561 | protected function touch($force = false) |
||
562 | { |
||
563 | if (true === $this->getState()->is('deleted')) { |
||
564 | return $this; |
||
565 | } |
||
566 | if (true === $this->getState()->is('empty') || true === $force) { |
||
567 | $record = $this->store->retrieveRecord($this->getType(), $this->getId()); |
||
568 | $this->initialize($record['properties']); |
||
569 | $this->getState()->setLoaded(); |
||
570 | } |
||
571 | return $this; |
||
572 | } |
||
573 | |||
574 | /** |
||
575 | * Validates that the model type (from a Model or Collection instance) can be set to the relationship field. |
||
576 | * |
||
577 | * @param string $relKey The relationship field key. |
||
578 | * @param string $type The model type that is being related. |
||
579 | * @return self |
||
580 | */ |
||
581 | protected function validateRelSet($relKey, $type) |
||
582 | { |
||
583 | $relMeta = $this->getMetadata()->getRelationship($relKey); |
||
584 | $relatedModelMeta = $this->getStore()->getMetadataForRelationship($relMeta); |
||
0 ignored issues
–
show
It seems like
$relMeta defined by $this->getMetadata()->getRelationship($relKey) on line 583 can be null ; however, As3\Modlr\Store\Store::g...tadataForRelationship() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
585 | $this->getStore()->validateRelationshipSet($relatedModelMeta, $type); |
||
586 | return $this; |
||
587 | } |
||
588 | } |
||
589 |
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.
Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.