Failed Conditions
Pull Request — develop (#6724)
by Marco
122:37 queued 57:31
created

StaticProxyFactory   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 10
dl 0
loc 237
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A generateProxyClasses() 0 20 3
B getProxy() 0 36 2
B makeInitializer() 0 27 2
A skippedFieldsFqns() 0 8 1
A transientFieldsFqns() 0 19 3
A collectionFieldsFqns() 0 19 3
A toManyFields() 0 14 3
A identifierFieldFqns() 0 16 2
A propertyFqcn() 0 12 3
1
<?php
2
3
4
declare(strict_types=1);
5
6
namespace Doctrine\ORM\Proxy\Factory;
7
8
use Doctrine\ORM\EntityManagerInterface;
9
use Doctrine\ORM\EntityNotFoundException;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
use Doctrine\ORM\Mapping\Property;
12
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
13
use Doctrine\ORM\Mapping\TransientMetadata;
14
use Doctrine\ORM\Persisters\Entity\EntityPersister;
15
use ProxyManager\Factory\LazyLoadingGhostFactory;
16
use ProxyManager\Proxy\GhostObjectInterface;
17
18
/**
19
 * Static factory for proxy objects.
20
 *
21
 * @package Doctrine\ORM\Proxy\Factory
22
 * @since 3.0
23
 *
24
 * @author Benjamin Eberlei <[email protected]>
25
 * @author Guilherme Blanco <[email protected]>
26
 * @author Marco Pivetta <[email protected]>
27
 *
28
 * @internal this class is to be used by ORM internals only
29
 */
30
final class StaticProxyFactory implements ProxyFactory
31
{
32
    private const SKIPPED_PROPERTIES = 'skippedProperties';
33
34
    /**
35
     * @var EntityManagerInterface
36
     */
37
    private $entityManager;
38
39
    /**
40
     * @var LazyLoadingGhostFactory
41
     */
42
    private $proxyFactory;
43
44
    /**
45
     * @var \Closure[] indexed by metadata class name
46
     */
47
    private $cachedInitializers = [];
48
49
    /**
50
     * @var EntityPersister[] indexed by metadata class name
51
     */
52
    private $cachedPersisters = [];
53
54
    /**
55
     * @var string[][] indexed by metadata class name
56
     */
57
    private $cachedSkippedProperties = [];
58
59
    /**
60
     * @var ToManyAssociationMetadata[][] indexed by metadata class name
61
     */
62
    private $cachedToManyFields = [];
63
64
    public function __construct(
65
        EntityManagerInterface $entityManager,
66
        LazyLoadingGhostFactory $proxyFactory
67
    ) {
68
        $this->entityManager     = $entityManager;
69
        $this->proxyFactory      = $proxyFactory;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     *
75
     * @param ClassMetadata[] $classes
76
     */
77
    public function generateProxyClasses(array $classes) : int
78
    {
79
        $concreteClasses = \array_filter($classes, function (ClassMetadata $metadata) : bool {
80
            return ! ($metadata->isMappedSuperclass || $metadata->getReflectionClass()->isAbstract());
81
        });
82
83
        foreach ($concreteClasses as $metadata) {
84
            $this
85
                ->proxyFactory
86
                ->createProxy(
87
                    $metadata->getClassName(),
88
                    function () {
89
                        // empty closure, serves its purpose, for now
90
                    },
91
                    $this->skippedFieldsFqns($metadata)
92
                );
93
        }
94
95
        return \count($concreteClasses);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     *
101
     * @throws \Doctrine\ORM\EntityNotFoundException
102
     *
103
     * Note: do not normalize this method any further, as it is performance-sensitive
104
     */
105
    public function getProxy(ClassMetadata $metadata, array $identifier) : GhostObjectInterface
106
    {
107
        $className = $metadata->getClassName();
108
        $persister = $this->cachedPersisters[$className]
109
            ?? $this->cachedPersisters[$className] = $this
110
                ->entityManager
111
                ->getUnitOfWork()
112
                ->getEntityPersister($metadata->getClassName());
113
        $toManyFields = $this->cachedToManyFields[$className]
114
            ?? $this->cachedToManyFields[$className] = $this->toManyFields($metadata);
115
116
        $proxyInstance = $this
117
            ->proxyFactory
118
            ->createProxy(
119
                $metadata->getClassName(),
120
                $this->cachedInitializers[$className]
121
                    ?? $this->cachedInitializers[$className] = $this->makeInitializer($metadata, $persister),
122
                $this->cachedSkippedProperties[$className]
123
                    ?? $this->cachedSkippedProperties[$className] = [
124
                        self::SKIPPED_PROPERTIES => $this->skippedFieldsFqns($metadata)
125
                    ]
126
            );
127
128
        $persister->setIdentifier($proxyInstance, $identifier);
129
130
        foreach ($toManyFields as $toMany) {
131
            $collection = $toMany->wrap($proxyInstance, null, $this->entityManager);
132
133
            $collection->setInitialized(false); // @TODO not the right approach, IMO
134
            $collection->setDirty(false); // @TODO not the right approach, IMO
135
136
            $toMany->setValue($proxyInstance, $collection);
137
        }
138
139
        return $proxyInstance;
140
    }
141
142
    private function makeInitializer(ClassMetadata $metadata, EntityPersister $persister) : \Closure
143
    {
144
        return function (
145
            GhostObjectInterface $ghostObject,
146
            string $method, // we don't care
147
            array $parameters, // we don't care
148
            & $initializer,
149
            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...
150
        ) use ($metadata, $persister) : bool {
151
            $originalInitializer = $initializer;
152
            $initializer = null;
153
154
            $identifier = $persister->getIdentifier($ghostObject);
155
156
            // @TODO how do we use `$properties` in the persister? That would be a massive optimisation
157
            if (! $persister->loadById($identifier, $ghostObject)) {
158
                $initializer = $originalInitializer;
159
160
                throw EntityNotFoundException::fromClassNameAndIdentifier(
161
                    $metadata->getClassName(),
162
                    $identifier
163
                );
164
            }
165
166
            return true;
167
        };
168
    }
169
170
    private function skippedFieldsFqns(ClassMetadata $metadata) : array
171
    {
172
        return \array_merge(
173
            $this->identifierFieldFqns($metadata),
174
            $this->collectionFieldsFqns($metadata),
175
            $this->transientFieldsFqns($metadata)
176
        );
177
    }
178
179
    private function transientFieldsFqns(ClassMetadata $metadata) : array
180
    {
181
        $transientFieldsFqns = [];
182
183
        foreach ($metadata->getDeclaredPropertiesIterator() as $name => $property) {
184
            if (! $property instanceof TransientMetadata) {
185
                continue;
186
            }
187
188
            $transientFieldsFqns[] = $this->propertyFqcn(
189
                $property
190
                    ->getDeclaringClass()
191
                    ->getReflectionClass()
192
                    ->getProperty($name) // @TODO possible NPE. This should never be null, why is it allowed to be?
193
            );
194
        }
195
196
        return $transientFieldsFqns;
197
    }
198
199
    private function collectionFieldsFqns(ClassMetadata $metadata) : array
200
    {
201
        $transientFieldsFqns = [];
202
203
        foreach ($metadata->getDeclaredPropertiesIterator() as $name => $property) {
204
            if (! $property instanceof ToManyAssociationMetadata) {
205
                continue;
206
            }
207
208
            $transientFieldsFqns[] = $this->propertyFqcn(
209
                $property
210
                    ->getDeclaringClass()
211
                    ->getReflectionClass()
212
                    ->getProperty($name) // @TODO possible NPR. This should never be null, why is it allowed to be?
213
            );
214
        }
215
216
        return $transientFieldsFqns;
217
    }
218
219
    /**
220
     * @return ToManyAssociationMetadata[]
221
     */
222
    private function toManyFields(ClassMetadata $metadata) : array
223
    {
224
        $toMany = [];
225
226
        foreach ($metadata->getDeclaredPropertiesIterator() as $property) {
227
            if (! $property instanceof ToManyAssociationMetadata) {
228
                continue;
229
            }
230
231
            $toMany[] = $property;
232
        }
233
234
        return $toMany;
235
    }
236
237
    private function identifierFieldFqns(ClassMetadata $metadata) : array
238
    {
239
        $idFieldFqcns = [];
240
241
        foreach ($metadata->getIdentifierFieldNames() as $idField) {
242
            $idFieldFqcns[] = $this->propertyFqcn(
243
                $metadata
244
                    ->getProperty($idField)
245
                    ->getDeclaringClass()
246
                    ->getReflectionClass()
247
                    ->getProperty($idField) // @TODO possible NPE. This should never be null, why is it allowed to be?
248
            );
249
        }
250
251
        return $idFieldFqcns;
252
    }
253
254
    private function propertyFqcn(\ReflectionProperty $property) : string
255
    {
256
        if ($property->isPrivate()) {
257
            return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName();
0 ignored issues
show
introduced by
Consider using $property->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
258
        }
259
260
        if ($property->isProtected()) {
261
            return "\0*\0" . $property->getName();
262
        }
263
264
        return $property->getName();
265
    }
266
}
267