Completed
Push — master ( ec6ad2...cf1149 )
by Maciej
12s
created

ProxyFactory::createCloner()   C

Complexity

Conditions 7
Paths 1

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 40.6659

Importance

Changes 0
Metric Value
dl 0
loc 34
ccs 2
cts 17
cp 0.1176
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 20
nc 1
nop 3
crap 40.6659
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Proxy;
6
7
use Doctrine\Common\EventManager;
8
use Doctrine\Common\NotifyPropertyChanged;
9
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
10
use Doctrine\Common\Proxy\AbstractProxyFactory;
11
use Doctrine\Common\Proxy\Proxy as BaseProxy;
12
use Doctrine\Common\Proxy\ProxyDefinition;
13
use Doctrine\Common\Proxy\ProxyGenerator;
14
use Doctrine\Common\Util\ClassUtils;
15
use Doctrine\ODM\MongoDB\DocumentManager;
16
use Doctrine\ODM\MongoDB\DocumentNotFoundException;
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 get_class;
23
24
/**
25
 * This factory is used to create proxy objects for documents at runtime.
26
 *
27
 */
28
class ProxyFactory extends AbstractProxyFactory
29
{
30
    /** @var ClassMetadataFactory */
31
    private $metadataFactory;
32
33
    /** @var UnitOfWork The UnitOfWork this factory is bound to. */
34
    private $uow;
35
36
    /** @var string The namespace that contains all proxy classes. */
37
    private $proxyNamespace;
38
39
    /** @var EventManager */
40
    private $lifecycleEventManager;
41
42
    /**
43
     * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
44
     * connected to the given <tt>DocumentManager</tt>.
45
     *
46
     * @param DocumentManager $documentManager The DocumentManager the new factory works for.
47
     * @param string          $proxyDir        The directory to use for the proxy classes. It
48
     *                                         must exist.
49
     * @param string          $proxyNamespace  The namespace to use for the proxy classes.
50
     * @param int             $autoGenerate    Whether to automatically generate proxy classes.
51
     */
52 1580
    public function __construct(DocumentManager $documentManager, $proxyDir, $proxyNamespace, $autoGenerate = AbstractProxyFactory::AUTOGENERATE_NEVER)
53
    {
54 1580
        $this->metadataFactory = $documentManager->getMetadataFactory();
55 1580
        $this->uow = $documentManager->getUnitOfWork();
56 1580
        $this->proxyNamespace = $proxyNamespace;
57 1580
        $this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager());
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\ODM\MongoD...ger->getEventManager()) of type object<Doctrine\ODM\Mong...\LifecycleEventManager> is incompatible with the declared type object<Doctrine\Common\EventManager> of property $lifecycleEventManager.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
58 1580
        $proxyGenerator = new ProxyGenerator($proxyDir, $proxyNamespace);
59
60 1580
        $proxyGenerator->setPlaceholder('baseProxyInterface', Proxy::class);
61
62 1580
        parent::__construct($proxyGenerator, $this->metadataFactory, $autoGenerate);
63 1580
    }
64
65
    /**
66
     * {@inheritDoc}
67
     */
68
    public function skipClass(BaseClassMetadata $class)
69
    {
70
        /* @var $class \Doctrine\ODM\Mongodb\Mapping\ClassMetadata */
71
        return $class->isMappedSuperclass || $class->isQueryResultDocument || $class->getReflectionClass()->isAbstract();
72
    }
73
74
    /**
75
     * {@inheritDoc}
76
     */
77 97
    public function createProxyDefinition($className)
78
    {
79
        /* @var $classMetadata \Doctrine\ODM\MongoDB\Mapping\ClassMetadata */
80 97
        $classMetadata     = $this->metadataFactory->getMetadataFor($className);
81 97
        $documentPersister = $this->uow->getDocumentPersister($className);
82 97
        $reflectionId      = $classMetadata->reflFields[$classMetadata->identifier];
83
84 97
        return new ProxyDefinition(
85 97
            ClassUtils::generateProxyClassName($className, $this->proxyNamespace),
86 97
            $classMetadata->getIdentifierFieldNames(),
87 97
            $classMetadata->getReflectionProperties(),
88 97
            $this->createInitializer($classMetadata, $documentPersister, $reflectionId),
89 97
            $this->createCloner($classMetadata, $documentPersister, $reflectionId)
90
        );
91
    }
