Completed
Pull Request — master (#1803)
by Maciej
15:26 queued 06:04
created

ProxyFactory::createProxyDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 10
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Proxy;
6
7
use Doctrine\Common\NotifyPropertyChanged;
8
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
9
use Doctrine\Common\Proxy\AbstractProxyFactory;
10
use Doctrine\Common\Proxy\Proxy as BaseProxy;
11
use Doctrine\Common\Proxy\ProxyDefinition;
12
use Doctrine\Common\Proxy\ProxyGenerator;
13
use Doctrine\Common\Util\ClassUtils;
14
use Doctrine\ODM\MongoDB\DocumentManager;
15
use Doctrine\ODM\MongoDB\DocumentNotFoundException;
16
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
17
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
18
use Doctrine\ODM\MongoDB\Persisters\DocumentPersister;
19
use Doctrine\ODM\MongoDB\UnitOfWork;
20
use Doctrine\ODM\MongoDB\Utility\LifecycleEventManager;
21
use ReflectionProperty;
22
use function assert;
23
use function get_class;
24
25
/**
26
 * This factory is used to create proxy objects for documents at runtime.
27
 *
28
 */
29
class ProxyFactory extends AbstractProxyFactory
30
{
31
    /** @var ClassMetadataFactory */
32
    private $metadataFactory;
33
34
    /** @var UnitOfWork The UnitOfWork this factory is bound to. */
35
    private $uow;
36
37
    /** @var string The namespace that contains all proxy classes. */
38
    private $proxyNamespace;
39
40
    /** @var LifecycleEventManager */
41
    private $lifecycleEventManager;
42
43
    /**
44
     * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
45
     * connected to the given <tt>DocumentManager</tt>.
46
     *
47
     * @param DocumentManager $documentManager The DocumentManager the new factory works for.
48
     * @param string          $proxyDir        The directory to use for the proxy classes. It
49
     *                                         must exist.
50
     * @param string          $proxyNamespace  The namespace to use for the proxy classes.
51
     * @param int             $autoGenerate    Whether to automatically generate proxy classes.
52
     */
53 1584
    public function __construct(DocumentManager $documentManager, $proxyDir, $proxyNamespace, $autoGenerate = AbstractProxyFactory::AUTOGENERATE_NEVER)
54
    {
55 1584
        $this->metadataFactory = $documentManager->getMetadataFactory();
56 1584
        $this->uow = $documentManager->getUnitOfWork();
57 1584
        $this->proxyNamespace = $proxyNamespace;
58 1584
        $this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager());
59 1584
        $proxyGenerator = new ProxyGenerator($proxyDir, $proxyNamespace);
60
61 1584
        $proxyGenerator->setPlaceholder('baseProxyInterface', Proxy::class);
62
63 1584
        parent::__construct($proxyGenerator, $this->metadataFactory, $autoGenerate);
64 1584
    }
65
66
    /**
67
     * {@inheritDoc}
68
     */
69
    public function skipClass(BaseClassMetadata $class)
70
    {
71
        assert($class instanceof ClassMetadata);
72
        return $class->isMappedSuperclass || $class->isQueryResultDocument || $class->getReflectionClass()->isAbstract();
73
    }
74
75
    /**
76
     * {@inheritDoc}
77
     */
78 97
    public function createProxyDefinition($className)
79
    {
80
        /** @var ClassMetadata $classMetadata */
81 97
        $classMetadata     = $this->metadataFactory->getMetadataFor($className);
82 97
        $documentPersister = $this->uow->getDocumentPersister($className);
83 97
        $reflectionId      = $classMetadata->reflFields[$classMetadata->identifier];
84
85 97
        return new ProxyDefinition(
86 97
            ClassUtils::generateProxyClassName($className, $this->proxyNamespace),
87 97
            $classMetadata->getIdentifierFieldNames(),
88 97
            $classMetadata->getReflectionProperties(),
89 97
            $this->createInitializer($classMetadata, $documentPersister, $reflectionId),
90 97
            $this->createCloner($classMetadata, $documentPersister, $reflectionId)
91
        );
92
    }
93
94
    /**
95
     * Generates a closure capable of initializing a proxy
96
     *
97
     *
98
     * @return \Closure
99
     *
100
     * @throws DocumentNotFoundException
101
     */
102 97
    private function createInitializer(
103
        BaseClassMetadata $classMetadata,
104
        DocumentPersister $documentPersister,
105
        ReflectionProperty $reflectionId
106
    ) {
107 97
        if ($classMetadata->getReflectionClass()->hasMethod('__wakeup')) {
108 View Code Duplication
            return function (BaseProxy $proxy) use ($documentPersister, $reflectionId) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
109
                $proxy->__setInitializer(null);
110
                $proxy->__setCloner(null);
111
112
                if ($proxy->__isInitialized()) {
113
                    return;
114
                }
115
116
                $properties = $proxy->__getLazyProperties();
117
118
                foreach ($properties as $propertyName => $property) {
119
                    if (isset($proxy->$propertyName)) {
120
                        continue;
121
                    }
122
123
                    $proxy->$propertyName = $properties[$propertyName];
124
                }
125
126
                $proxy->__setInitialized(true);
127
                $proxy->__wakeup();
128
129
                $id = $reflectionId->getValue($proxy);
130
131
                if ($documentPersister->load(['_id' => $id], $proxy) === null) {
132
                    if (! $this->lifecycleEventManager->documentNotFound($proxy, $id)) {
133
                        throw DocumentNotFoundException::documentNotFound(get_class($proxy), $id);
134
                    }
135
                }
136
137
                if (! ($proxy instanceof NotifyPropertyChanged)) {
0 ignored issues
show
Bug introduced by
The class Doctrine\Common\NotifyPropertyChanged 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...
138
                    return;
139
                }
140
141
                $proxy->addPropertyChangedListener($this->uow);
142
            };
143
        }
144
145 View Code Duplication
        return function (BaseProxy $proxy) use ($documentPersister, $reflectionId) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
146 31
            $proxy->__setInitializer(null);
147 31
            $proxy->__setCloner(null);
148
149 31
            if ($proxy->__isInitialized()) {
150
                return;
151
            }
152
153 31
            $properties = $proxy->__getLazyProperties();
154
155 31
            foreach ($properties as $propertyName => $property) {
156 12
                if (isset($proxy->$propertyName)) {
157
                    continue;
158
                }
159
160 12
                $proxy->$propertyName = $properties[$propertyName];
161
            }
162
163 31
            $proxy->__setInitialized(true);
164
165 31
            $id = $reflectionId->getValue($proxy);
166
167 31
            if ($documentPersister->load(['_id' => $id], $proxy) === null) {
168 9
                if (! $this->lifecycleEventManager->documentNotFound($proxy, $id)) {
169 8
                    throw DocumentNotFoundException::documentNotFound(get_class($proxy), $id);
170
                }
171
            }
172
173 25
            if (! ($proxy instanceof NotifyPropertyChanged)) {
0 ignored issues
show
Bug introduced by
The class Doctrine\Common\NotifyPropertyChanged 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...
174 24
                return;
175
            }
176
177 1
            $proxy->addPropertyChangedListener($this->uow);
178 97
        };
