1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
|
4
|
|
|
declare(strict_types=1); |
5
|
|
|
|
6
|
|
|
namespace Doctrine\ORM\Proxy\Factory; |
7
|
|
|
|
8
|
|
|
use Doctrine\ORM\Configuration\ProxyConfiguration; |
9
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
10
|
|
|
use Doctrine\ORM\EntityNotFoundException; |
11
|
|
|
use Doctrine\ORM\Mapping\ClassMetadata; |
12
|
|
|
use Doctrine\ORM\Mapping\TransientMetadata; |
13
|
|
|
use Doctrine\ORM\Proxy\Proxy; |
14
|
|
|
use ProxyManager\Configuration; |
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
|
|
|
*/ |
27
|
|
|
class StaticProxyFactory implements ProxyFactory |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* @var EntityManagerInterface |
31
|
|
|
*/ |
32
|
|
|
protected $entityManager; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var ProxyGenerator |
36
|
|
|
*/ |
37
|
|
|
protected $generator; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var ProxyDefinitionFactory |
41
|
|
|
*/ |
42
|
|
|
protected $definitionFactory; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var array<string, ProxyDefinition> |
46
|
|
|
*/ |
47
|
|
|
private $definitions = []; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* ProxyFactory constructor. |
51
|
|
|
* |
52
|
|
|
* @param ProxyConfiguration $configuration |
53
|
|
|
*/ |
54
|
|
|
public function __construct(EntityManagerInterface $entityManager, ProxyConfiguration $configuration) |
55
|
|
|
{ |
56
|
|
|
$resolver = $configuration->getResolver(); |
57
|
|
|
//$autoGenerate = $configuration->getAutoGenerate(); |
|
|
|
|
58
|
|
|
$generator = new ProxyGenerator(); |
59
|
|
|
$generatorStrategy = new Strategy\ConditionalFileWriterProxyGeneratorStrategy($generator); |
60
|
|
|
$definitionFactory = new ProxyDefinitionFactory($entityManager, $resolver, $generatorStrategy); |
61
|
|
|
|
62
|
|
|
$generator->setPlaceholder('baseProxyInterface', GhostObjectInterface::class); |
63
|
|
|
|
64
|
|
|
$this->entityManager = $entityManager; |
65
|
|
|
$this->definitionFactory = $definitionFactory; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* {@inheritdoc} |
70
|
|
|
*/ |
71
|
|
|
public function generateProxyClasses(array $classMetadataList) : int |
72
|
|
|
{ |
73
|
|
|
$generated = 0; |
74
|
|
|
|
75
|
|
|
foreach ($classMetadataList as $classMetadata) { |
76
|
|
|
if ($classMetadata->isMappedSuperclass || $classMetadata->getReflectionClass()->isAbstract()) { |
77
|
|
|
continue; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
$this->definitionFactory->build($classMetadata); |
81
|
|
|
|
82
|
|
|
$generated++; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
return $generated; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* {@inheritdoc} |
90
|
|
|
* @throws \Doctrine\ORM\EntityNotFoundException |
91
|
|
|
*/ |
92
|
|
|
public function getProxy(string $className, array $identifier) : GhostObjectInterface |
93
|
|
|
{ |
94
|
|
|
$metadata = $this->entityManager->getClassMetadata($className); |
95
|
|
|
|
96
|
|
|
$proxyInstance = (new LazyLoadingGhostFactory(new Configuration())) |
97
|
|
|
->createProxy( |
98
|
|
|
$metadata->getClassName(), |
|
|
|
|
99
|
|
|
function ( |
100
|
|
|
GhostObjectInterface $ghostObject, |
101
|
|
|
string $method, // we don't care |
102
|
|
|
array $parameters, // we don't care |
103
|
|
|
& $initializer, |
104
|
|
|
array $properties |
|
|
|
|
105
|
|
|
) use ($metadata) : bool { |
106
|
|
|
$originalInitializer = $initializer; |
107
|
|
|
$initializer = null; |
108
|
|
|
|
109
|
|
|
$persister = $this->entityManager->getUnitOfWork()->getEntityPersister($metadata->getClassName()); |
|
|
|
|
110
|
|
|
|
111
|
|
|
$identifier = $persister->getIdentifier($ghostObject); |
112
|
|
|
|
113
|
|
|
// @TODO how do we use `$properties` in the persister? That would be a massive optimisation |
114
|
|
|
if (! $persister->loadById($identifier, $ghostObject)) { |
115
|
|
|
$initializer = $originalInitializer; |
116
|
|
|
|
117
|
|
|
throw EntityNotFoundException::fromClassNameAndIdentifier( |
118
|
|
|
$metadata->getClassName(), |
|
|
|
|
119
|
|
|
$identifier |
120
|
|
|
); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
return true; |
124
|
|
|
}, |
125
|
|
|
[ |
126
|
|
|
// @TODO this should be a constant reference, not a magic constant |
127
|
|
|
'skippedProperties' => \array_merge( |
128
|
|
|
$this->identifierFieldFqns($metadata), |
|
|
|
|
129
|
|
|
$this->transientFieldsFqns($metadata) |
|
|
|
|
130
|
|
|
), |
131
|
|
|
] |
132
|
|
|
); |
133
|
|
|
|
134
|
|
|
$this->transientFieldsFqns($metadata); |
|
|
|
|
135
|
|
|
$proxyDefinition = $this->getOrCreateProxyDefinition($className); |
136
|
|
|
$proxyPersister = $proxyDefinition->entityPersister; |
137
|
|
|
|
138
|
|
|
$proxyPersister->setIdentifier($proxyInstance, $identifier); |
139
|
|
|
|
140
|
|
|
return $proxyInstance; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
View Code Duplication |
private function transientFieldsFqns(ClassMetadata $metadata) : array |
144
|
|
|
{ |
145
|
|
|
$transientFieldsFqns = []; |
146
|
|
|
|
147
|
|
|
foreach ($metadata->getDeclaredPropertiesIterator() as $name => $property) { |
148
|
|
|
if (! $property instanceof TransientMetadata) { |
149
|
|
|
continue; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
$transientFieldsFqns[] = $this->propertyFqcn( |
153
|
|
|
$property |
154
|
|
|
->getDeclaringClass() |
155
|
|
|
->getReflectionClass() |
156
|
|
|
->getProperty($name) // @TODO possible NPR. This should never be null, why is it allowed to be? |
157
|
|
|
); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return $transientFieldsFqns; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
View Code Duplication |
private function identifierFieldFqns(ClassMetadata $metadata) : array |
164
|
|
|
{ |
165
|
|
|
$idFieldFqcns = []; |
166
|
|
|
|
167
|
|
|
foreach ($metadata->getIdentifierFieldNames() as $idField) { |
168
|
|
|
$property = $metadata->getProperty($idField); |
169
|
|
|
|
170
|
|
|
$idFieldFqcns[] = $this->propertyFqcn( |
171
|
|
|
$property |
172
|
|
|
->getDeclaringClass() |
173
|
|
|
->getReflectionClass() |
174
|
|
|
->getProperty($idField) // @TODO possible NPR. This should never be null, why is it allowed to be? |
175
|
|
|
); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return $idFieldFqcns; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
private function propertyFqcn(\ReflectionProperty $property) : string |
182
|
|
|
{ |
183
|
|
|
if ($property->isPrivate()) { |
184
|
|
|
return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName(); |
|
|
|
|
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
if ($property->isProtected()) { |
188
|
|
|
return "\0*\0" . $property->getName(); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
return $property->getName(); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param ProxyDefinition $definition |
196
|
|
|
* |
197
|
|
|
* @return Proxy |
198
|
|
|
*/ |
199
|
|
|
protected function createProxyInstance(ProxyDefinition $definition) : Proxy |
200
|
|
|
{ |
201
|
|
|
/** @var Proxy $classMetadata */ |
202
|
|
|
$proxyClassName = $definition->proxyClassName; |
203
|
|
|
|
204
|
|
|
return new $proxyClassName($definition); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Create a proxy definition for the given class name. |
209
|
|
|
* |
210
|
|
|
* @param string $className |
211
|
|
|
* |
212
|
|
|
* @return ProxyDefinition |
213
|
|
|
*/ |
214
|
|
|
private function getOrCreateProxyDefinition(string $className) : ProxyDefinition |
215
|
|
|
{ |
216
|
|
|
if (! isset($this->definitions[$className])) { |
217
|
|
|
$classMetadata = $this->entityManager->getClassMetadata($className); |
218
|
|
|
|
219
|
|
|
$this->definitions[$className] = $this->definitionFactory->build($classMetadata); |
|
|
|
|
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
return $this->definitions[$className]; |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.