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 | * Spiral, Core Components |
||
4 | * |
||
5 | * @author Wolfy-J |
||
6 | */ |
||
7 | |||
8 | namespace Spiral\ODM; |
||
9 | |||
10 | use MongoDB\BSON\ObjectID; |
||
11 | use Spiral\Core\Component; |
||
12 | use Spiral\Core\Exceptions\ScopeException; |
||
13 | use Spiral\Core\Traits\SaturateTrait; |
||
14 | use Spiral\Models\AccessorInterface; |
||
15 | use Spiral\Models\SchematicEntity; |
||
16 | use Spiral\Models\Traits\SolidableTrait; |
||
17 | use Spiral\ODM\Entities\DocumentCompositor; |
||
18 | use Spiral\ODM\Entities\DocumentInstantiator; |
||
19 | use Spiral\ODM\Exceptions\AccessException; |
||
20 | use Spiral\ODM\Exceptions\AggregationException; |
||
21 | use Spiral\ODM\Exceptions\DocumentException; |
||
22 | use Spiral\ODM\Helpers\AggregationHelper; |
||
23 | use Spiral\ODM\Schemas\Definitions\CompositionDefinition; |
||
24 | |||
25 | /** |
||
26 | * Primary class for spiral ODM, provides ability to pack it's own updates in a form of atomic |
||
27 | * updates. |
||
28 | * |
||
29 | * You can use same properties to configure entity as in DataEntity + schema property. |
||
30 | * |
||
31 | * Example: |
||
32 | * |
||
33 | * class Test extends DocumentEntity |
||
34 | * { |
||
35 | * const SCHEMA = [ |
||
36 | * 'name' => 'string' |
||
37 | * ]; |
||
38 | * } |
||
39 | * |
||
40 | * Configuration properties: |
||
41 | * - schema |
||
42 | * - defaults |
||
43 | * - secured (* by default) |
||
44 | * - fillable |
||
45 | */ |
||
46 | abstract class DocumentEntity extends SchematicEntity implements CompositableInterface |
||
47 | { |
||
48 | use SaturateTrait, SolidableTrait; |
||
49 | |||
50 | /* |
||
51 | * Begin set of behaviour and description constants. |
||
52 | * ================================================ |
||
53 | */ |
||
54 | |||
55 | /** |
||
56 | * Set of schema sections needed to describe entity behaviour. |
||
57 | */ |
||
58 | const SH_INSTANTIATION = 0; |
||
59 | const SH_DEFAULTS = 1; |
||
60 | const SH_COMPOSITIONS = 5; |
||
61 | const SH_AGGREGATIONS = 6; |
||
62 | |||
63 | /** |
||
64 | * Constants used to describe aggregation relations (also used internally to identify |
||
65 | * composition). |
||
66 | * |
||
67 | * Example: |
||
68 | * 'items' => [self::MANY => Item::class, ['parentID' => 'key::_id']] |
||
69 | * |
||
70 | * @see DocumentEntity::SCHEMA |
||
71 | */ |
||
72 | const MANY = 778; |
||
73 | const ONE = 899; |
||
74 | |||
75 | /* |
||
76 | * ================================================ |
||
77 | * End set of behaviour and description constants. |
||
78 | */ |
||
79 | |||
80 | /** |
||
81 | * Class responsible for instance construction. |
||
82 | */ |
||
83 | const INSTANTIATOR = DocumentInstantiator::class; |
||
84 | |||
85 | /** |
||
86 | * Document fields, accessors and relations. ODM will generate setters and getters for some |
||
87 | * fields based on their types. |
||
88 | * |
||
89 | * Example, fields: |
||
90 | * const SCHEMA = [ |
||
91 | * '_id' => 'MongoId', //Primary key field |
||
92 | * 'value' => 'string', //Default string field |
||
93 | * 'values' => ['string'] //ScalarArray accessor will be applied for fields like that |
||
94 | * ]; |
||
95 | * |
||
96 | * Compositions: |
||
97 | * const SCHEMA = [ |
||
98 | * ..., |
||
99 | * 'child' => Child::class, //One document are composited, for example user Profile |
||
100 | * 'many' => [Child::class] //Compositor accessor will be applied, allows to |
||
101 | * //composite many document instances |
||
102 | * ]; |
||
103 | * |
||
104 | * Documents can extend each other, in this case schema will also be inherited. |
||
105 | * |
||
106 | * Attention, make sure you properly set FILLABLE option in parent class to use constructions |
||
107 | * like: |
||
108 | * $parent->child = [...]; |
||
109 | * |
||
110 | * or |
||
111 | * $parent->setFields(['child'=>[...]]); |
||
112 | * |
||
113 | * @var array |
||
114 | */ |
||
115 | const SCHEMA = []; |
||
116 | |||
117 | /** |
||
118 | * Default field values. |
||
119 | * |
||
120 | * @var array |
||
121 | */ |
||
122 | const DEFAULTS = []; |
||
123 | |||
124 | /** |
||
125 | * Model behaviour configurations. |
||
126 | */ |
||
127 | const SECURED = '*'; |
||
128 | const FILLABLE = []; |
||
129 | const SETTERS = []; |
||
130 | const GETTERS = []; |
||
131 | const ACCESSORS = []; |
||
132 | |||
133 | /** |
||
134 | * Document behaviour schema. |
||
135 | * |
||
136 | * @var array |
||
137 | */ |
||
138 | private $documentSchema = []; |
||
139 | |||
140 | /** |
||
141 | * Document field updates (changed values). |
||
142 | * |
||
143 | * @var array |
||
144 | */ |
||
145 | private $changes = []; |
||
146 | |||
147 | /** |
||
148 | * Parent ODM instance, responsible for aggregations and lazy loading operations. |
||
149 | * |
||
150 | * @invisible |
||
151 | * @var ODMInterface |
||
152 | */ |
||
153 | protected $odm; |
||
154 | |||
155 | /** |
||
156 | * {@inheritdoc} |
||
157 | * |
||
158 | * @param ODMInterface $odm To lazy create nested document ang aggregations. |
||
159 | * |
||
160 | * @throws ScopeException When no ODM instance can be resolved. |
||
161 | */ |
||
162 | public function __construct(array $data = [], ODMInterface $odm = null, array $schema = null) |
||
163 | { |
||
164 | //We can use global container as fallback if no default values were provided |
||
165 | $this->odm = $this->saturate($odm, ODMInterface::class); |
||
166 | |||
167 | //Use supplied schema or fetch one from ODM |
||
168 | $this->documentSchema = !empty($schema) ? $schema : $this->odm->define( |
||
169 | static::class, |
||
170 | ODMInterface::D_SCHEMA |
||
171 | ); |
||
172 | |||
173 | $data = is_array($data) ? $data : []; |
||
174 | if (!empty($this->documentSchema[self::SH_DEFAULTS])) { |
||
175 | //Merging with default values |
||
176 | $data = array_replace_recursive($this->documentSchema[self::SH_DEFAULTS], $data); |
||
177 | } |
||
178 | |||
179 | parent::__construct($data, $this->documentSchema); |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * {@inheritdoc} |
||
184 | */ |
||
185 | public function getField(string $name, $default = null, bool $filter = true) |
||
186 | { |
||
187 | $this->assertField($name); |
||
188 | |||
189 | return parent::getField($name, $default, $filter); |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * {@inheritdoc} |
||
194 | * |
||
195 | * Tracks field changes. |
||
196 | */ |
||
197 | public function setField(string $name, $value, bool $filter = true) |
||
198 | { |
||
199 | $this->assertField($name); |
||
200 | $this->registerChange($name); |
||
201 | |||
202 | parent::setField($name, $value, $filter); |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * {@inheritdoc} |
||
207 | * |
||
208 | * Will restore default value if presented. |
||
209 | */ |
||
210 | public function __unset($offset) |
||
211 | { |
||
212 | if (!$this->isNullable($offset)) { |
||
213 | throw new AccessException("Unable to unset not nullable field '{$offset}'"); |
||
214 | } |
||
215 | |||
216 | $this->setField($offset, null, false); |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Provides ability to invoke document aggregation. |
||
221 | * |
||
222 | * @param string $method |
||
223 | * @param array $arguments |
||
224 | * |
||
225 | * @return mixed|null|AccessorInterface|CompositableInterface|Document|Entities\DocumentSelector |
||
226 | */ |
||
227 | public function __call($method, array $arguments) |
||
228 | { |
||
229 | if (isset($this->documentSchema[self::SH_AGGREGATIONS][$method])) { |
||
230 | if (!empty($arguments)) { |
||
231 | throw new AggregationException("Aggregation method call except 0 parameters"); |
||
232 | } |
||
233 | |||
234 | $helper = new AggregationHelper($this, $this->odm); |
||
235 | |||
236 | return $helper->createAggregation($method); |
||
237 | } |
||
238 | |||
239 | throw new DocumentException("Undefined method call '{$method}' in '" . get_called_class() . "'"); |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * {@inheritdoc} |
||
244 | * |
||
245 | * @param string $field Check once specific field changes. |
||
246 | */ |
||
247 | public function hasChanges(string $field = null): bool |
||
248 | { |
||
249 | //Check updates for specific field |
||
250 | if (!empty($field)) { |
||
251 | if (array_key_exists($field, $this->changes)) { |
||
252 | return true; |
||
253 | } |
||
254 | |||
255 | //Do not force accessor creation |
||
256 | $value = $this->getField($field, null, false); |
||
257 | if ($value instanceof CompositableInterface && $value->hasChanges()) { |
||
258 | return true; |
||
259 | } |
||
260 | |||
261 | return false; |
||
262 | } |
||
263 | |||
264 | if (!empty($this->changes)) { |
||
265 | return true; |
||
266 | } |
||
267 | |||
268 | //Do not force accessor creation |
||
269 | foreach ($this->getFields(false) as $value) { |
||
270 | //Checking all fields for changes (handled internally) |
||
271 | if ($value instanceof CompositableInterface && $value->hasChanges()) { |
||
272 | return true; |
||
273 | } |
||
274 | } |
||
275 | |||
276 | return false; |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * {@inheritdoc} |
||
281 | */ |
||
282 | public function buildAtomics(string $container = null): array |
||
283 | { |
||
284 | if (!$this->hasChanges() && !$this->isSolid()) { |
||
285 | return []; |
||
286 | } |
||
287 | |||
288 | if ($this->isSolid()) { |
||
289 | if (!empty($container)) { |
||
290 | //Simple nested document in solid state |
||
291 | return ['$set' => [$container => $this->packValue(false)]]; |
||
292 | } |
||
293 | |||
294 | //No parent container |
||
295 | return ['$set' => $this->packValue(false)]; |
||
296 | } |
||
297 | |||
298 | //Aggregate atomics from every nested composition |
||
299 | $atomics = []; |
||
300 | |||
301 | foreach ($this->getFields(false) as $field => $value) { |
||
302 | if ($value instanceof CompositableInterface) { |
||
303 | $atomics = array_merge_recursive( |
||
304 | $atomics, |
||
305 | $value->buildAtomics((!empty($container) ? $container . '.' : '') . $field) |
||
306 | ); |
||
307 | |||
308 | continue; |
||
309 | } |
||
310 | |||
311 | foreach ($atomics as $atomic => $operations) { |
||
312 | if (array_key_exists($field, $operations) && $atomic != '$set') { |
||
313 | //Property already changed by atomic operation |
||
314 | continue; |
||
315 | } |
||
316 | } |
||
317 | |||
318 | if (array_key_exists($field, $this->changes)) { |
||
319 | //Generating set operation for changed field |
||
320 | $atomics['$set'][(!empty($container) ? $container . '.' : '') . $field] = $value; |
||
321 | } |
||
322 | } |
||
323 | |||
324 | return $atomics; |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * {@inheritdoc} |
||
329 | */ |
||
330 | public function flushChanges() |
||
331 | { |
||
332 | $this->changes = []; |
||
333 | |||
334 | foreach ($this->getFields(false) as $field => $value) { |
||
335 | if ($value instanceof CompositableInterface) { |
||
336 | $value->flushChanges(); |
||
337 | } |
||
338 | } |
||
339 | } |
||
340 | |||
341 | /** |
||
342 | * {@inheritdoc} |
||
343 | * |
||
344 | * @param bool $includeID Set to false to exclude _id from packed fields. |
||
345 | */ |
||
346 | public function packValue(bool $includeID = true) |
||
347 | { |
||
348 | $values = parent::packValue(); |
||
349 | |||
350 | if (!$includeID) { |
||
351 | unset($values['_id']); |
||
352 | } |
||
353 | |||
354 | return $values; |
||
355 | } |
||
356 | |||
357 | /** |
||
358 | * Since most of ODM documents might contain ObjectIDs and other fields we will try to normalize |
||
359 | * them into string values. |
||
360 | * |
||
361 | * @return array |
||
362 | */ |
||
363 | public function publicValue(): array |
||
364 | { |
||
365 | $public = parent::publicValue(); |
||
366 | |||
367 | array_walk_recursive($public, function (&$value) { |
||
368 | if ($value instanceof ObjectID) { |
||
0 ignored issues
–
show
|
|||
369 | $value = (string)$value; |
||
370 | } |
||
371 | }); |
||
372 | |||
373 | return $public; |
||
374 | } |
||
375 | |||
376 | /** |
||
377 | * Cloning will be called when object will be embedded into another document. |
||
378 | */ |
||
379 | public function __clone() |
||
380 | { |
||
381 | //De-serialize document in order to ensure that all compositions are recreated |
||
382 | $this->setValue($this->packValue()); |
||
383 | |||
384 | //Since document embedded as one piece let's ensure that it is solid |
||
385 | $this->solidState = true; |
||
386 | $this->changes = []; |
||
387 | } |
||
388 | |||
389 | /** |
||
390 | * @return array |
||
391 | */ |
||
392 | public function __debugInfo() |
||
393 | { |
||
394 | return [ |
||
395 | 'fields' => $this->getFields(), |
||
396 | 'atomics' => $this->hasChanges() ? $this->buildAtomics() : [], |
||
397 | ]; |
||
398 | } |
||
399 | |||
400 | /** |
||
401 | * {@inheritdoc} |
||
402 | * |
||
403 | * @see CompositionDefinition |
||
404 | */ |
||
405 | protected function getMutator(string $field, string $mutator) |
||
406 | { |
||
407 | /** |
||
408 | * Every document composition is valid accessor but defined a bit differently. |
||
409 | */ |
||
410 | if (isset($this->documentSchema[self::SH_COMPOSITIONS][$field])) { |
||
411 | return $this->documentSchema[self::SH_COMPOSITIONS][$field]; |
||
412 | } |
||
413 | |||
414 | return parent::getMutator($field, $mutator); |
||
415 | } |
||
416 | |||
417 | /** |
||
418 | * {@inheritdoc} |
||
419 | */ |
||
420 | protected function isNullable(string $field): bool |
||
421 | { |
||
422 | if (array_key_exists($field, $this->documentSchema[self::SH_DEFAULTS])) { |
||
423 | //Only fields with default null value can be nullable |
||
424 | return is_null($this->documentSchema[self::SH_DEFAULTS][$field]); |
||
425 | } |
||
426 | |||
427 | //Values unknown to schema always nullable |
||
428 | return true; |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * {@inheritdoc} |
||
433 | * |
||
434 | * DocumentEntity will pass ODM instance as part of accessor context. |
||
435 | * |
||
436 | * @see CompositionDefinition |
||
437 | */ |
||
438 | protected function createAccessor( |
||
439 | $accessor, |
||
440 | string $name, |
||
441 | $value, |
||
442 | array $context = [] |
||
443 | ): AccessorInterface { |
||
444 | if (is_array($accessor)) { |
||
445 | //We are working with definition of composition. |
||
446 | switch ($accessor[0]) { |
||
447 | case self::ONE: |
||
448 | //Singular embedded document |
||
449 | return $this->odm->make($accessor[1], $value, false); |
||
450 | case self::MANY: |
||
451 | return new DocumentCompositor($accessor[1], $value, $this->odm); |
||
452 | } |
||
453 | |||
454 | throw new AccessException("Invalid accessor definition for field '{$name}'"); |
||
455 | } |
||
456 | |||
457 | //Field as a context |
||
458 | return parent::createAccessor($accessor, $name, $value, $context + ['odm' => $this->odm]); |
||
459 | } |
||
460 | |||
461 | /** |
||
462 | * {@inheritdoc} |
||
463 | */ |
||
464 | protected function iocContainer() |
||
465 | { |
||
466 | if ($this->odm instanceof Component) { |
||
467 | //Forwarding IoC scope to parent ODM instance |
||
468 | return $this->odm->iocContainer(); |
||
469 | } |
||
470 | |||
471 | return parent::iocContainer(); |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * @param string $name |
||
476 | */ |
||
477 | private function registerChange(string $name) |
||
478 | { |
||
479 | $original = $this->getField($name, null, false); |
||
480 | |||
481 | if (!array_key_exists($name, $this->changes)) { |
||
482 | //Let's keep track of how field looked before first change |
||
483 | $this->changes[$name] = $original instanceof AccessorInterface |
||
484 | ? $original->packValue() |
||
485 | : $original; |
||
486 | } |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * @param string $name |
||
491 | * |
||
492 | * @throws AccessException |
||
493 | */ |
||
494 | private function assertField(string $name) |
||
495 | { |
||
496 | if (!$this->hasField($name)) { |
||
497 | throw new AccessException(sprintf( |
||
498 | "No such property '%s' in '%s', check schema being relevant", |
||
499 | $name, |
||
500 | get_called_class() |
||
501 | )); |
||
502 | } |
||
503 | } |
||
504 | } |
||
505 |
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.