Failed Conditions
Push — develop ( 7f14a9...67d800 )
by Marco
142:57 queued 77:59
created

StaticProxyFactory::getProxy()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 18
nc 1
nop 2
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\TransientMetadata;
12
use Doctrine\ORM\Persisters\Entity\EntityPersister;
13
use ProxyManager\Factory\LazyLoadingGhostFactory;
14
use ProxyManager\Proxy\GhostObjectInterface;
15
16
/**
17
 * Static factory for proxy objects.
18
 *
19
 * @package Doctrine\ORM\Proxy\Factory
20
 * @since 3.0
21
 *
22
 * @author Benjamin Eberlei <[email protected]>
23
 * @author Guilherme Blanco <[email protected]>
24
 * @author Marco Pivetta <[email protected]>
25
 *
26
 * @internal this class is to be used by ORM internals only
27
 */
28
final class StaticProxyFactory implements ProxyFactory
29
{
30
    private const SKIPPED_PROPERTIES = 'skippedProperties';
31
32
    /**
33
     * @var EntityManagerInterface
34
     */
35
    private $entityManager;
36
37
    /**
38
     * @var LazyLoadingGhostFactory
39
     */
40
    private $proxyFactory;
41
42
    /**
43
     * @var \Closure[] indexed by metadata class name
44
     */
45
    private $cachedInitializers = [];
46
47
    /**
48
     * @var EntityPersister[] indexed by metadata class name
49
     */
50
    private $cachedPersisters = [];
51
52
    /**
53
     * @var string[][] indexed by metadata class name
54
     */
55
    private $cachedSkippedProperties = [];
56
57
    public function __construct(
58
        EntityManagerInterface $entityManager,
59
        LazyLoadingGhostFactory $proxyFactory
60
    ) {
61
        $this->entityManager     = $entityManager;
62
        $this->proxyFactory      = $proxyFactory;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     *
68
     * @param ClassMetadata[] $classes
69
     */
70
    public function generateProxyClasses(array $classes) : int
71
    {
72
        $concreteClasses = \array_filter($classes, function (ClassMetadata $metadata) : bool {
73
            return ! ($metadata->isMappedSuperclass || $metadata->getReflectionClass()->isAbstract());
74
        });
75
76
        foreach ($concreteClasses as $metadata) {
77
            $this
78
                ->proxyFactory
79
                ->createProxy(
80
                    $metadata->getClassName(),
81
                    function () {
82
                        // empty closure, serves its purpose, for now
83
                    },
84
                    $this->skippedFieldsFqns($metadata)
85
                );
86
        }
87
88
        return \count($concreteClasses);
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     *
94
     * @throws \Doctrine\ORM\EntityNotFoundException
95
     */
96
    public function getProxy(ClassMetadata $metadata, array $identifier) : GhostObjectInterface
97
    {
98
        $className = $metadata->getClassName();
99
        $persister = $this->cachedPersisters[$className]
100
            ?? $this->cachedPersisters[$className] = $this
101
                ->entityManager
102
                ->getUnitOfWork()
103
                ->getEntityPersister($metadata->getClassName());
104
105
        $proxyInstance = $this
106
            ->proxyFactory
107
            ->createProxy(
108
                $metadata->getClassName(),
109
                $this->cachedInitializers[$className]
110
                    ?? $this->cachedInitializers[$className] = $this->makeInitializer($metadata, $persister),
111
                $this->cachedSkippedProperties[$className]
112
                    ?? $this->cachedSkippedProperties[$className] = [
113
                        self::SKIPPED_PROPERTIES => $this->skippedFieldsFqns($metadata)
114
                    ]
115
            );
116
117
        $persister->setIdentifier($proxyInstance, $identifier);
118
119
        return $proxyInstance;
120
    }
121
122
    private function makeInitializer(ClassMetadata $metadata, EntityPersister $persister) : \Closure
123
    {
124
        return function (
125
            GhostObjectInterface $ghostObject,
126
            string $method, // we don't care
127
            array $parameters, // we don't care
128
            & $initializer,
129
            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...
130
        ) use ($metadata, $persister) : bool {
131
            $originalInitializer = $initializer;
132
            $initializer = null;
133
134
            $identifier = $persister->getIdentifier($ghostObject);
135
136
            // @TODO how do we use `$properties` in the persister? That would be a massive optimisation
137
            if (! $persister->loadById($identifier, $ghostObject)) {
138
                $initializer = $originalInitializer;
139
140
                throw EntityNotFoundException::fromClassNameAndIdentifier(
141
                    $metadata->getClassName(),
142
                    $identifier
143
                );
144
            }
145
146
            return true;
147
        };
148
    }
149
150
    private function skippedFieldsFqns(ClassMetadata $metadata) : array
151
    {
152
        return \array_merge(
153
            $this->identifierFieldFqns($metadata),
154
            $this->transientFieldsFqns($metadata)
155
        );
156
    }
157
158
    private function transientFieldsFqns(ClassMetadata $metadata) : array
159
    {
160
        $transientFieldsFqns = [];
161
162
        foreach ($metadata->getDeclaredPropertiesIterator() as $name => $property) {
163
            if (! $property instanceof TransientMetadata) {
164
                continue;
165
            }
166
167
            $transientFieldsFqns[] = $this->propertyFqcn(
168
                $property
169
                    ->getDeclaringClass()
170
                    ->getReflectionClass()
171
                    ->getProperty($name) // @TODO possible NPR. This should never be null, why is it allowed to be?
172
            );
173
        }
174
175
        return $transientFieldsFqns;
176
    }
177
178
    private function identifierFieldFqns(ClassMetadata $metadata) : array
179
    {
180
        $idFieldFqcns = [];
181
182
        foreach ($metadata->getIdentifierFieldNames() as $idField) {
183
            $idFieldFqcns[] = $this->propertyFqcn(
184
                $metadata
185
                    ->getProperty($idField)
186
                    ->getDeclaringClass()
187
                    ->getReflectionClass()
188
                    ->getProperty($idField) // @TODO possible NPR. This should never be null, why is it allowed to be?
189
            );
190
        }
191
192
        return $idFieldFqcns;
193
    }
194
195
    private function propertyFqcn(\ReflectionProperty $property) : string
196
    {
197
        if ($property->isPrivate()) {
198
            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...
199
        }
200
201
        if ($property->isProtected()) {
202
            return "\0*\0" . $property->getName();
203
        }
204
205
        return $property->getName();
206
    }
207
}
208