These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Doctrine\ODM\MongoDB\PersistentCollection; |
||
6 | |||
7 | use Doctrine\ODM\MongoDB\Configuration; |
||
8 | use ReflectionClass; |
||
9 | use ReflectionException; |
||
10 | use ReflectionMethod; |
||
11 | use ReflectionParameter; |
||
12 | use ReflectionType; |
||
13 | use const DIRECTORY_SEPARATOR; |
||
14 | use function array_map; |
||
15 | use function array_pop; |
||
16 | use function class_exists; |
||
17 | use function dirname; |
||
18 | use function explode; |
||
19 | use function file_exists; |
||
20 | use function file_put_contents; |
||
21 | use function implode; |
||
22 | use function interface_exists; |
||
23 | use function is_dir; |
||
24 | use function is_writable; |
||
25 | use function method_exists; |
||
26 | use function mkdir; |
||
27 | use function rename; |
||
28 | use function str_replace; |
||
29 | use function strtolower; |
||
30 | use function substr; |
||
31 | use function uniqid; |
||
32 | use function var_export; |
||
33 | |||
34 | /** |
||
35 | * Default generator for custom PersistentCollection classes. |
||
36 | */ |
||
37 | final class DefaultPersistentCollectionGenerator implements PersistentCollectionGenerator |
||
38 | { |
||
39 | /** |
||
40 | * The namespace that contains all persistent collection classes. |
||
41 | * |
||
42 | * @var string |
||
43 | */ |
||
44 | private $collectionNamespace; |
||
45 | |||
46 | /** |
||
47 | * The directory that contains all persistent collection classes. |
||
48 | * |
||
49 | * @var string |
||
50 | */ |
||
51 | private $collectionDir; |
||
52 | |||
53 | 11 | public function __construct(string $collectionDir, string $collectionNs) |
|
54 | { |
||
55 | 11 | $this->collectionDir = $collectionDir; |
|
56 | 11 | $this->collectionNamespace = $collectionNs; |
|
57 | 11 | } |
|
58 | |||
59 | /** |
||
60 | * {@inheritdoc} |
||
61 | */ |
||
62 | public function generateClass(string $class, string $dir) : void |
||
63 | { |
||
64 | $collClassName = str_replace('\\', '', $class) . 'Persistent'; |
||
65 | $className = $this->collectionNamespace . '\\' . $collClassName; |
||
66 | $fileName = $dir . DIRECTORY_SEPARATOR . $collClassName . '.php'; |
||
67 | $this->generateCollectionClass($class, $className, $fileName); |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * {@inheritdoc} |
||
72 | */ |
||
73 | 10 | public function loadClass(string $collectionClass, int $autoGenerate) : string |
|
74 | { |
||
75 | // These checks are not in __construct() because of BC and should be moved for 2.0 |
||
76 | 10 | if (! $this->collectionDir) { |
|
77 | throw PersistentCollectionException::directoryRequired(); |
||
78 | } |
||
79 | 10 | if (! $this->collectionNamespace) { |
|
80 | throw PersistentCollectionException::namespaceRequired(); |
||
81 | } |
||
82 | |||
83 | 10 | $collClassName = str_replace('\\', '', $collectionClass) . 'Persistent'; |
|
84 | 10 | $className = $this->collectionNamespace . '\\' . $collClassName; |
|
85 | 10 | if (! class_exists($className, false)) { |
|
86 | 6 | $fileName = $this->collectionDir . DIRECTORY_SEPARATOR . $collClassName . '.php'; |
|
87 | 6 | switch ($autoGenerate) { |
|
88 | case Configuration::AUTOGENERATE_NEVER: |
||
89 | require $fileName; |
||
90 | break; |
||
91 | |||
92 | case Configuration::AUTOGENERATE_ALWAYS: |
||
93 | 3 | $this->generateCollectionClass($collectionClass, $className, $fileName); |
|
94 | 3 | require $fileName; |
|
95 | 3 | break; |
|
96 | |||
97 | case Configuration::AUTOGENERATE_FILE_NOT_EXISTS: |
||
98 | if (! file_exists($fileName)) { |
||
99 | $this->generateCollectionClass($collectionClass, $className, $fileName); |
||
100 | } |
||
101 | require $fileName; |
||
102 | break; |
||
103 | |||
104 | case Configuration::AUTOGENERATE_EVAL: |
||
105 | 3 | $this->generateCollectionClass($collectionClass, $className, false); |
|
106 | 3 | break; |
|
107 | } |
||
108 | } |
||
109 | |||
110 | 10 | return $className; |
|
111 | } |
||
112 | |||
113 | /** |
||
114 | * @param string|false $fileName Filename to write collection class code or false to eval it. |
||
115 | */ |
||
116 | 6 | private function generateCollectionClass(string $for, string $targetFqcn, $fileName) |
|
117 | { |
||
118 | 6 | $exploded = explode('\\', $targetFqcn); |
|
119 | 6 | $class = array_pop($exploded); |
|
120 | 6 | $namespace = implode('\\', $exploded); |
|
121 | $code = <<<CODE |
||
122 | <?php |
||
123 | |||
124 | 6 | namespace $namespace; |
|
125 | |||
126 | use Doctrine\Common\Collections\Collection as BaseCollection; |
||
127 | use Doctrine\ODM\MongoDB\DocumentManager; |
||
128 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
||
129 | use Doctrine\ODM\MongoDB\MongoDBException; |
||
130 | use Doctrine\ODM\MongoDB\UnitOfWork; |
||
131 | use Doctrine\ODM\MongoDB\Utility\CollectionHelper; |
||
132 | |||
133 | /** |
||
134 | * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PERSISTENT COLLECTION GENERATOR |
||
135 | */ |
||
136 | 6 | class $class extends \\$for implements \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface |
|
137 | { |
||
138 | use \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait; |
||
139 | |||
140 | /** |
||
141 | * @param BaseCollection \$coll |
||
142 | * @param DocumentManager \$dm |
||
143 | * @param UnitOfWork \$uow |
||
144 | */ |
||
145 | public function __construct(BaseCollection \$coll, DocumentManager \$dm, UnitOfWork \$uow) |
||
146 | { |
||
147 | \$this->coll = \$coll; |
||
148 | \$this->dm = \$dm; |
||
149 | \$this->uow = \$uow; |
||
150 | } |
||
151 | |||
152 | CODE; |
||
153 | 6 | $rc = new ReflectionClass($for); |
|
154 | 6 | $rt = new ReflectionClass('Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait'); |
|
155 | 6 | foreach ($rc->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { |
|
156 | 6 | if ($rt->hasMethod($method->name) || |
|
157 | 6 | $method->isConstructor() || |
|
158 | 6 | $method->isFinal() || |
|
159 | 6 | $method->isStatic() |
|
160 | ) { |
||
161 | 6 | continue; |
|
162 | } |
||
163 | 6 | $code .= $this->generateMethod($method); |
|
164 | } |
||
165 | 6 | $code .= "}\n"; |
|
166 | |||
167 | 6 | if ($fileName === false) { |
|
168 | 3 | if (! class_exists($targetFqcn)) { |
|
169 | 3 | eval(substr($code, 5)); |
|
170 | } |
||
171 | } else { |
||
172 | 3 | $parentDirectory = dirname($fileName); |
|
173 | |||
174 | 3 | if (! is_dir($parentDirectory) && (@mkdir($parentDirectory, 0775, true) === false)) { |
|
175 | throw PersistentCollectionException::directoryNotWritable(); |
||
176 | } |
||
177 | |||
178 | 3 | if (! is_writable($parentDirectory)) { |
|
179 | throw PersistentCollectionException::directoryNotWritable(); |
||
180 | } |
||
181 | |||
182 | 3 | $tmpFileName = $fileName . '.' . uniqid('', true); |
|
183 | 3 | file_put_contents($tmpFileName, $code); |
|
184 | 3 | rename($tmpFileName, $fileName); |
|
185 | } |
||
186 | 6 | } |
|
187 | |||
188 | 6 | private function generateMethod(ReflectionMethod $method) : string |
|
189 | { |
||
190 | 6 | $parametersString = $this->buildParametersString($method); |
|
191 | 6 | $callParamsString = implode(', ', $this->getParameterNamesForDecoratedCall($method->getParameters())); |
|
192 | |||
193 | return <<<CODE |
||
194 | |||
195 | /** |
||
196 | * {@inheritDoc} |
||
197 | */ |
||
198 | 6 | public function {$method->name}($parametersString){$this->getMethodReturnType($method)} |
|
199 | { |
||
200 | \$this->initialize(); |
||
201 | if (\$this->needsSchedulingForDirtyCheck()) { |
||
202 | \$this->changed(); |
||
203 | } |
||
204 | 6 | return \$this->coll->{$method->name}($callParamsString); |
|
205 | } |
||
206 | |||
207 | CODE; |
||
208 | } |
||
209 | |||
210 | 6 | private function buildParametersString(ReflectionMethod $method) : string |
|
211 | { |
||
212 | 6 | $parameters = $method->getParameters(); |
|
213 | 6 | $parameterDefinitions = []; |
|
214 | |||
215 | /** @var ReflectionParameter $param */ |
||
216 | 6 | foreach ($parameters as $param) { |
|
217 | 6 | $parameterDefinition = ''; |
|
218 | 6 | $parameterType = $this->getParameterType($param); |
|
219 | |||
220 | 6 | if ($parameterType) { |
|
221 | 6 | $parameterDefinition .= $parameterType . ' '; |
|
222 | } |
||
223 | |||
224 | 6 | if ($param->isPassedByReference()) { |
|
225 | $parameterDefinition .= '&'; |
||
226 | } |
||
227 | |||
228 | 6 | if (method_exists($param, 'isVariadic')) { |
|
229 | 6 | if ($param->isVariadic()) { |
|
230 | $parameterDefinition .= '...'; |
||
231 | } |
||
232 | } |
||
233 | |||
234 | 6 | $parameters[] = '$' . $param->name; |
|
235 | 6 | $parameterDefinition .= '$' . $param->name; |
|
236 | |||
237 | 6 | if ($param->isDefaultValueAvailable()) { |
|
238 | $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true); |
||
239 | } |
||
240 | |||
241 | 6 | $parameterDefinitions[] = $parameterDefinition; |
|
242 | } |
||
243 | |||
244 | 6 | return implode(', ', $parameterDefinitions); |
|
245 | } |
||
246 | |||
247 | 6 | private function getParameterType(ReflectionParameter $parameter) : ?string |
|
248 | { |
||
249 | // We need to pick the type hint class too |
||
250 | 6 | if ($parameter->isArray()) { |
|
251 | return 'array'; |
||
252 | } |
||
253 | |||
254 | 6 | if (method_exists($parameter, 'isCallable') && $parameter->isCallable()) { |
|
255 | return 'callable'; |
||
256 | } |
||
257 | |||
258 | try { |
||
259 | 6 | $parameterClass = $parameter->getClass(); |
|
260 | |||
261 | 6 | if ($parameterClass) { |
|
262 | 6 | return '\\' . $parameterClass->name; |
|
263 | } |
||
264 | } catch (ReflectionException $previous) { |
||
265 | // @todo ProxyGenerator throws specialized exceptions |
||
266 | throw $previous; |
||
267 | } |
||
268 | |||
269 | 2 | return null; |
|
270 | } |
||
271 | |||
272 | /** |
||
273 | * @param ReflectionParameter[] $parameters |
||
274 | * |
||
275 | * @return string[] |
||
276 | */ |
||
277 | 6 | private function getParameterNamesForDecoratedCall(array $parameters) : array |
|
278 | { |
||
279 | 6 | return array_map( |
|
280 | static function (ReflectionParameter $parameter) { |
||
281 | 6 | $name = ''; |
|
282 | |||
283 | 6 | if (method_exists($parameter, 'isVariadic')) { |
|
284 | 6 | if ($parameter->isVariadic()) { |
|
285 | $name .= '...'; |
||
286 | } |
||
287 | } |
||
288 | |||
289 | 6 | return $name . '$' . $parameter->name; |
|
290 | 6 | }, |
|
291 | 6 | $parameters |
|
292 | ); |
||
293 | } |
||
294 | |||
295 | 6 | private function getMethodReturnType(ReflectionMethod $method) : string |
|
296 | { |
||
297 | 6 | $returnType = $method->getReturnType(); |
|
298 | 6 | if ($returnType === null) { |
|
299 | 6 | return ''; |
|
300 | } |
||
301 | |||
302 | 2 | return ': ' . $this->formatType($returnType, $method); |
|
303 | } |
||
304 | |||
305 | 2 | private function formatType( |
|
306 | ReflectionType $type, |
||
307 | ReflectionMethod $method, |
||
308 | ?ReflectionParameter $parameter = null |
||
309 | ) : string { |
||
310 | 2 | $name = method_exists($type, 'getName') ? $type->getName() : (string) $type; |
|
311 | 2 | $nameLower = strtolower($name); |
|
312 | 2 | if ($nameLower === 'self') { |
|
313 | $name = $method->getDeclaringClass()->getName(); |
||
314 | } |
||
315 | 2 | if ($nameLower === 'parent') { |
|
316 | $parentClass = $method->getDeclaringClass()->getParentClass(); |
||
317 | if (! $parentClass) { |
||
318 | throw PersistentCollectionException::parentClassRequired($method->getDeclaringClass()->getName(), $method->getName()); |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
319 | } |
||
320 | |||
321 | $name = $parentClass->getName(); |
||
322 | } |
||
323 | 2 | if (! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) { |
|
324 | if ($parameter !== null) { |
||
325 | throw PersistentCollectionException::invalidParameterTypeHint( |
||
326 | $method->getDeclaringClass()->getName(), |
||
327 | $method->getName(), |
||
328 | $parameter->getName() |
||
329 | ); |
||
330 | } |
||
331 | throw PersistentCollectionException::invalidReturnTypeHint( |
||
332 | $method->getDeclaringClass()->getName(), |
||
333 | $method->getName() |
||
334 | ); |
||
335 | } |
||
336 | 2 | if (! $type->isBuiltin()) { |
|
337 | 2 | $name = '\\' . $name; |
|
338 | } |
||
339 | 2 | if ($type->allowsNull() |
|
340 | 2 | && ($parameter === null || ! $parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== null) |
|
341 | ) { |
||
342 | 1 | $name = '?' . $name; |
|
343 | } |
||
344 | 2 | return $name; |
|
345 | } |
||
346 | } |
||
347 |