179
    }
180
181
    /**
182
     * Generates a closure capable of finalizing a cloned proxy
183
     *
184
     *
185
     * @return \Closure
186
     *
187
     * @throws DocumentNotFoundException
188
     */
189 97
    private function createCloner(
190
        BaseClassMetadata $classMetadata,
191
        DocumentPersister $documentPersister,
192
        ReflectionProperty $reflectionId
193
    ) {
194
        return function (BaseProxy $proxy) use ($documentPersister, $classMetadata, $reflectionId) {
195
            if ($proxy->__isInitialized()) {
196
                return;
197
            }
198
199
            $proxy->__setInitialized(true);
200
            $proxy->__setInitializer(null);
201
202
            $id       = $reflectionId->getValue($proxy);
203
            $original = $documentPersister->load(['_id' => $id]);
204
205
            if ($original === null) {
206
                if (! $this->lifecycleEventManager->documentNotFound($proxy, $id)) {
207
                    throw DocumentNotFoundException::documentNotFound(get_class($proxy), $id);
208
                }
209
            }
210
211
            foreach ($classMetadata->getReflectionClass()->getProperties() as $reflectionProperty) {
212
                $propertyName = $reflectionProperty->getName();
213
214
                if (! $classMetadata->hasField($propertyName) && ! $classMetadata->hasAssociation($propertyName)) {
215
                    continue;
216
                }
217
218
                $reflectionProperty->setAccessible(true);
219
                $reflectionProperty->setValue($proxy, $reflectionProperty->getValue($original));
220
            }
221 97
        };
222
    }
223
}
224