Chris@0
|
1 <?php
|
Chris@0
|
2 /*
|
Chris@0
|
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
Chris@0
|
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
Chris@0
|
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
Chris@0
|
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
Chris@0
|
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
Chris@0
|
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
Chris@0
|
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
Chris@0
|
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
Chris@0
|
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
Chris@0
|
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
Chris@0
|
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
Chris@0
|
14 *
|
Chris@0
|
15 * This software consists of voluntary contributions made by many individuals
|
Chris@0
|
16 * and is licensed under the MIT license. For more information, see
|
Chris@0
|
17 * <http://www.doctrine-project.org>.
|
Chris@0
|
18 */
|
Chris@0
|
19
|
Chris@0
|
20 namespace Doctrine\Common\Proxy;
|
Chris@0
|
21
|
Chris@0
|
22 use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
Chris@0
|
23 use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
|
Chris@0
|
24 use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
|
Chris@0
|
25 use Doctrine\Common\Util\ClassUtils;
|
Chris@0
|
26
|
Chris@0
|
27 /**
|
Chris@0
|
28 * This factory is used to generate proxy classes.
|
Chris@0
|
29 * It builds proxies from given parameters, a template and class metadata.
|
Chris@0
|
30 *
|
Chris@0
|
31 * @author Marco Pivetta <ocramius@gmail.com>
|
Chris@0
|
32 * @since 2.4
|
Chris@0
|
33 */
|
Chris@0
|
34 class ProxyGenerator
|
Chris@0
|
35 {
|
Chris@0
|
36 /**
|
Chris@0
|
37 * Used to match very simple id methods that don't need
|
Chris@0
|
38 * to be decorated since the identifier is known.
|
Chris@0
|
39 */
|
Chris@12
|
40 const PATTERN_MATCH_ID_METHOD = '((public\s+)?(function\s+%s\s*\(\)\s*)\s*(?::\s*\??\s*\\\\?[a-z_\x7f-\xff][\w\x7f-\xff]*(?:\\\\[a-z_\x7f-\xff][\w\x7f-\xff]*)*\s*)?{\s*return\s*\$this->%s;\s*})i';
|
Chris@0
|
41
|
Chris@0
|
42 /**
|
Chris@0
|
43 * The namespace that contains all proxy classes.
|
Chris@0
|
44 *
|
Chris@0
|
45 * @var string
|
Chris@0
|
46 */
|
Chris@0
|
47 private $proxyNamespace;
|
Chris@0
|
48
|
Chris@0
|
49 /**
|
Chris@0
|
50 * The directory that contains all proxy classes.
|
Chris@0
|
51 *
|
Chris@0
|
52 * @var string
|
Chris@0
|
53 */
|
Chris@0
|
54 private $proxyDirectory;
|
Chris@0
|
55
|
Chris@0
|
56 /**
|
Chris@0
|
57 * Map of callables used to fill in placeholders set in the template.
|
Chris@0
|
58 *
|
Chris@0
|
59 * @var string[]|callable[]
|
Chris@0
|
60 */
|
Chris@0
|
61 protected $placeholders = [
|
Chris@0
|
62 'baseProxyInterface' => Proxy::class,
|
Chris@0
|
63 'additionalProperties' => '',
|
Chris@0
|
64 ];
|
Chris@0
|
65
|
Chris@0
|
66 /**
|
Chris@0
|
67 * Template used as a blueprint to generate proxies.
|
Chris@0
|
68 *
|
Chris@0
|
69 * @var string
|
Chris@0
|
70 */
|
Chris@0
|
71 protected $proxyClassTemplate = '<?php
|
Chris@0
|
72
|
Chris@0
|
73 namespace <namespace>;
|
Chris@0
|
74
|
Chris@0
|
75 /**
|
Chris@0
|
76 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR
|
Chris@0
|
77 */
|
Chris@0
|
78 class <proxyShortClassName> extends \<className> implements \<baseProxyInterface>
|
Chris@0
|
79 {
|
Chris@0
|
80 /**
|
Chris@0
|
81 * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with
|
Chris@0
|
82 * three parameters, being respectively the proxy object to be initialized, the method that triggered the
|
Chris@0
|
83 * initialization process and an array of ordered parameters that were passed to that method.
|
Chris@0
|
84 *
|
Chris@0
|
85 * @see \Doctrine\Common\Persistence\Proxy::__setInitializer
|
Chris@0
|
86 */
|
Chris@0
|
87 public $__initializer__;
|
Chris@0
|
88
|
Chris@0
|
89 /**
|
Chris@0
|
90 * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
|
Chris@0
|
91 *
|
Chris@0
|
92 * @see \Doctrine\Common\Persistence\Proxy::__setCloner
|
Chris@0
|
93 */
|
Chris@0
|
94 public $__cloner__;
|
Chris@0
|
95
|
Chris@0
|
96 /**
|
Chris@0
|
97 * @var boolean flag indicating if this object was already initialized
|
Chris@0
|
98 *
|
Chris@0
|
99 * @see \Doctrine\Common\Persistence\Proxy::__isInitialized
|
Chris@0
|
100 */
|
Chris@0
|
101 public $__isInitialized__ = false;
|
Chris@0
|
102
|
Chris@0
|
103 /**
|
Chris@0
|
104 * @var array properties to be lazy loaded, with keys being the property
|
Chris@0
|
105 * names and values being their default values
|
Chris@0
|
106 *
|
Chris@0
|
107 * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties
|
Chris@0
|
108 */
|
Chris@0
|
109 public static $lazyPropertiesDefaults = [<lazyPropertiesDefaults>];
|
Chris@0
|
110
|
Chris@0
|
111 <additionalProperties>
|
Chris@0
|
112
|
Chris@0
|
113 <constructorImpl>
|
Chris@0
|
114
|
Chris@0
|
115 <magicGet>
|
Chris@0
|
116
|
Chris@0
|
117 <magicSet>
|
Chris@0
|
118
|
Chris@0
|
119 <magicIsset>
|
Chris@0
|
120
|
Chris@0
|
121 <sleepImpl>
|
Chris@0
|
122
|
Chris@0
|
123 <wakeupImpl>
|
Chris@0
|
124
|
Chris@0
|
125 <cloneImpl>
|
Chris@0
|
126
|
Chris@0
|
127 /**
|
Chris@0
|
128 * Forces initialization of the proxy
|
Chris@0
|
129 */
|
Chris@0
|
130 public function __load()
|
Chris@0
|
131 {
|
Chris@0
|
132 $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []);
|
Chris@0
|
133 }
|
Chris@0
|
134
|
Chris@0
|
135 /**
|
Chris@0
|
136 * {@inheritDoc}
|
Chris@0
|
137 * @internal generated method: use only when explicitly handling proxy specific loading logic
|
Chris@0
|
138 */
|
Chris@0
|
139 public function __isInitialized()
|
Chris@0
|
140 {
|
Chris@0
|
141 return $this->__isInitialized__;
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@0
|
144 /**
|
Chris@0
|
145 * {@inheritDoc}
|
Chris@0
|
146 * @internal generated method: use only when explicitly handling proxy specific loading logic
|
Chris@0
|
147 */
|
Chris@0
|
148 public function __setInitialized($initialized)
|
Chris@0
|
149 {
|
Chris@0
|
150 $this->__isInitialized__ = $initialized;
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 /**
|
Chris@0
|
154 * {@inheritDoc}
|
Chris@0
|
155 * @internal generated method: use only when explicitly handling proxy specific loading logic
|
Chris@0
|
156 */
|
Chris@0
|
157 public function __setInitializer(\Closure $initializer = null)
|
Chris@0
|
158 {
|
Chris@0
|
159 $this->__initializer__ = $initializer;
|
Chris@0
|
160 }
|
Chris@0
|
161
|
Chris@0
|
162 /**
|
Chris@0
|
163 * {@inheritDoc}
|
Chris@0
|
164 * @internal generated method: use only when explicitly handling proxy specific loading logic
|
Chris@0
|
165 */
|
Chris@0
|
166 public function __getInitializer()
|
Chris@0
|
167 {
|
Chris@0
|
168 return $this->__initializer__;
|
Chris@0
|
169 }
|
Chris@0
|
170
|
Chris@0
|
171 /**
|
Chris@0
|
172 * {@inheritDoc}
|
Chris@0
|
173 * @internal generated method: use only when explicitly handling proxy specific loading logic
|
Chris@0
|
174 */
|
Chris@0
|
175 public function __setCloner(\Closure $cloner = null)
|
Chris@0
|
176 {
|
Chris@0
|
177 $this->__cloner__ = $cloner;
|
Chris@0
|
178 }
|
Chris@0
|
179
|
Chris@0
|
180 /**
|
Chris@0
|
181 * {@inheritDoc}
|
Chris@0
|
182 * @internal generated method: use only when explicitly handling proxy specific cloning logic
|
Chris@0
|
183 */
|
Chris@0
|
184 public function __getCloner()
|
Chris@0
|
185 {
|
Chris@0
|
186 return $this->__cloner__;
|
Chris@0
|
187 }
|
Chris@0
|
188
|
Chris@0
|
189 /**
|
Chris@0
|
190 * {@inheritDoc}
|
Chris@0
|
191 * @internal generated method: use only when explicitly handling proxy specific loading logic
|
Chris@0
|
192 * @static
|
Chris@0
|
193 */
|
Chris@0
|
194 public function __getLazyProperties()
|
Chris@0
|
195 {
|
Chris@0
|
196 return self::$lazyPropertiesDefaults;
|
Chris@0
|
197 }
|
Chris@0
|
198
|
Chris@0
|
199 <methods>
|
Chris@0
|
200 }
|
Chris@0
|
201 ';
|
Chris@0
|
202
|
Chris@0
|
203 /**
|
Chris@0
|
204 * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
|
Chris@0
|
205 * connected to the given <tt>EntityManager</tt>.
|
Chris@0
|
206 *
|
Chris@0
|
207 * @param string $proxyDirectory The directory to use for the proxy classes. It must exist.
|
Chris@0
|
208 * @param string $proxyNamespace The namespace to use for the proxy classes.
|
Chris@0
|
209 *
|
Chris@0
|
210 * @throws InvalidArgumentException
|
Chris@0
|
211 */
|
Chris@0
|
212 public function __construct($proxyDirectory, $proxyNamespace)
|
Chris@0
|
213 {
|
Chris@0
|
214 if ( ! $proxyDirectory) {
|
Chris@0
|
215 throw InvalidArgumentException::proxyDirectoryRequired();
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 if ( ! $proxyNamespace) {
|
Chris@0
|
219 throw InvalidArgumentException::proxyNamespaceRequired();
|
Chris@0
|
220 }
|
Chris@0
|
221
|
Chris@0
|
222 $this->proxyDirectory = $proxyDirectory;
|
Chris@0
|
223 $this->proxyNamespace = $proxyNamespace;
|
Chris@0
|
224 }
|
Chris@0
|
225
|
Chris@0
|
226 /**
|
Chris@0
|
227 * Sets a placeholder to be replaced in the template.
|
Chris@0
|
228 *
|
Chris@0
|
229 * @param string $name
|
Chris@0
|
230 * @param string|callable $placeholder
|
Chris@0
|
231 *
|
Chris@0
|
232 * @throws InvalidArgumentException
|
Chris@0
|
233 */
|
Chris@0
|
234 public function setPlaceholder($name, $placeholder)
|
Chris@0
|
235 {
|
Chris@0
|
236 if ( ! is_string($placeholder) && ! is_callable($placeholder)) {
|
Chris@0
|
237 throw InvalidArgumentException::invalidPlaceholder($name);
|
Chris@0
|
238 }
|
Chris@0
|
239
|
Chris@0
|
240 $this->placeholders[$name] = $placeholder;
|
Chris@0
|
241 }
|
Chris@0
|
242
|
Chris@0
|
243 /**
|
Chris@0
|
244 * Sets the base template used to create proxy classes.
|
Chris@0
|
245 *
|
Chris@0
|
246 * @param string $proxyClassTemplate
|
Chris@0
|
247 */
|
Chris@0
|
248 public function setProxyClassTemplate($proxyClassTemplate)
|
Chris@0
|
249 {
|
Chris@0
|
250 $this->proxyClassTemplate = (string) $proxyClassTemplate;
|
Chris@0
|
251 }
|
Chris@0
|
252
|
Chris@0
|
253 /**
|
Chris@0
|
254 * Generates a proxy class file.
|
Chris@0
|
255 *
|
Chris@0
|
256 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class Metadata for the original class.
|
Chris@0
|
257 * @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used.
|
Chris@0
|
258 *
|
Chris@0
|
259 * @throws UnexpectedValueException
|
Chris@0
|
260 */
|
Chris@0
|
261 public function generateProxyClass(ClassMetadata $class, $fileName = false)
|
Chris@0
|
262 {
|
Chris@0
|
263 preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches);
|
Chris@0
|
264
|
Chris@0
|
265 $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]);
|
Chris@0
|
266 $placeholders = [];
|
Chris@0
|
267
|
Chris@0
|
268 foreach ($placeholderMatches as $placeholder => $name) {
|
Chris@0
|
269 $placeholders[$placeholder] = isset($this->placeholders[$name])
|
Chris@0
|
270 ? $this->placeholders[$name]
|
Chris@0
|
271 : [$this, 'generate' . $name];
|
Chris@0
|
272 }
|
Chris@0
|
273
|
Chris@0
|
274 foreach ($placeholders as & $placeholder) {
|
Chris@0
|
275 if (is_callable($placeholder)) {
|
Chris@0
|
276 $placeholder = call_user_func($placeholder, $class);
|
Chris@0
|
277 }
|
Chris@0
|
278 }
|
Chris@0
|
279
|
Chris@0
|
280 $proxyCode = strtr($this->proxyClassTemplate, $placeholders);
|
Chris@0
|
281
|
Chris@0
|
282 if ( ! $fileName) {
|
Chris@0
|
283 $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class);
|
Chris@0
|
284
|
Chris@0
|
285 if ( ! class_exists($proxyClassName)) {
|
Chris@0
|
286 eval(substr($proxyCode, 5));
|
Chris@0
|
287 }
|
Chris@0
|
288
|
Chris@0
|
289 return;
|
Chris@0
|
290 }
|
Chris@0
|
291
|
Chris@0
|
292 $parentDirectory = dirname($fileName);
|
Chris@0
|
293
|
Chris@0
|
294 if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) {
|
Chris@0
|
295 throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
|
Chris@0
|
296 }
|
Chris@0
|
297
|
Chris@0
|
298 if ( ! is_writable($parentDirectory)) {
|
Chris@0
|
299 throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
|
Chris@0
|
300 }
|
Chris@0
|
301
|
Chris@0
|
302 $tmpFileName = $fileName . '.' . uniqid('', true);
|
Chris@0
|
303
|
Chris@0
|
304 file_put_contents($tmpFileName, $proxyCode);
|
Chris@0
|
305 @chmod($tmpFileName, 0664);
|
Chris@0
|
306 rename($tmpFileName, $fileName);
|
Chris@0
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 /**
|
Chris@0
|
310 * Generates the proxy short class name to be used in the template.
|
Chris@0
|
311 *
|
Chris@0
|
312 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
313 *
|
Chris@0
|
314 * @return string
|
Chris@0
|
315 */
|
Chris@0
|
316 private function generateProxyShortClassName(ClassMetadata $class)
|
Chris@0
|
317 {
|
Chris@0
|
318 $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
|
Chris@0
|
319 $parts = explode('\\', strrev($proxyClassName), 2);
|
Chris@0
|
320
|
Chris@0
|
321 return strrev($parts[0]);
|
Chris@0
|
322 }
|
Chris@0
|
323
|
Chris@0
|
324 /**
|
Chris@0
|
325 * Generates the proxy namespace.
|
Chris@0
|
326 *
|
Chris@0
|
327 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
328 *
|
Chris@0
|
329 * @return string
|
Chris@0
|
330 */
|
Chris@0
|
331 private function generateNamespace(ClassMetadata $class)
|
Chris@0
|
332 {
|
Chris@0
|
333 $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
|
Chris@0
|
334 $parts = explode('\\', strrev($proxyClassName), 2);
|
Chris@0
|
335
|
Chris@0
|
336 return strrev($parts[1]);
|
Chris@0
|
337 }
|
Chris@0
|
338
|
Chris@0
|
339 /**
|
Chris@0
|
340 * Generates the original class name.
|
Chris@0
|
341 *
|
Chris@0
|
342 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
343 *
|
Chris@0
|
344 * @return string
|
Chris@0
|
345 */
|
Chris@0
|
346 private function generateClassName(ClassMetadata $class)
|
Chris@0
|
347 {
|
Chris@0
|
348 return ltrim($class->getName(), '\\');
|
Chris@0
|
349 }
|
Chris@0
|
350
|
Chris@0
|
351 /**
|
Chris@0
|
352 * Generates the array representation of lazy loaded public properties and their default values.
|
Chris@0
|
353 *
|
Chris@0
|
354 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
355 *
|
Chris@0
|
356 * @return string
|
Chris@0
|
357 */
|
Chris@0
|
358 private function generateLazyPropertiesDefaults(ClassMetadata $class)
|
Chris@0
|
359 {
|
Chris@0
|
360 $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
|
Chris@0
|
361 $values = [];
|
Chris@0
|
362
|
Chris@0
|
363 foreach ($lazyPublicProperties as $key => $value) {
|
Chris@0
|
364 $values[] = var_export($key, true) . ' => ' . var_export($value, true);
|
Chris@0
|
365 }
|
Chris@0
|
366
|
Chris@0
|
367 return implode(', ', $values);
|
Chris@0
|
368 }
|
Chris@0
|
369
|
Chris@0
|
370 /**
|
Chris@0
|
371 * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values).
|
Chris@0
|
372 *
|
Chris@0
|
373 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
374 *
|
Chris@0
|
375 * @return string
|
Chris@0
|
376 */
|
Chris@0
|
377 private function generateConstructorImpl(ClassMetadata $class)
|
Chris@0
|
378 {
|
Chris@0
|
379 $constructorImpl = <<<'EOT'
|
Chris@0
|
380 /**
|
Chris@0
|
381 * @param \Closure $initializer
|
Chris@0
|
382 * @param \Closure $cloner
|
Chris@0
|
383 */
|
Chris@0
|
384 public function __construct($initializer = null, $cloner = null)
|
Chris@0
|
385 {
|
Chris@0
|
386
|
Chris@0
|
387 EOT;
|
Chris@0
|
388 $toUnset = [];
|
Chris@0
|
389
|
Chris@0
|
390 foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) {
|
Chris@0
|
391 $toUnset[] = '$this->' . $lazyPublicProperty;
|
Chris@0
|
392 }
|
Chris@0
|
393
|
Chris@0
|
394 $constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n")
|
Chris@0
|
395 . <<<'EOT'
|
Chris@0
|
396
|
Chris@0
|
397 $this->__initializer__ = $initializer;
|
Chris@0
|
398 $this->__cloner__ = $cloner;
|
Chris@0
|
399 }
|
Chris@0
|
400 EOT;
|
Chris@0
|
401
|
Chris@0
|
402 return $constructorImpl;
|
Chris@0
|
403 }
|
Chris@0
|
404
|
Chris@0
|
405 /**
|
Chris@0
|
406 * Generates the magic getter invoked when lazy loaded public properties are requested.
|
Chris@0
|
407 *
|
Chris@0
|
408 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
409 *
|
Chris@0
|
410 * @return string
|
Chris@0
|
411 */
|
Chris@0
|
412 private function generateMagicGet(ClassMetadata $class)
|
Chris@0
|
413 {
|
Chris@0
|
414 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
|
Chris@0
|
415 $reflectionClass = $class->getReflectionClass();
|
Chris@0
|
416 $hasParentGet = false;
|
Chris@0
|
417 $returnReference = '';
|
Chris@0
|
418 $inheritDoc = '';
|
Chris@0
|
419
|
Chris@0
|
420 if ($reflectionClass->hasMethod('__get')) {
|
Chris@0
|
421 $hasParentGet = true;
|
Chris@0
|
422 $inheritDoc = '{@inheritDoc}';
|
Chris@0
|
423
|
Chris@0
|
424 if ($reflectionClass->getMethod('__get')->returnsReference()) {
|
Chris@0
|
425 $returnReference = '& ';
|
Chris@0
|
426 }
|
Chris@0
|
427 }
|
Chris@0
|
428
|
Chris@0
|
429 if (empty($lazyPublicProperties) && ! $hasParentGet) {
|
Chris@0
|
430 return '';
|
Chris@0
|
431 }
|
Chris@0
|
432
|
Chris@0
|
433 $magicGet = <<<EOT
|
Chris@0
|
434 /**
|
Chris@0
|
435 * $inheritDoc
|
Chris@0
|
436 * @param string \$name
|
Chris@0
|
437 */
|
Chris@0
|
438 public function {$returnReference}__get(\$name)
|
Chris@0
|
439 {
|
Chris@0
|
440
|
Chris@0
|
441 EOT;
|
Chris@0
|
442
|
Chris@0
|
443 if ( ! empty($lazyPublicProperties)) {
|
Chris@0
|
444 $magicGet .= <<<'EOT'
|
Chris@0
|
445 if (array_key_exists($name, $this->__getLazyProperties())) {
|
Chris@0
|
446 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
|
Chris@0
|
447
|
Chris@0
|
448 return $this->$name;
|
Chris@0
|
449 }
|
Chris@0
|
450
|
Chris@0
|
451
|
Chris@0
|
452 EOT;
|
Chris@0
|
453 }
|
Chris@0
|
454
|
Chris@0
|
455 if ($hasParentGet) {
|
Chris@0
|
456 $magicGet .= <<<'EOT'
|
Chris@0
|
457 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
|
Chris@0
|
458
|
Chris@0
|
459 return parent::__get($name);
|
Chris@0
|
460
|
Chris@0
|
461 EOT;
|
Chris@0
|
462 } else {
|
Chris@0
|
463 $magicGet .= <<<'EOT'
|
Chris@0
|
464 trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE);
|
Chris@0
|
465
|
Chris@0
|
466 EOT;
|
Chris@0
|
467 }
|
Chris@0
|
468
|
Chris@0
|
469 $magicGet .= " }";
|
Chris@0
|
470
|
Chris@0
|
471 return $magicGet;
|
Chris@0
|
472 }
|
Chris@0
|
473
|
Chris@0
|
474 /**
|
Chris@0
|
475 * Generates the magic setter (currently unused).
|
Chris@0
|
476 *
|
Chris@0
|
477 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
478 *
|
Chris@0
|
479 * @return string
|
Chris@0
|
480 */
|
Chris@0
|
481 private function generateMagicSet(ClassMetadata $class)
|
Chris@0
|
482 {
|
Chris@0
|
483 $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
|
Chris@0
|
484 $hasParentSet = $class->getReflectionClass()->hasMethod('__set');
|
Chris@0
|
485
|
Chris@0
|
486 if (empty($lazyPublicProperties) && ! $hasParentSet) {
|
Chris@0
|
487 return '';
|
Chris@0
|
488 }
|
Chris@0
|
489
|
Chris@0
|
490 $inheritDoc = $hasParentSet ? '{@inheritDoc}' : '';
|
Chris@0
|
491 $magicSet = <<<EOT
|
Chris@0
|
492 /**
|
Chris@0
|
493 * $inheritDoc
|
Chris@0
|
494 * @param string \$name
|
Chris@0
|
495 * @param mixed \$value
|
Chris@0
|
496 */
|
Chris@0
|
497 public function __set(\$name, \$value)
|
Chris@0
|
498 {
|
Chris@0
|
499
|
Chris@0
|
500 EOT;
|
Chris@0
|
501
|
Chris@0
|
502 if ( ! empty($lazyPublicProperties)) {
|
Chris@0
|
503 $magicSet .= <<<'EOT'
|
Chris@0
|
504 if (array_key_exists($name, $this->__getLazyProperties())) {
|
Chris@0
|
505 $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
|
Chris@0
|
506
|
Chris@0
|
507 $this->$name = $value;
|
Chris@0
|
508
|
Chris@0
|
509 return;
|
Chris@0
|
510 }
|
Chris@0
|
511
|
Chris@0
|
512
|
Chris@0
|
513 EOT;
|
Chris@0
|
514 }
|
Chris@0
|
515
|
Chris@0
|
516 if ($hasParentSet) {
|
Chris@0
|
517 $magicSet .= <<<'EOT'
|
Chris@0
|
518 $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
|
Chris@0
|
519
|
Chris@0
|
520 return parent::__set($name, $value);
|
Chris@0
|
521 EOT;
|
Chris@0
|
522 } else {
|
Chris@0
|
523 $magicSet .= " \$this->\$name = \$value;";
|
Chris@0
|
524 }
|
Chris@0
|
525
|
Chris@0
|
526 $magicSet .= "\n }";
|
Chris@0
|
527
|
Chris@0
|
528 return $magicSet;
|
Chris@0
|
529 }
|
Chris@0
|
530
|
Chris@0
|
531 /**
|
Chris@0
|
532 * Generates the magic issetter invoked when lazy loaded public properties are checked against isset().
|
Chris@0
|
533 *
|
Chris@0
|
534 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
535 *
|
Chris@0
|
536 * @return string
|
Chris@0
|
537 */
|
Chris@0
|
538 private function generateMagicIsset(ClassMetadata $class)
|
Chris@0
|
539 {
|
Chris@0
|
540 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
|
Chris@0
|
541 $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset');
|
Chris@0
|
542
|
Chris@0
|
543 if (empty($lazyPublicProperties) && ! $hasParentIsset) {
|
Chris@0
|
544 return '';
|
Chris@0
|
545 }
|
Chris@0
|
546
|
Chris@0
|
547 $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : '';
|
Chris@0
|
548 $magicIsset = <<<EOT
|
Chris@0
|
549 /**
|
Chris@0
|
550 * $inheritDoc
|
Chris@0
|
551 * @param string \$name
|
Chris@0
|
552 * @return boolean
|
Chris@0
|
553 */
|
Chris@0
|
554 public function __isset(\$name)
|
Chris@0
|
555 {
|
Chris@0
|
556
|
Chris@0
|
557 EOT;
|
Chris@0
|
558
|
Chris@0
|
559 if ( ! empty($lazyPublicProperties)) {
|
Chris@0
|
560 $magicIsset .= <<<'EOT'
|
Chris@0
|
561 if (array_key_exists($name, $this->__getLazyProperties())) {
|
Chris@0
|
562 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
|
Chris@0
|
563
|
Chris@0
|
564 return isset($this->$name);
|
Chris@0
|
565 }
|
Chris@0
|
566
|
Chris@0
|
567
|
Chris@0
|
568 EOT;
|
Chris@0
|
569 }
|
Chris@0
|
570
|
Chris@0
|
571 if ($hasParentIsset) {
|
Chris@0
|
572 $magicIsset .= <<<'EOT'
|
Chris@0
|
573 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
|
Chris@0
|
574
|
Chris@0
|
575 return parent::__isset($name);
|
Chris@0
|
576
|
Chris@0
|
577 EOT;
|
Chris@0
|
578 } else {
|
Chris@0
|
579 $magicIsset .= " return false;";
|
Chris@0
|
580 }
|
Chris@0
|
581
|
Chris@0
|
582 return $magicIsset . "\n }";
|
Chris@0
|
583 }
|
Chris@0
|
584
|
Chris@0
|
585 /**
|
Chris@0
|
586 * Generates implementation for the `__sleep` method of proxies.
|
Chris@0
|
587 *
|
Chris@0
|
588 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
589 *
|
Chris@0
|
590 * @return string
|
Chris@0
|
591 */
|
Chris@0
|
592 private function generateSleepImpl(ClassMetadata $class)
|
Chris@0
|
593 {
|
Chris@0
|
594 $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep');
|
Chris@0
|
595 $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : '';
|
Chris@0
|
596 $sleepImpl = <<<EOT
|
Chris@0
|
597 /**
|
Chris@0
|
598 * $inheritDoc
|
Chris@0
|
599 * @return array
|
Chris@0
|
600 */
|
Chris@0
|
601 public function __sleep()
|
Chris@0
|
602 {
|
Chris@0
|
603
|
Chris@0
|
604 EOT;
|
Chris@0
|
605
|
Chris@0
|
606 if ($hasParentSleep) {
|
Chris@0
|
607 return $sleepImpl . <<<'EOT'
|
Chris@0
|
608 $properties = array_merge(['__isInitialized__'], parent::__sleep());
|
Chris@0
|
609
|
Chris@0
|
610 if ($this->__isInitialized__) {
|
Chris@0
|
611 $properties = array_diff($properties, array_keys($this->__getLazyProperties()));
|
Chris@0
|
612 }
|
Chris@0
|
613
|
Chris@0
|
614 return $properties;
|
Chris@0
|
615 }
|
Chris@0
|
616 EOT;
|
Chris@0
|
617 }
|
Chris@0
|
618
|
Chris@0
|
619 $allProperties = ['__isInitialized__'];
|
Chris@0
|
620
|
Chris@0
|
621 /* @var $prop \ReflectionProperty */
|
Chris@0
|
622 foreach ($class->getReflectionClass()->getProperties() as $prop) {
|
Chris@0
|
623 if ($prop->isStatic()) {
|
Chris@0
|
624 continue;
|
Chris@0
|
625 }
|
Chris@0
|
626
|
Chris@0
|
627 $allProperties[] = $prop->isPrivate()
|
Chris@0
|
628 ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName()
|
Chris@0
|
629 : $prop->getName();
|
Chris@0
|
630 }
|
Chris@0
|
631
|
Chris@0
|
632 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
|
Chris@0
|
633 $protectedProperties = array_diff($allProperties, $lazyPublicProperties);
|
Chris@0
|
634
|
Chris@0
|
635 foreach ($allProperties as &$property) {
|
Chris@0
|
636 $property = var_export($property, true);
|
Chris@0
|
637 }
|
Chris@0
|
638
|
Chris@0
|
639 foreach ($protectedProperties as &$property) {
|
Chris@0
|
640 $property = var_export($property, true);
|
Chris@0
|
641 }
|
Chris@0
|
642
|
Chris@0
|
643 $allProperties = implode(', ', $allProperties);
|
Chris@0
|
644 $protectedProperties = implode(', ', $protectedProperties);
|
Chris@0
|
645
|
Chris@0
|
646 return $sleepImpl . <<<EOT
|
Chris@0
|
647 if (\$this->__isInitialized__) {
|
Chris@0
|
648 return [$allProperties];
|
Chris@0
|
649 }
|
Chris@0
|
650
|
Chris@0
|
651 return [$protectedProperties];
|
Chris@0
|
652 }
|
Chris@0
|
653 EOT;
|
Chris@0
|
654 }
|
Chris@0
|
655
|
Chris@0
|
656 /**
|
Chris@0
|
657 * Generates implementation for the `__wakeup` method of proxies.
|
Chris@0
|
658 *
|
Chris@0
|
659 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
660 *
|
Chris@0
|
661 * @return string
|
Chris@0
|
662 */
|
Chris@0
|
663 private function generateWakeupImpl(ClassMetadata $class)
|
Chris@0
|
664 {
|
Chris@0
|
665 $unsetPublicProperties = [];
|
Chris@0
|
666 $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup');
|
Chris@0
|
667
|
Chris@0
|
668 foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) {
|
Chris@0
|
669 $unsetPublicProperties[] = '$this->' . $lazyPublicProperty;
|
Chris@0
|
670 }
|
Chris@0
|
671
|
Chris@0
|
672 $shortName = $this->generateProxyShortClassName($class);
|
Chris@0
|
673 $inheritDoc = $hasWakeup ? '{@inheritDoc}' : '';
|
Chris@0
|
674 $wakeupImpl = <<<EOT
|
Chris@0
|
675 /**
|
Chris@0
|
676 * $inheritDoc
|
Chris@0
|
677 */
|
Chris@0
|
678 public function __wakeup()
|
Chris@0
|
679 {
|
Chris@0
|
680 if ( ! \$this->__isInitialized__) {
|
Chris@0
|
681 \$this->__initializer__ = function ($shortName \$proxy) {
|
Chris@0
|
682 \$proxy->__setInitializer(null);
|
Chris@0
|
683 \$proxy->__setCloner(null);
|
Chris@0
|
684
|
Chris@0
|
685 \$existingProperties = get_object_vars(\$proxy);
|
Chris@0
|
686
|
Chris@0
|
687 foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) {
|
Chris@0
|
688 if ( ! array_key_exists(\$property, \$existingProperties)) {
|
Chris@0
|
689 \$proxy->\$property = \$defaultValue;
|
Chris@0
|
690 }
|
Chris@0
|
691 }
|
Chris@0
|
692 };
|
Chris@0
|
693
|
Chris@0
|
694 EOT;
|
Chris@0
|
695
|
Chris@0
|
696 if ( ! empty($unsetPublicProperties)) {
|
Chris@0
|
697 $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");";
|
Chris@0
|
698 }
|
Chris@0
|
699
|
Chris@0
|
700 $wakeupImpl .= "\n }";
|
Chris@0
|
701
|
Chris@0
|
702 if ($hasWakeup) {
|
Chris@0
|
703 $wakeupImpl .= "\n parent::__wakeup();";
|
Chris@0
|
704 }
|
Chris@0
|
705
|
Chris@0
|
706 $wakeupImpl .= "\n }";
|
Chris@0
|
707
|
Chris@0
|
708 return $wakeupImpl;
|
Chris@0
|
709 }
|
Chris@0
|
710
|
Chris@0
|
711 /**
|
Chris@0
|
712 * Generates implementation for the `__clone` method of proxies.
|
Chris@0
|
713 *
|
Chris@0
|
714 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
715 *
|
Chris@0
|
716 * @return string
|
Chris@0
|
717 */
|
Chris@0
|
718 private function generateCloneImpl(ClassMetadata $class)
|
Chris@0
|
719 {
|
Chris@0
|
720 $hasParentClone = $class->getReflectionClass()->hasMethod('__clone');
|
Chris@0
|
721 $inheritDoc = $hasParentClone ? '{@inheritDoc}' : '';
|
Chris@0
|
722 $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : '';
|
Chris@0
|
723
|
Chris@0
|
724 return <<<EOT
|
Chris@0
|
725 /**
|
Chris@0
|
726 * $inheritDoc
|
Chris@0
|
727 */
|
Chris@0
|
728 public function __clone()
|
Chris@0
|
729 {
|
Chris@0
|
730 \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []);
|
Chris@0
|
731 $callParentClone }
|
Chris@0
|
732 EOT;
|
Chris@0
|
733 }
|
Chris@0
|
734
|
Chris@0
|
735 /**
|
Chris@0
|
736 * Generates decorated methods by picking those available in the parent class.
|
Chris@0
|
737 *
|
Chris@0
|
738 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
739 *
|
Chris@0
|
740 * @return string
|
Chris@0
|
741 */
|
Chris@0
|
742 private function generateMethods(ClassMetadata $class)
|
Chris@0
|
743 {
|
Chris@0
|
744 $methods = '';
|
Chris@0
|
745 $methodNames = [];
|
Chris@0
|
746 $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC);
|
Chris@0
|
747 $skippedMethods = [
|
Chris@0
|
748 '__sleep' => true,
|
Chris@0
|
749 '__clone' => true,
|
Chris@0
|
750 '__wakeup' => true,
|
Chris@0
|
751 '__get' => true,
|
Chris@0
|
752 '__set' => true,
|
Chris@0
|
753 '__isset' => true,
|
Chris@0
|
754 ];
|
Chris@0
|
755
|
Chris@0
|
756 foreach ($reflectionMethods as $method) {
|
Chris@0
|
757 $name = $method->getName();
|
Chris@0
|
758
|
Chris@0
|
759 if (
|
Chris@0
|
760 $method->isConstructor() ||
|
Chris@0
|
761 isset($skippedMethods[strtolower($name)]) ||
|
Chris@0
|
762 isset($methodNames[$name]) ||
|
Chris@0
|
763 $method->isFinal() ||
|
Chris@0
|
764 $method->isStatic() ||
|
Chris@0
|
765 ( ! $method->isPublic())
|
Chris@0
|
766 ) {
|
Chris@0
|
767 continue;
|
Chris@0
|
768 }
|
Chris@0
|
769
|
Chris@0
|
770 $methodNames[$name] = true;
|
Chris@0
|
771 $methods .= "\n /**\n"
|
Chris@0
|
772 . " * {@inheritDoc}\n"
|
Chris@0
|
773 . " */\n"
|
Chris@0
|
774 . ' public function ';
|
Chris@0
|
775
|
Chris@0
|
776 if ($method->returnsReference()) {
|
Chris@0
|
777 $methods .= '&';
|
Chris@0
|
778 }
|
Chris@0
|
779
|
Chris@0
|
780 $methods .= $name . '(' . $this->buildParametersString($class, $method, $method->getParameters()) . ')';
|
Chris@0
|
781 $methods .= $this->getMethodReturnType($method);
|
Chris@0
|
782 $methods .= "\n" . ' {' . "\n";
|
Chris@0
|
783
|
Chris@0
|
784 if ($this->isShortIdentifierGetter($method, $class)) {
|
Chris@0
|
785 $identifier = lcfirst(substr($name, 3));
|
Chris@0
|
786 $fieldType = $class->getTypeOfField($identifier);
|
Chris@0
|
787 $cast = in_array($fieldType, ['integer', 'smallint']) ? '(int) ' : '';
|
Chris@0
|
788
|
Chris@0
|
789 $methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
|
Chris@12
|
790 $methods .= ' ';
|
Chris@12
|
791 $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' : '';
|
Chris@12
|
792 $methods .= $cast . ' parent::' . $method->getName() . "();\n";
|
Chris@0
|
793 $methods .= ' }' . "\n\n";
|
Chris@0
|
794 }
|
Chris@0
|
795
|
Chris@0
|
796 $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters()));
|
Chris@0
|
797 $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters()));
|
Chris@0
|
798
|
Chris@0
|
799 $methods .= "\n \$this->__initializer__ "
|
Chris@0
|
800 . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true)
|
Chris@0
|
801 . ", [" . $invokeParamsString . "]);"
|
Chris@12
|
802 . "\n\n "
|
Chris@12
|
803 . ($this->shouldProxiedMethodReturn($method) ? 'return ' : '')
|
Chris@12
|
804 . "parent::" . $name . '(' . $callParamsString . ');'
|
Chris@0
|
805 . "\n" . ' }' . "\n";
|
Chris@0
|
806 }
|
Chris@0
|
807
|
Chris@0
|
808 return $methods;
|
Chris@0
|
809 }
|
Chris@0
|
810
|
Chris@0
|
811 /**
|
Chris@0
|
812 * Generates the Proxy file name.
|
Chris@0
|
813 *
|
Chris@0
|
814 * @param string $className
|
Chris@0
|
815 * @param string $baseDirectory Optional base directory for proxy file name generation.
|
Chris@0
|
816 * If not specified, the directory configured on the Configuration of the
|
Chris@0
|
817 * EntityManager will be used by this factory.
|
Chris@0
|
818 *
|
Chris@0
|
819 * @return string
|
Chris@0
|
820 */
|
Chris@0
|
821 public function getProxyFileName($className, $baseDirectory = null)
|
Chris@0
|
822 {
|
Chris@0
|
823 $baseDirectory = $baseDirectory ?: $this->proxyDirectory;
|
Chris@0
|
824
|
Chris@0
|
825 return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER
|
Chris@0
|
826 . str_replace('\\', '', $className) . '.php';
|
Chris@0
|
827 }
|
Chris@0
|
828
|
Chris@0
|
829 /**
|
Chris@0
|
830 * Checks if the method is a short identifier getter.
|
Chris@0
|
831 *
|
Chris@0
|
832 * What does this mean? For proxy objects the identifier is already known,
|
Chris@0
|
833 * however accessing the getter for this identifier usually triggers the
|
Chris@0
|
834 * lazy loading, leading to a query that may not be necessary if only the
|
Chris@0
|
835 * ID is interesting for the userland code (for example in views that
|
Chris@0
|
836 * generate links to the entity, but do not display anything else).
|
Chris@0
|
837 *
|
Chris@0
|
838 * @param \ReflectionMethod $method
|
Chris@0
|
839 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
840 *
|
Chris@0
|
841 * @return boolean
|
Chris@0
|
842 */
|
Chris@0
|
843 private function isShortIdentifierGetter($method, ClassMetadata $class)
|
Chris@0
|
844 {
|
Chris@0
|
845 $identifier = lcfirst(substr($method->getName(), 3));
|
Chris@0
|
846 $startLine = $method->getStartLine();
|
Chris@0
|
847 $endLine = $method->getEndLine();
|
Chris@0
|
848 $cheapCheck = (
|
Chris@0
|
849 $method->getNumberOfParameters() == 0
|
Chris@0
|
850 && substr($method->getName(), 0, 3) == 'get'
|
Chris@0
|
851 && in_array($identifier, $class->getIdentifier(), true)
|
Chris@0
|
852 && $class->hasField($identifier)
|
Chris@0
|
853 && (($endLine - $startLine) <= 4)
|
Chris@0
|
854 );
|
Chris@0
|
855
|
Chris@0
|
856 if ($cheapCheck) {
|
Chris@0
|
857 $code = file($method->getDeclaringClass()->getFileName());
|
Chris@0
|
858 $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1)));
|
Chris@0
|
859
|
Chris@0
|
860 $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
|
Chris@0
|
861
|
Chris@0
|
862 if (preg_match($pattern, $code)) {
|
Chris@0
|
863 return true;
|
Chris@0
|
864 }
|
Chris@0
|
865 }
|
Chris@0
|
866
|
Chris@0
|
867 return false;
|
Chris@0
|
868 }
|
Chris@0
|
869
|
Chris@0
|
870 /**
|
Chris@0
|
871 * Generates the list of public properties to be lazy loaded, with their default values.
|
Chris@0
|
872 *
|
Chris@0
|
873 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
|
Chris@0
|
874 *
|
Chris@0
|
875 * @return mixed[]
|
Chris@0
|
876 */
|
Chris@0
|
877 private function getLazyLoadedPublicProperties(ClassMetadata $class)
|
Chris@0
|
878 {
|
Chris@0
|
879 $defaultProperties = $class->getReflectionClass()->getDefaultProperties();
|
Chris@0
|
880 $properties = [];
|
Chris@0
|
881
|
Chris@0
|
882 foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
|
Chris@0
|
883 $name = $property->getName();
|
Chris@0
|
884
|
Chris@0
|
885 if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) {
|
Chris@0
|
886 $properties[$name] = $defaultProperties[$name];
|
Chris@0
|
887 }
|
Chris@0
|
888 }
|
Chris@0
|
889
|
Chris@0
|
890 return $properties;
|
Chris@0
|
891 }
|
Chris@0
|
892
|
Chris@0
|
893 /**
|
Chris@0
|
894 * @param ClassMetadata $class
|
Chris@0
|
895 * @param \ReflectionMethod $method
|
Chris@0
|
896 * @param \ReflectionParameter[] $parameters
|
Chris@0
|
897 *
|
Chris@0
|
898 * @return string
|
Chris@0
|
899 */
|
Chris@0
|
900 private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters)
|
Chris@0
|
901 {
|
Chris@0
|
902 $parameterDefinitions = [];
|
Chris@0
|
903
|
Chris@0
|
904 /* @var $param \ReflectionParameter */
|
Chris@0
|
905 foreach ($parameters as $param) {
|
Chris@0
|
906 $parameterDefinition = '';
|
Chris@0
|
907
|
Chris@0
|
908 if ($parameterType = $this->getParameterType($class, $method, $param)) {
|
Chris@0
|
909 $parameterDefinition .= $parameterType . ' ';
|
Chris@0
|
910 }
|
Chris@0
|
911
|
Chris@0
|
912 if ($param->isPassedByReference()) {
|
Chris@0
|
913 $parameterDefinition .= '&';
|
Chris@0
|
914 }
|
Chris@0
|
915
|
Chris@0
|
916 if (method_exists($param, 'isVariadic') && $param->isVariadic()) {
|
Chris@0
|
917 $parameterDefinition .= '...';
|
Chris@0
|
918 }
|
Chris@0
|
919
|
Chris@0
|
920 $parameters[] = '$' . $param->getName();
|
Chris@0
|
921 $parameterDefinition .= '$' . $param->getName();
|
Chris@0
|
922
|
Chris@0
|
923 if ($param->isDefaultValueAvailable()) {
|
Chris@0
|
924 $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
|
Chris@0
|
925 }
|
Chris@0
|
926
|
Chris@0
|
927 $parameterDefinitions[] = $parameterDefinition;
|
Chris@0
|
928 }
|
Chris@0
|
929
|
Chris@0
|
930 return implode(', ', $parameterDefinitions);
|
Chris@0
|
931 }
|
Chris@0
|
932
|
Chris@0
|
933 /**
|
Chris@0
|
934 * @param ClassMetadata $class
|
Chris@0
|
935 * @param \ReflectionMethod $method
|
Chris@0
|
936 * @param \ReflectionParameter $parameter
|
Chris@0
|
937 *
|
Chris@0
|
938 * @return string|null
|
Chris@0
|
939 */
|
Chris@0
|
940 private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter)
|
Chris@0
|
941 {
|
Chris@12
|
942 if (method_exists($parameter, 'hasType')) {
|
Chris@12
|
943 if ( ! $parameter->hasType()) {
|
Chris@12
|
944 return '';
|
Chris@12
|
945 }
|
Chris@0
|
946
|
Chris@12
|
947 return $this->formatType($parameter->getType(), $parameter->getDeclaringFunction(), $parameter);
|
Chris@12
|
948 }
|
Chris@12
|
949
|
Chris@12
|
950 // For PHP 5.x, we need to pick the type hint in the old way (to be removed for PHP 7.0+)
|
Chris@0
|
951 if ($parameter->isArray()) {
|
Chris@0
|
952 return 'array';
|
Chris@0
|
953 }
|
Chris@0
|
954
|
Chris@0
|
955 if ($parameter->isCallable()) {
|
Chris@0
|
956 return 'callable';
|
Chris@0
|
957 }
|
Chris@0
|
958
|
Chris@0
|
959 try {
|
Chris@0
|
960 $parameterClass = $parameter->getClass();
|
Chris@0
|
961
|
Chris@0
|
962 if ($parameterClass) {
|
Chris@0
|
963 return '\\' . $parameterClass->getName();
|
Chris@0
|
964 }
|
Chris@0
|
965 } catch (\ReflectionException $previous) {
|
Chris@0
|
966 throw UnexpectedValueException::invalidParameterTypeHint(
|
Chris@0
|
967 $class->getName(),
|
Chris@0
|
968 $method->getName(),
|
Chris@0
|
969 $parameter->getName(),
|
Chris@0
|
970 $previous
|
Chris@0
|
971 );
|
Chris@0
|
972 }
|
Chris@0
|
973
|
Chris@0
|
974 return null;
|
Chris@0
|
975 }
|
Chris@0
|
976
|
Chris@0
|
977 /**
|
Chris@0
|
978 * @param \ReflectionParameter[] $parameters
|
Chris@0
|
979 *
|
Chris@0
|
980 * @return string[]
|
Chris@0
|
981 */
|
Chris@0
|
982 private function getParameterNamesForInvoke(array $parameters)
|
Chris@0
|
983 {
|
Chris@0
|
984 return array_map(
|
Chris@0
|
985 function (\ReflectionParameter $parameter) {
|
Chris@0
|
986 return '$' . $parameter->getName();
|
Chris@0
|
987 },
|
Chris@0
|
988 $parameters
|
Chris@0
|
989 );
|
Chris@0
|
990 }
|
Chris@0
|
991
|
Chris@0
|
992 /**
|
Chris@0
|
993 * @param \ReflectionParameter[] $parameters
|
Chris@0
|
994 *
|
Chris@0
|
995 * @return string[]
|
Chris@0
|
996 */
|
Chris@0
|
997 private function getParameterNamesForParentCall(array $parameters)
|
Chris@0
|
998 {
|
Chris@0
|
999 return array_map(
|
Chris@0
|
1000 function (\ReflectionParameter $parameter) {
|
Chris@0
|
1001 $name = '';
|
Chris@0
|
1002
|
Chris@0
|
1003 if (method_exists($parameter, 'isVariadic') && $parameter->isVariadic()) {
|
Chris@0
|
1004 $name .= '...';
|
Chris@0
|
1005 }
|
Chris@0
|
1006
|
Chris@0
|
1007 $name .= '$' . $parameter->getName();
|
Chris@0
|
1008
|
Chris@0
|
1009 return $name;
|
Chris@0
|
1010 },
|
Chris@0
|
1011 $parameters
|
Chris@0
|
1012 );
|
Chris@0
|
1013 }
|
Chris@0
|
1014
|
Chris@0
|
1015 /**
|
Chris@0
|
1016 * @Param \ReflectionMethod $method
|
Chris@0
|
1017 *
|
Chris@0
|
1018 * @return string
|
Chris@0
|
1019 */
|
Chris@0
|
1020 private function getMethodReturnType(\ReflectionMethod $method)
|
Chris@0
|
1021 {
|
Chris@12
|
1022 if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
|
Chris@0
|
1023 return '';
|
Chris@0
|
1024 }
|
Chris@0
|
1025
|
Chris@12
|
1026 return ': ' . $this->formatType($method->getReturnType(), $method);
|
Chris@12
|
1027 }
|
Chris@0
|
1028
|
Chris@12
|
1029 /**
|
Chris@12
|
1030 * @param \ReflectionMethod $method
|
Chris@12
|
1031 *
|
Chris@12
|
1032 * @return bool
|
Chris@12
|
1033 */
|
Chris@12
|
1034 private function shouldProxiedMethodReturn(\ReflectionMethod $method)
|
Chris@12
|
1035 {
|
Chris@12
|
1036 if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
|
Chris@12
|
1037 return true;
|
Chris@0
|
1038 }
|
Chris@0
|
1039
|
Chris@12
|
1040 return 'void' !== strtolower($this->formatType($method->getReturnType(), $method));
|
Chris@12
|
1041 }
|
Chris@12
|
1042
|
Chris@12
|
1043 /**
|
Chris@12
|
1044 * @param \ReflectionType $type
|
Chris@12
|
1045 * @param \ReflectionMethod $method
|
Chris@12
|
1046 * @param \ReflectionParameter|null $parameter
|
Chris@12
|
1047 *
|
Chris@12
|
1048 * @return string
|
Chris@12
|
1049 */
|
Chris@12
|
1050 private function formatType(
|
Chris@12
|
1051 \ReflectionType $type,
|
Chris@12
|
1052 \ReflectionMethod $method,
|
Chris@12
|
1053 \ReflectionParameter $parameter = null
|
Chris@12
|
1054 ) {
|
Chris@12
|
1055 $name = method_exists($type, 'getName') ? $type->getName() : (string) $type;
|
Chris@12
|
1056 $nameLower = strtolower($name);
|
Chris@0
|
1057
|
Chris@0
|
1058 if ('self' === $nameLower) {
|
Chris@12
|
1059 $name = $method->getDeclaringClass()->getName();
|
Chris@0
|
1060 }
|
Chris@0
|
1061
|
Chris@0
|
1062 if ('parent' === $nameLower) {
|
Chris@12
|
1063 $name = $method->getDeclaringClass()->getParentClass()->getName();
|
Chris@0
|
1064 }
|
Chris@0
|
1065
|
Chris@12
|
1066 if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) {
|
Chris@12
|
1067 if (null !== $parameter) {
|
Chris@12
|
1068 throw UnexpectedValueException::invalidParameterTypeHint(
|
Chris@12
|
1069 $method->getDeclaringClass()->getName(),
|
Chris@12
|
1070 $method->getName(),
|
Chris@12
|
1071 $parameter->getName()
|
Chris@12
|
1072 );
|
Chris@12
|
1073 }
|
Chris@12
|
1074
|
Chris@12
|
1075 throw UnexpectedValueException::invalidReturnTypeHint(
|
Chris@12
|
1076 $method->getDeclaringClass()->getName(),
|
Chris@12
|
1077 $method->getName()
|
Chris@12
|
1078 );
|
Chris@12
|
1079 }
|
Chris@12
|
1080
|
Chris@12
|
1081 if ( ! $type->isBuiltin()) {
|
Chris@12
|
1082 $name = '\\' . $name;
|
Chris@12
|
1083 }
|
Chris@12
|
1084
|
Chris@12
|
1085 if ($type->allowsNull()
|
Chris@12
|
1086 && (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue())
|
Chris@12
|
1087 ) {
|
Chris@12
|
1088 $name = '?' . $name;
|
Chris@12
|
1089 }
|
Chris@12
|
1090
|
Chris@12
|
1091 return $name;
|
Chris@0
|
1092 }
|
Chris@0
|
1093 }
|