ProxyGenerator.php 33 KB

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