Completed
Push — master ( b42750...9f1562 )
by Andreas
22:10 queued 12s
created

HydratorFactory::hydrate()   C

Complexity

Conditions 11
Paths 64

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 52
ccs 21
cts 21
cp 1
rs 6.9006
c 0
b 0
f 0
cc 11
nc 64
nop 3
crap 11

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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 the composer.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 or require-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 ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
469 73
            // Inject an empty initialiser to not load any object data
470
            $document->setProxyInitializer(static function (
471
                GhostObjectInterface $ghostObject,
472 394
                string $method, // we don't care
473
                array $parameters, // we don't care
474
                &$initializer,
475 390
                array $properties // we currently do not use this
0 ignored issues
show
Unused Code introduced by
The parameter $properties is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
476 13
            ) : bool {
477
                $initializer = null;
478 390
479 4
                return true;
480
            });
481
        }
482 390
483
        $data = $this->getHydratorFor($metadata->name)->hydrate($document, $data, $hints);
484
485
        // Invoke the postLoad lifecycle callbacks and listeners
486
        if (! empty($metadata->lifecycleCallbacks[Events::postLoad])) {
487
            $metadata->invokeLifecycleCallbacks(Events::postLoad, $document, [new LifecycleEventArgs($document, $this->dm)]);
488
        }
489
        if ($this->evm->hasListeners(Events::postLoad)) {
490
            $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($document, $this->dm));
491
        }
492
493
        return $data;
494
    }
495
}
496