Completed
Push — master ( 87aba0...0e8729 )
by Andreas
25s queued 16s
created

Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
464 69
            $document->__setInitializer(null);
465 69
            $document->__setCloner(null);
466
            // lazy properties may be left uninitialized
467 69
            $properties = $document->__getLazyProperties();
468 69
            foreach ($properties as $propertyName => $property) {
469 27
                if ( ! isset($document->$propertyName)) {
470 10
                    $document->$propertyName = $properties[$propertyName];
471
                }
472
            }
473
        }
474
475
        // Invoke the postLoad lifecycle callbacks and listeners
476 407 View Code Duplication
        if ( ! empty($metadata->lifecycleCallbacks[Events::postLoad])) {
477 13
            $metadata->invokeLifecycleCallbacks(Events::postLoad, $document, array(new LifecycleEventArgs($document, $this->dm)));
478
        }
479 407 View Code Duplication
        if ($this->evm->hasListeners(Events::postLoad)) {
480 4
            $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($document, $this->dm));
481
        }
482
483 407
        return $data;
484
    }
485
}
486