92
93
    /**
94
     * Generates a closure capable of initializing a proxy
95
     *
96
     *
97
     * @return \Closure
98
     *
99
     * @throws DocumentNotFoundException
100
     */
101 97
    private function createInitializer(
102
        BaseClassMetadata $classMetadata,
103
        DocumentPersister $documentPersister,
104
        ReflectionProperty $reflectionId
105
    ) {
106 97
        if ($classMetadata->getReflectionClass()->hasMethod('__wakeup')) {
107 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...
108
                $proxy->__setInitializer(null);
109
                $proxy->__setCloner(null);
110
111
                if ($proxy->__isInitialized()) {
112
                    return;
113
                }
114
115
                $properties = $proxy->__getLazyProperties();
116
117
                foreach ($properties as $propertyName => $property) {
118
                    if (isset($proxy->$propertyName)) {
119
                        continue;
120
                    }
121
122
                    $proxy->$propertyName = $properties[$propertyName];
123
                }
124
125
                $proxy->__setInitialized(true);
126
                $proxy->__wakeup();
127
128
                $id = $reflectionId->getValue($proxy);
129
130
                if ($documentPersister->load(['_id' => $id], $proxy) === null) {
131
                    if (! $this->lifecycleEventManager->documentNotFound($proxy, $id)) {
132
                        throw DocumentNotFoundException::documentNotFound(get_class($proxy), $id);
133
                    }
134
                }
135
136
                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...
137
                    return;
138
                }
139
140
                $proxy->addPropertyChangedListener($this->uow);
141
            };
142
        }
143
144 97 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...
145 31
            $proxy->__setInitializer(null);
146 31
            $proxy->__setCloner(null);
147
148 31
            if ($proxy->__isInitialized()) {
149
                return;
150
            }
151
152 31
            $properties = $proxy->__getLazyProperties();
153
154 31
            foreach ($properties as $propertyName => $property) {
155 12
                if (isset($proxy->$propertyName)) {
156
                    continue;
157
                }
158
159 12
                $proxy->$propertyName = $properties[$propertyName];
160
            }
161
162 31
            $proxy->__setInitialized(true);
163
164 31
            $id = $reflectionId->getValue($proxy);
165
166 31
            if ($documentPersister->load(['_id' => $id], $proxy) === null) {
167 9
                if (! $this->lifecycleEventManager->documentNotFound($proxy, $id)) {
168 8
                    throw DocumentNotFoundException::documentNotFound(get_class($proxy), $id);
169
                }
170
            }
171
172 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...
173 24
                return;
174
            }
175
176 1
            $proxy->addPropertyChangedListener($this->uow);
177 97
        };
178
    }
179
180
    /**
181
     * Generates a closure capable of finalizing a cloned proxy
182
     *
183
     *
184
     * @return \Closure
185
     *
186
     * @throws DocumentNotFoundException
187
     */
188
    private function createCloner(
189
        BaseClassMetadata $classMetadata,
190
        DocumentPersister $documentPersister,
191
        ReflectionProperty $reflectionId
192
    ) {
193 97
        return function (BaseProxy $proxy) use ($documentPersister, $classMetadata, $reflectionId) {
194
            if ($proxy->__isInitialized()) {
195
                return;
196
            }
197
198
            $proxy->__setInitialized(true);
199
            $proxy->__setInitializer(null);
200
201
            $id       = $reflectionId->getValue($proxy);
202
            $original = $documentPersister->load(['_id' => $id]);
203
204
            if ($original === null) {
205
                if (! $this->lifecycleEventManager->documentNotFound($proxy, $id)) {
206
                    throw DocumentNotFoundException::documentNotFound(get_class($proxy), $id);
207
                }
208
            }
209
210
            foreach ($classMetadata->getReflectionClass()->getProperties() as $reflectionProperty) {
211
                $propertyName = $reflectionProperty->getName();
212
213
                if (! $classMetadata->hasField($propertyName) && ! $classMetadata->hasAssociation($propertyName)) {
214
                    continue;
215
                }
216
217
                $reflectionProperty->setAccessible(true);
218
                $reflectionProperty->setValue($proxy, $reflectionProperty->getValue($original));
219
            }
220 97
        };
221
    }
222
}
223