These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Doctrine\ODM\MongoDB\Hydrator; |
||
6 | |||
7 | use Doctrine\Common\EventManager; |
||
8 | use Doctrine\ODM\MongoDB\Configuration; |
||
9 | use Doctrine\ODM\MongoDB\DocumentManager; |
||
10 | use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs; |
||
11 | use Doctrine\ODM\MongoDB\Event\PreLoadEventArgs; |
||
12 | use Doctrine\ODM\MongoDB\Events; |
||
13 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
||
14 | use Doctrine\ODM\MongoDB\Types\Type; |
||
15 | use Doctrine\ODM\MongoDB\UnitOfWork; |
||
16 | use ProxyManager\Proxy\GhostObjectInterface; |
||
17 | use const DIRECTORY_SEPARATOR; |
||
18 | use function array_key_exists; |
||
19 | use function chmod; |
||
20 | use function class_exists; |
||
21 | use function dirname; |
||
22 | use function file_exists; |
||
23 | use function file_put_contents; |
||
24 | use function get_class; |
||
25 | use function is_dir; |
||
26 | use function is_writable; |
||
27 | use function mkdir; |
||
28 | use function rename; |
||
29 | use function rtrim; |
||
30 | use function sprintf; |
||
31 | use function str_replace; |
||
32 | use function substr; |
||
33 | use function uniqid; |
||
34 | |||
35 | /** |
||
36 | * The HydratorFactory class is responsible for instantiating a correct hydrator |
||
37 | * type based on document's ClassMetadata |
||
38 | */ |
||
39 | class HydratorFactory |
||
40 | { |
||
41 | /** |
||
42 | * The DocumentManager this factory is bound to. |
||
43 | * |
||
44 | * @var DocumentManager |
||
45 | */ |
||
46 | private $dm; |
||
47 | |||
48 | /** |
||
49 | * The UnitOfWork used to coordinate object-level transactions. |
||
50 | * |
||
51 | * @var UnitOfWork |
||
52 | */ |
||
53 | private $unitOfWork; |
||
54 | |||
55 | /** |
||
56 | * The EventManager associated with this Hydrator |
||
57 | * |
||
58 | * @var EventManager |
||
59 | */ |
||
60 | private $evm; |
||
61 | |||
62 | /** |
||
63 | * Which algorithm to use to automatically (re)generate hydrator classes. |
||
64 | * |
||
65 | * @var int |
||
66 | */ |
||
67 | private $autoGenerate; |
||
68 | |||
69 | /** |
||
70 | * The namespace that contains all hydrator classes. |
||
71 | * |
||
72 | * @var string|null |
||
73 | */ |
||
74 | private $hydratorNamespace; |
||
75 | |||
76 | /** |
||
77 | * The directory that contains all hydrator classes. |
||
78 | * |
||
79 | * @var string |
||
80 | */ |
||
81 | private $hydratorDir; |
||
82 | |||
83 | /** |
||
84 | * Array of instantiated document hydrators. |
||
85 | * |
||
86 | * @var array |
||
87 | */ |
||
88 | private $hydrators = []; |
||
89 | |||
90 | /** |
||
91 | * @throws HydratorException |
||
92 | */ |
||
93 | 1651 | public function __construct(DocumentManager $dm, EventManager $evm, ?string $hydratorDir, ?string $hydratorNs, int $autoGenerate) |
|
94 | { |
||
95 | 1651 | if (! $hydratorDir) { |
|
96 | throw HydratorException::hydratorDirectoryRequired(); |
||
97 | } |
||
98 | 1651 | if (! $hydratorNs) { |
|
99 | throw HydratorException::hydratorNamespaceRequired(); |
||
100 | } |
||
101 | 1651 | $this->dm = $dm; |
|
102 | 1651 | $this->evm = $evm; |
|
103 | 1651 | $this->hydratorDir = $hydratorDir; |
|
104 | 1651 | $this->hydratorNamespace = $hydratorNs; |
|
105 | 1651 | $this->autoGenerate = $autoGenerate; |
|
106 | 1651 | } |
|
107 | |||
108 | /** |
||
109 | * Sets the UnitOfWork instance. |
||
110 | */ |
||
111 | 1651 | public function setUnitOfWork(UnitOfWork $uow) : void |
|
112 | { |
||
113 | 1651 | $this->unitOfWork = $uow; |
|
114 | 1651 | } |
|
115 | |||
116 | /** |
||
117 | * Gets the hydrator object for the given document class. |
||
118 | */ |
||
119 | 385 | public function getHydratorFor(string $className) : HydratorInterface |
|
120 | { |
||
121 | 385 | if (isset($this->hydrators[$className])) { |
|
122 | 213 | return $this->hydrators[$className]; |
|
123 | } |
||
124 | 385 | $hydratorClassName = str_replace('\\', '', $className) . 'Hydrator'; |
|
125 | 385 | $fqn = $this->hydratorNamespace . '\\' . $hydratorClassName; |
|
126 | 385 | $class = $this->dm->getClassMetadata($className); |
|
127 | |||
128 | 385 | if (! class_exists($fqn, false)) { |
|
129 | 177 | $fileName = $this->hydratorDir . DIRECTORY_SEPARATOR . $hydratorClassName . '.php'; |
|
130 | 177 | switch ($this->autoGenerate) { |
|
131 | case Configuration::AUTOGENERATE_NEVER: |
||
132 | require $fileName; |
||
133 | break; |
||
134 | |||
135 | case Configuration::AUTOGENERATE_ALWAYS: |
||
136 | 177 | $this->generateHydratorClass($class, $hydratorClassName, $fileName); |
|
137 | 177 | require $fileName; |
|
138 | 177 | break; |
|
139 | |||
140 | case Configuration::AUTOGENERATE_FILE_NOT_EXISTS: |
||
141 | if (! file_exists($fileName)) { |
||
142 | $this->generateHydratorClass($class, $hydratorClassName, $fileName); |
||
143 | } |
||
144 | require $fileName; |
||
145 | break; |
||
146 | |||
147 | case Configuration::AUTOGENERATE_EVAL: |
||
148 | $this->generateHydratorClass($class, $hydratorClassName, null); |
||
149 | break; |
||
150 | } |
||
151 | } |
||
152 | 385 | $this->hydrators[$className] = new $fqn($this->dm, $this->unitOfWork, $class); |
|
153 | 385 | return $this->hydrators[$className]; |
|
154 | } |
||
155 | |||
156 | /** |
||
157 | * Generates hydrator classes for all given classes. |
||
158 | * |
||
159 | * @param array $classes The classes (ClassMetadata instances) for which to generate hydrators. |
||
160 | * @param string $toDir The target directory of the hydrator classes. If not specified, the |
||
161 | * directory configured on the Configuration of the DocumentManager used |
||
162 | * by this factory is used. |
||
163 | */ |
||
164 | public function generateHydratorClasses(array $classes, ?string $toDir = null) : void |
||
165 | { |
||
166 | $hydratorDir = $toDir ?: $this->hydratorDir; |
||
167 | $hydratorDir = rtrim($hydratorDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
||
168 | foreach ($classes as $class) { |
||
169 | $hydratorClassName = str_replace('\\', '', $class->name) . 'Hydrator'; |
||
170 | $hydratorFileName = $hydratorDir . $hydratorClassName . '.php'; |
||
171 | $this->generateHydratorClass($class, $hydratorClassName, $hydratorFileName); |
||
172 | } |
||
173 | } |
||
174 | |||
175 | 177 | private function generateHydratorClass(ClassMetadata $class, string $hydratorClassName, ?string $fileName) : void |
|
176 | { |
||
177 | 177 | $code = ''; |
|
178 | |||
179 | 177 | foreach ($class->fieldMappings as $fieldName => $mapping) { |
|
180 | 177 | if (isset($mapping['alsoLoadFields'])) { |
|
181 | 5 | foreach ($mapping['alsoLoadFields'] as $name) { |
|
182 | 5 | $code .= sprintf( |
|
183 | <<<EOF |
||
184 | |||
185 | 5 | /** @AlsoLoad("$name") */ |
|
186 | 5 | if (!array_key_exists('%1\$s', \$data) && array_key_exists('$name', \$data)) { |
|
187 | 5 | \$data['%1\$s'] = \$data['$name']; |
|
188 | } |
||
189 | |||
190 | EOF |
||
191 | , |
||
192 | 5 | $mapping['name'] |
|
193 | ); |
||
194 | } |
||
195 | } |
||
196 | |||
197 | 177 | if ($mapping['type'] === 'date') { |
|
198 | 9 | $code .= sprintf( |
|
199 | <<<EOF |
||
200 | |||
201 | /** @Field(type="date") */ |
||
202 | if (isset(\$data['%1\$s'])) { |
||
203 | \$value = \$data['%1\$s']; |
||
204 | %3\$s |
||
205 | \$this->class->reflFields['%2\$s']->setValue(\$document, clone \$return); |
||
206 | \$hydratedData['%2\$s'] = \$return; |
||
207 | } |
||
208 | |||
209 | EOF |
||
210 | , |
||
211 | 9 | $mapping['name'], |
|
212 | 9 | $mapping['fieldName'], |
|
213 | 9 | Type::getType($mapping['type'])->closureToPHP() |
|
214 | ); |
||
215 | 177 | } elseif (! isset($mapping['association'])) { |
|
216 | 176 | $code .= sprintf( |
|
217 | <<<EOF |
||
218 | |||
219 | 176 | /** @Field(type="{$mapping['type']}") */ |
|
220 | if (isset(\$data['%1\$s']) || (! empty(\$this->class->fieldMappings['%2\$s']['nullable']) && array_key_exists('%1\$s', \$data))) { |
||
221 | \$value = \$data['%1\$s']; |
||
222 | if (\$value !== null) { |
||
223 | \$typeIdentifier = \$this->class->fieldMappings['%2\$s']['type']; |
||
224 | %3\$s |
||
225 | } else { |
||
226 | \$return = null; |
||
227 | } |
||
228 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
229 | \$hydratedData['%2\$s'] = \$return; |
||
230 | } |
||
231 | |||
232 | EOF |
||
233 | , |
||
234 | 176 | $mapping['name'], |
|
235 | 176 | $mapping['fieldName'], |
|
236 | 176 | Type::getType($mapping['type'])->closureToPHP() |
|
237 | ); |
||
238 | 118 | } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isOwningSide']) { |
|
239 | 50 | $code .= sprintf( |
|
240 | <<<EOF |
||
241 | |||
242 | /** @ReferenceOne */ |
||
243 | if (isset(\$data['%1\$s'])) { |
||
244 | \$reference = \$data['%1\$s']; |
||
245 | \$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$reference); |
||
246 | \$identifier = ClassMetadata::getReferenceId(\$reference, \$this->class->fieldMappings['%2\$s']['storeAs']); |
||
247 | \$targetMetadata = \$this->dm->getClassMetadata(\$className); |
||
248 | \$id = \$targetMetadata->getPHPIdentifierValue(\$identifier); |
||
249 | \$return = \$this->dm->getReference(\$className, \$id); |
||
250 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
251 | \$hydratedData['%2\$s'] = \$return; |
||
252 | } |
||
253 | |||
254 | EOF |
||
255 | , |
||
256 | 50 | $mapping['name'], |
|
257 | 50 | $mapping['fieldName'] |
|
258 | ); |
||
259 | 100 | } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isInverseSide']) { |
|
260 | 6 | if (isset($mapping['repositoryMethod']) && $mapping['repositoryMethod']) { |
|
261 | 1 | $code .= sprintf( |
|
262 | <<<EOF |
||
263 | |||
264 | \$className = \$this->class->fieldMappings['%2\$s']['targetDocument']; |
||
265 | \$return = \$this->dm->getRepository(\$className)->%3\$s(\$document); |
||
266 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
267 | \$hydratedData['%2\$s'] = \$return; |
||
268 | |||
269 | EOF |
||
270 | , |
||
271 | 1 | $mapping['name'], |
|
272 | 1 | $mapping['fieldName'], |
|
273 | 1 | $mapping['repositoryMethod'] |
|
274 | ); |
||
275 | } else { |
||
276 | 6 | $code .= sprintf( |
|
277 | <<<EOF |
||
278 | |||
279 | \$mapping = \$this->class->fieldMappings['%2\$s']; |
||
280 | \$className = \$mapping['targetDocument']; |
||
281 | \$targetClass = \$this->dm->getClassMetadata(\$mapping['targetDocument']); |
||
282 | \$mappedByMapping = \$targetClass->fieldMappings[\$mapping['mappedBy']]; |
||
283 | \$mappedByFieldName = ClassMetadata::getReferenceFieldName(\$mappedByMapping['storeAs'], \$mapping['mappedBy']); |
||
284 | \$criteria = array_merge( |
||
285 | array(\$mappedByFieldName => \$data['_id']), |
||
286 | isset(\$this->class->fieldMappings['%2\$s']['criteria']) ? \$this->class->fieldMappings['%2\$s']['criteria'] : array() |
||
287 | ); |
||
288 | \$sort = isset(\$this->class->fieldMappings['%2\$s']['sort']) ? \$this->class->fieldMappings['%2\$s']['sort'] : array(); |
||
289 | \$return = \$this->unitOfWork->getDocumentPersister(\$className)->load(\$criteria, null, array(), 0, \$sort); |
||
290 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
291 | \$hydratedData['%2\$s'] = \$return; |
||
292 | |||
293 | EOF |
||
294 | , |
||
295 | 6 | $mapping['name'], |
|
296 | 6 | $mapping['fieldName'] |
|
297 | ); |
||
298 | } |
||
299 | 99 | } elseif ($mapping['association'] === ClassMetadata::REFERENCE_MANY || $mapping['association'] === ClassMetadata::EMBED_MANY) { |
|
300 | 79 | $code .= sprintf( |
|
301 | <<<EOF |
||
302 | |||
303 | /** @Many */ |
||
304 | \$mongoData = isset(\$data['%1\$s']) ? \$data['%1\$s'] : null; |
||
305 | \$return = \$this->dm->getConfiguration()->getPersistentCollectionFactory()->create(\$this->dm, \$this->class->fieldMappings['%2\$s']); |
||
306 | \$return->setHints(\$hints); |
||
307 | \$return->setOwner(\$document, \$this->class->fieldMappings['%2\$s']); |
||
308 | \$return->setInitialized(false); |
||
309 | if (\$mongoData) { |
||
310 | \$return->setMongoData(\$mongoData); |
||
311 | } |
||
312 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
313 | \$hydratedData['%2\$s'] = \$return; |
||
314 | |||
315 | EOF |
||
316 | , |
||
317 | 79 | $mapping['name'], |
|
318 | 79 | $mapping['fieldName'] |
|
319 | ); |
||
320 | 44 | } elseif ($mapping['association'] === ClassMetadata::EMBED_ONE) { |
|
321 | 44 | $code .= sprintf( |
|
322 | <<<EOF |
||
323 | |||
324 | /** @EmbedOne */ |
||
325 | if (isset(\$data['%1\$s'])) { |
||
326 | \$embeddedDocument = \$data['%1\$s']; |
||
327 | \$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$embeddedDocument); |
||
328 | \$embeddedMetadata = \$this->dm->getClassMetadata(\$className); |
||
329 | \$return = \$embeddedMetadata->newInstance(); |
||
330 | |||
331 | \$this->unitOfWork->setParentAssociation(\$return, \$this->class->fieldMappings['%2\$s'], \$document, '%1\$s'); |
||
332 | |||
333 | \$embeddedData = \$this->dm->getHydratorFactory()->hydrate(\$return, \$embeddedDocument, \$hints); |
||
334 | \$embeddedId = \$embeddedMetadata->identifier && isset(\$embeddedData[\$embeddedMetadata->identifier]) ? \$embeddedData[\$embeddedMetadata->identifier] : null; |
||
335 | |||
336 | if (empty(\$hints[Query::HINT_READ_ONLY])) { |
||
337 | \$this->unitOfWork->registerManaged(\$return, \$embeddedId, \$embeddedData); |
||
338 | } |
||
339 | |||
340 | \$this->class->reflFields['%2\$s']->setValue(\$document, \$return); |
||
341 | \$hydratedData['%2\$s'] = \$return; |
||
342 | } |
||
343 | |||
344 | EOF |
||
345 | , |
||
346 | 44 | $mapping['name'], |
|
347 | 44 | $mapping['fieldName'] |
|
348 | ); |
||
349 | } |
||
350 | } |
||
351 | |||
352 | 177 | $namespace = $this->hydratorNamespace; |
|
353 | 177 | $code = sprintf( |
|
354 | <<<EOF |
||
355 | <?php |
||
356 | |||
357 | 177 | namespace $namespace; |
|
358 | |||
359 | use Doctrine\ODM\MongoDB\DocumentManager; |
||
360 | use Doctrine\ODM\MongoDB\Hydrator\HydratorInterface; |
||
361 | use Doctrine\ODM\MongoDB\Query\Query; |
||
362 | use Doctrine\ODM\MongoDB\UnitOfWork; |
||
363 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
||
364 | |||
365 | /** |
||
366 | * THIS CLASS WAS GENERATED BY THE DOCTRINE ODM. DO NOT EDIT THIS FILE. |
||
367 | */ |
||
368 | 177 | class $hydratorClassName implements HydratorInterface |
|
369 | { |
||
370 | private \$dm; |
||
371 | private \$unitOfWork; |
||
372 | private \$class; |
||
373 | |||
374 | public function __construct(DocumentManager \$dm, UnitOfWork \$uow, ClassMetadata \$class) |
||
375 | { |
||
376 | \$this->dm = \$dm; |
||
377 | \$this->unitOfWork = \$uow; |
||
378 | \$this->class = \$class; |
||
379 | } |
||
380 | |||
381 | public function hydrate(object \$document, array \$data, array \$hints = array()): array |
||
382 | { |
||
383 | \$hydratedData = array(); |
||
384 | %s return \$hydratedData; |
||
385 | } |
||
386 | } |
||
387 | EOF |
||
388 | , |
||
389 | 177 | $code |
|
390 | ); |
||
391 | |||
392 | 177 | if ($fileName === null) { |
|
393 | if (! class_exists($namespace . '\\' . $hydratorClassName)) { |
||
394 | eval(substr($code, 5)); |
||
395 | } |
||
396 | |||
397 | return; |
||
398 | } |
||
399 | |||
400 | 177 | $parentDirectory = dirname($fileName); |
|
401 | |||
402 | 177 | if (! is_dir($parentDirectory) && (@mkdir($parentDirectory, 0775, true) === false)) { |
|
403 | throw HydratorException::hydratorDirectoryNotWritable(); |
||
404 | } |
||
405 | |||
406 | 177 | if (! is_writable($parentDirectory)) { |
|
407 | throw HydratorException::hydratorDirectoryNotWritable(); |
||
408 | } |
||
409 | |||
410 | 177 | $tmpFileName = $fileName . '.' . uniqid('', true); |
|
411 | 177 | file_put_contents($tmpFileName, $code); |
|
412 | 177 | rename($tmpFileName, $fileName); |
|
413 | 177 | chmod($fileName, 0664); |
|
414 | 177 | } |
|
415 | |||
416 | /** |
||
417 | * Hydrate array of MongoDB document data into the given document object. |
||
418 | * |
||
419 | * @param array $hints Any hints to account for during reconstitution/lookup of the document. |
||
420 | */ |
||
421 | 385 | public function hydrate(object $document, array $data, array $hints = []) : array |
|
422 | { |
||
423 | 385 | $metadata = $this->dm->getClassMetadata(get_class($document)); |
|
424 | // Invoke preLoad lifecycle events and listeners |
||
425 | 385 | if (! empty($metadata->lifecycleCallbacks[Events::preLoad])) { |
|
426 | 14 | $args = [new PreLoadEventArgs($document, $this->dm, $data)]; |
|
427 | 14 | $metadata->invokeLifecycleCallbacks(Events::preLoad, $document, $args); |
|
428 | } |
||
429 | 385 | if ($this->evm->hasListeners(Events::preLoad)) { |
|
430 | 3 | $this->evm->dispatchEvent(Events::preLoad, new PreLoadEventArgs($document, $this->dm, $data)); |
|
431 | } |
||
432 | |||
433 | // alsoLoadMethods may transform the document before hydration |
||
434 | 385 | if (! empty($metadata->alsoLoadMethods)) { |
|
435 | 12 | foreach ($metadata->alsoLoadMethods as $method => $fieldNames) { |
|
436 | 12 | foreach ($fieldNames as $fieldName) { |
|
437 | // Invoke the method only once for the first field we find |
||
438 | 12 | if (array_key_exists($fieldName, $data)) { |
|
439 | 8 | $document->$method($data[$fieldName]); |
|
440 | 8 | continue 2; |
|
441 | } |
||
442 | } |
||
443 | } |
||
444 | } |
||
445 | |||
446 | 385 | if ($document instanceof GhostObjectInterface) { |
|
0 ignored issues
–
show
|
|||
447 | 73 | $document->setProxyInitializer(null); |
|
448 | } |
||
449 | |||
450 | 385 | $data = $this->getHydratorFor($metadata->name)->hydrate($document, $data, $hints); |
|
451 | |||
452 | // Invoke the postLoad lifecycle callbacks and listeners |
||
453 | 385 | if (! empty($metadata->lifecycleCallbacks[Events::postLoad])) { |
|
454 | 13 | $metadata->invokeLifecycleCallbacks(Events::postLoad, $document, [new LifecycleEventArgs($document, $this->dm)]); |
|
455 | } |
||
456 | 385 | if ($this->evm->hasListeners(Events::postLoad)) { |
|
457 | 4 | $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($document, $this->dm)); |
|
458 | } |
||
459 | |||
460 | 385 | return $data; |
|
461 | } |
||
462 | } |
||
463 |
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.