ProxyGenerator.php 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. <?php
  2. namespace Doctrine\Common\Proxy;
  3. use Doctrine\Common\Persistence\Mapping\ClassMetadata;
  4. use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
  5. use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
  6. use Doctrine\Common\Util\ClassUtils;
  7. /**
  8. * This factory is used to generate proxy classes.
  9. * It builds proxies from given parameters, a template and class metadata.
  10. *
  11. * @author Marco Pivetta <ocramius@gmail.com>
  12. * @since 2.4
  13. *
  14. * @deprecated The Doctrine\Common\Proxy component is deprecated, please use ocramius/proxy-manager instead.
  15. */
  16. class ProxyGenerator
  17. {
  18. /**
  19. * Used to match very simple id methods that don't need
  20. * to be decorated since the identifier is known.
  21. */
  22. 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';
  23. /**
  24. * The namespace that contains all proxy classes.
  25. *
  26. * @var string
  27. */
  28. private $proxyNamespace;
  29. /**
  30. * The directory that contains all proxy classes.
  31. *
  32. * @var string
  33. */
  34. private $proxyDirectory;
  35. /**
  36. * Map of callables used to fill in placeholders set in the template.
  37. *
  38. * @var string[]|callable[]
  39. */
  40. protected $placeholders = [
  41. 'baseProxyInterface' => Proxy::class,
  42. 'additionalProperties' => '',
  43. ];
  44. /**
  45. * Template used as a blueprint to generate proxies.
  46. *
  47. * @var string
  48. */
  49. protected $proxyClassTemplate = '<?php
  50. namespace <namespace>;
  51. /**
  52. * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR
  53. */
  54. class <proxyShortClassName> extends \<className> implements \<baseProxyInterface>
  55. {
  56. /**
  57. * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with
  58. * three parameters, being respectively the proxy object to be initialized, the method that triggered the
  59. * initialization process and an array of ordered parameters that were passed to that method.
  60. *
  61. * @see \Doctrine\Common\Persistence\Proxy::__setInitializer
  62. */
  63. public $__initializer__;
  64. /**
  65. * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
  66. *
  67. * @see \Doctrine\Common\Persistence\Proxy::__setCloner
  68. */
  69. public $__cloner__;
  70. /**
  71. * @var boolean flag indicating if this object was already initialized
  72. *
  73. * @see \Doctrine\Common\Persistence\Proxy::__isInitialized
  74. */
  75. public $__isInitialized__ = false;
  76. /**
  77. * @var array properties to be lazy loaded, with keys being the property
  78. * names and values being their default values
  79. *
  80. * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties
  81. */
  82. public static $lazyPropertiesDefaults = [<lazyPropertiesDefaults>];
  83. <additionalProperties>
  84. <constructorImpl>
  85. <magicGet>
  86. <magicSet>
  87. <magicIsset>
  88. <sleepImpl>
  89. <wakeupImpl>
  90. <cloneImpl>
  91. /**
  92. * Forces initialization of the proxy
  93. */
  94. public function __load()
  95. {
  96. $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []);
  97. }
  98. /**
  99. * {@inheritDoc}
  100. * @internal generated method: use only when explicitly handling proxy specific loading logic
  101. */
  102. public function __isInitialized()
  103. {
  104. return $this->__isInitialized__;
  105. }
  106. /**
  107. * {@inheritDoc}
  108. * @internal generated method: use only when explicitly handling proxy specific loading logic
  109. */
  110. public function __setInitialized($initialized)
  111. {
  112. $this->__isInitialized__ = $initialized;
  113. }
  114. /**
  115. * {@inheritDoc}
  116. * @internal generated method: use only when explicitly handling proxy specific loading logic
  117. */
  118. public function __setInitializer(\Closure $initializer = null)
  119. {
  120. $this->__initializer__ = $initializer;
  121. }
  122. /**
  123. * {@inheritDoc}
  124. * @internal generated method: use only when explicitly handling proxy specific loading logic
  125. */
  126. public function __getInitializer()
  127. {
  128. return $this->__initializer__;
  129. }
  130. /**
  131. * {@inheritDoc}
  132. * @internal generated method: use only when explicitly handling proxy specific loading logic
  133. */
  134. public function __setCloner(\Closure $cloner = null)
  135. {
  136. $this->__cloner__ = $cloner;
  137. }
  138. /**
  139. * {@inheritDoc}
  140. * @internal generated method: use only when explicitly handling proxy specific cloning logic
  141. */
  142. public function __getCloner()
  143. {
  144. return $this->__cloner__;
  145. }
  146. /**
  147. * {@inheritDoc}
  148. * @internal generated method: use only when explicitly handling proxy specific loading logic
  149. * @static
  150. */
  151. public function __getLazyProperties()
  152. {
  153. return self::$lazyPropertiesDefaults;
  154. }
  155. <methods>
  156. }
  157. ';
  158. /**
  159. * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
  160. * connected to the given <tt>EntityManager</tt>.
  161. *
  162. * @param string $proxyDirectory The directory to use for the proxy classes. It must exist.
  163. * @param string $proxyNamespace The namespace to use for the proxy classes.
  164. *
  165. * @throws InvalidArgumentException
  166. */
  167. public function __construct($proxyDirectory, $proxyNamespace)
  168. {
  169. if ( ! $proxyDirectory) {
  170. throw InvalidArgumentException::proxyDirectoryRequired();
  171. }
  172. if ( ! $proxyNamespace) {
  173. throw InvalidArgumentException::proxyNamespaceRequired();
  174. }
  175. $this->proxyDirectory = $proxyDirectory;
  176. $this->proxyNamespace = $proxyNamespace;
  177. }
  178. /**
  179. * Sets a placeholder to be replaced in the template.
  180. *
  181. * @param string $name
  182. * @param string|callable $placeholder
  183. *
  184. * @throws InvalidArgumentException
  185. */
  186. public function setPlaceholder($name, $placeholder)
  187. {
  188. if ( ! is_string($placeholder) && ! is_callable($placeholder)) {
  189. throw InvalidArgumentException::invalidPlaceholder($name);
  190. }
  191. $this->placeholders[$name] = $placeholder;
  192. }
  193. /**
  194. * Sets the base template used to create proxy classes.
  195. *
  196. * @param string $proxyClassTemplate
  197. */
  198. public function setProxyClassTemplate($proxyClassTemplate)
  199. {
  200. $this->proxyClassTemplate = (string) $proxyClassTemplate;
  201. }
  202. /**
  203. * Generates a proxy class file.
  204. *
  205. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class Metadata for the original class.
  206. * @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used.
  207. *
  208. * @throws InvalidArgumentException
  209. * @throws UnexpectedValueException
  210. */
  211. public function generateProxyClass(ClassMetadata $class, $fileName = false)
  212. {
  213. $this->verifyClassCanBeProxied($class);
  214. preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches);
  215. $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]);
  216. $placeholders = [];
  217. foreach ($placeholderMatches as $placeholder => $name) {
  218. $placeholders[$placeholder] = isset($this->placeholders[$name])
  219. ? $this->placeholders[$name]
  220. : [$this, 'generate' . $name];
  221. }
  222. foreach ($placeholders as & $placeholder) {
  223. if (is_callable($placeholder)) {
  224. $placeholder = call_user_func($placeholder, $class);
  225. }
  226. }
  227. $proxyCode = strtr($this->proxyClassTemplate, $placeholders);
  228. if ( ! $fileName) {
  229. $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class);
  230. if ( ! class_exists($proxyClassName)) {
  231. eval(substr($proxyCode, 5));
  232. }
  233. return;
  234. }
  235. $parentDirectory = dirname($fileName);
  236. if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) {
  237. throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
  238. }
  239. if ( ! is_writable($parentDirectory)) {
  240. throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
  241. }
  242. $tmpFileName = $fileName . '.' . uniqid('', true);
  243. file_put_contents($tmpFileName, $proxyCode);
  244. @chmod($tmpFileName, 0664);
  245. rename($tmpFileName, $fileName);
  246. }
  247. /**
  248. * @param ClassMetadata $class
  249. *
  250. * @throws InvalidArgumentException
  251. */
  252. private function verifyClassCanBeProxied(ClassMetadata $class)
  253. {
  254. if ($class->getReflectionClass()->isFinal()) {
  255. throw InvalidArgumentException::classMustNotBeFinal($class->getName());
  256. }
  257. if ($class->getReflectionClass()->isAbstract()) {
  258. throw InvalidArgumentException::classMustNotBeAbstract($class->getName());
  259. }
  260. }
  261. /**
  262. * Generates the proxy short class name to be used in the template.
  263. *
  264. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  265. *
  266. * @return string
  267. */
  268. private function generateProxyShortClassName(ClassMetadata $class)
  269. {
  270. $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
  271. $parts = explode('\\', strrev($proxyClassName), 2);
  272. return strrev($parts[0]);
  273. }
  274. /**
  275. * Generates the proxy namespace.
  276. *
  277. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  278. *
  279. * @return string
  280. */
  281. private function generateNamespace(ClassMetadata $class)
  282. {
  283. $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
  284. $parts = explode('\\', strrev($proxyClassName), 2);
  285. return strrev($parts[1]);
  286. }
  287. /**
  288. * Generates the original class name.
  289. *
  290. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  291. *
  292. * @return string
  293. */
  294. private function generateClassName(ClassMetadata $class)
  295. {
  296. return ltrim($class->getName(), '\\');
  297. }
  298. /**
  299. * Generates the array representation of lazy loaded public properties and their default values.
  300. *
  301. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  302. *
  303. * @return string
  304. */
  305. private function generateLazyPropertiesDefaults(ClassMetadata $class)
  306. {
  307. $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
  308. $values = [];
  309. foreach ($lazyPublicProperties as $key => $value) {
  310. $values[] = var_export($key, true) . ' => ' . var_export($value, true);
  311. }
  312. return implode(', ', $values);
  313. }
  314. /**
  315. * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values).
  316. *
  317. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  318. *
  319. * @return string
  320. */
  321. private function generateConstructorImpl(ClassMetadata $class)
  322. {
  323. $constructorImpl = <<<'EOT'
  324. /**
  325. * @param \Closure $initializer
  326. * @param \Closure $cloner
  327. */
  328. public function __construct($initializer = null, $cloner = null)
  329. {
  330. EOT;
  331. $toUnset = [];
  332. foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) {
  333. $toUnset[] = '$this->' . $lazyPublicProperty;
  334. }
  335. $constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n")
  336. . <<<'EOT'
  337. $this->__initializer__ = $initializer;
  338. $this->__cloner__ = $cloner;
  339. }
  340. EOT;
  341. return $constructorImpl;
  342. }
  343. /**
  344. * Generates the magic getter invoked when lazy loaded public properties are requested.
  345. *
  346. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  347. *
  348. * @return string
  349. */
  350. private function generateMagicGet(ClassMetadata $class)
  351. {
  352. $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
  353. $reflectionClass = $class->getReflectionClass();
  354. $hasParentGet = false;
  355. $returnReference = '';
  356. $inheritDoc = '';
  357. if ($reflectionClass->hasMethod('__get')) {
  358. $hasParentGet = true;
  359. $inheritDoc = '{@inheritDoc}';
  360. if ($reflectionClass->getMethod('__get')->returnsReference()) {
  361. $returnReference = '& ';
  362. }
  363. }
  364. if (empty($lazyPublicProperties) && ! $hasParentGet) {
  365. return '';
  366. }
  367. $magicGet = <<<EOT
  368. /**
  369. * $inheritDoc
  370. * @param string \$name
  371. */
  372. public function {$returnReference}__get(\$name)
  373. {
  374. EOT;
  375. if ( ! empty($lazyPublicProperties)) {
  376. $magicGet .= <<<'EOT'
  377. if (array_key_exists($name, $this->__getLazyProperties())) {
  378. $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
  379. return $this->$name;
  380. }
  381. EOT;
  382. }
  383. if ($hasParentGet) {
  384. $magicGet .= <<<'EOT'
  385. $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
  386. return parent::__get($name);
  387. EOT;
  388. } else {
  389. $magicGet .= <<<'EOT'
  390. trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE);
  391. EOT;
  392. }
  393. $magicGet .= " }";
  394. return $magicGet;
  395. }
  396. /**
  397. * Generates the magic setter (currently unused).
  398. *
  399. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  400. *
  401. * @return string
  402. */
  403. private function generateMagicSet(ClassMetadata $class)
  404. {
  405. $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
  406. $hasParentSet = $class->getReflectionClass()->hasMethod('__set');
  407. if (empty($lazyPublicProperties) && ! $hasParentSet) {
  408. return '';
  409. }
  410. $inheritDoc = $hasParentSet ? '{@inheritDoc}' : '';
  411. $magicSet = <<<EOT
  412. /**
  413. * $inheritDoc
  414. * @param string \$name
  415. * @param mixed \$value
  416. */
  417. public function __set(\$name, \$value)
  418. {
  419. EOT;
  420. if ( ! empty($lazyPublicProperties)) {
  421. $magicSet .= <<<'EOT'
  422. if (array_key_exists($name, $this->__getLazyProperties())) {
  423. $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
  424. $this->$name = $value;
  425. return;
  426. }
  427. EOT;
  428. }
  429. if ($hasParentSet) {
  430. $magicSet .= <<<'EOT'
  431. $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
  432. return parent::__set($name, $value);
  433. EOT;
  434. } else {
  435. $magicSet .= " \$this->\$name = \$value;";
  436. }
  437. $magicSet .= "\n }";
  438. return $magicSet;
  439. }
  440. /**
  441. * Generates the magic issetter invoked when lazy loaded public properties are checked against isset().
  442. *
  443. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  444. *
  445. * @return string
  446. */
  447. private function generateMagicIsset(ClassMetadata $class)
  448. {
  449. $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
  450. $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset');
  451. if (empty($lazyPublicProperties) && ! $hasParentIsset) {
  452. return '';
  453. }
  454. $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : '';
  455. $magicIsset = <<<EOT
  456. /**
  457. * $inheritDoc
  458. * @param string \$name
  459. * @return boolean
  460. */
  461. public function __isset(\$name)
  462. {
  463. EOT;
  464. if ( ! empty($lazyPublicProperties)) {
  465. $magicIsset .= <<<'EOT'
  466. if (array_key_exists($name, $this->__getLazyProperties())) {
  467. $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
  468. return isset($this->$name);
  469. }
  470. EOT;
  471. }
  472. if ($hasParentIsset) {
  473. $magicIsset .= <<<'EOT'
  474. $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
  475. return parent::__isset($name);
  476. EOT;
  477. } else {
  478. $magicIsset .= " return false;";
  479. }
  480. return $magicIsset . "\n }";
  481. }
  482. /**
  483. * Generates implementation for the `__sleep` method of proxies.
  484. *
  485. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  486. *
  487. * @return string
  488. */
  489. private function generateSleepImpl(ClassMetadata $class)
  490. {
  491. $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep');
  492. $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : '';
  493. $sleepImpl = <<<EOT
  494. /**
  495. * $inheritDoc
  496. * @return array
  497. */
  498. public function __sleep()
  499. {
  500. EOT;
  501. if ($hasParentSleep) {
  502. return $sleepImpl . <<<'EOT'
  503. $properties = array_merge(['__isInitialized__'], parent::__sleep());
  504. if ($this->__isInitialized__) {
  505. $properties = array_diff($properties, array_keys($this->__getLazyProperties()));
  506. }
  507. return $properties;
  508. }
  509. EOT;
  510. }
  511. $allProperties = ['__isInitialized__'];
  512. /* @var $prop \ReflectionProperty */
  513. foreach ($class->getReflectionClass()->getProperties() as $prop) {
  514. if ($prop->isStatic()) {
  515. continue;
  516. }
  517. $allProperties[] = $prop->isPrivate()
  518. ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName()
  519. : $prop->getName();
  520. }
  521. $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
  522. $protectedProperties = array_diff($allProperties, $lazyPublicProperties);
  523. foreach ($allProperties as &$property) {
  524. $property = var_export($property, true);
  525. }
  526. foreach ($protectedProperties as &$property) {
  527. $property = var_export($property, true);
  528. }
  529. $allProperties = implode(', ', $allProperties);
  530. $protectedProperties = implode(', ', $protectedProperties);
  531. return $sleepImpl . <<<EOT
  532. if (\$this->__isInitialized__) {
  533. return [$allProperties];
  534. }
  535. return [$protectedProperties];
  536. }
  537. EOT;
  538. }
  539. /**
  540. * Generates implementation for the `__wakeup` method of proxies.
  541. *
  542. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  543. *
  544. * @return string
  545. */
  546. private function generateWakeupImpl(ClassMetadata $class)
  547. {
  548. $unsetPublicProperties = [];
  549. $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup');
  550. foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) {
  551. $unsetPublicProperties[] = '$this->' . $lazyPublicProperty;
  552. }
  553. $shortName = $this->generateProxyShortClassName($class);
  554. $inheritDoc = $hasWakeup ? '{@inheritDoc}' : '';
  555. $wakeupImpl = <<<EOT
  556. /**
  557. * $inheritDoc
  558. */
  559. public function __wakeup()
  560. {
  561. if ( ! \$this->__isInitialized__) {
  562. \$this->__initializer__ = function ($shortName \$proxy) {
  563. \$proxy->__setInitializer(null);
  564. \$proxy->__setCloner(null);
  565. \$existingProperties = get_object_vars(\$proxy);
  566. foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) {
  567. if ( ! array_key_exists(\$property, \$existingProperties)) {
  568. \$proxy->\$property = \$defaultValue;
  569. }
  570. }
  571. };
  572. EOT;
  573. if ( ! empty($unsetPublicProperties)) {
  574. $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");";
  575. }
  576. $wakeupImpl .= "\n }";
  577. if ($hasWakeup) {
  578. $wakeupImpl .= "\n parent::__wakeup();";
  579. }
  580. $wakeupImpl .= "\n }";
  581. return $wakeupImpl;
  582. }
  583. /**
  584. * Generates implementation for the `__clone` method of proxies.
  585. *
  586. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  587. *
  588. * @return string
  589. */
  590. private function generateCloneImpl(ClassMetadata $class)
  591. {
  592. $hasParentClone = $class->getReflectionClass()->hasMethod('__clone');
  593. $inheritDoc = $hasParentClone ? '{@inheritDoc}' : '';
  594. $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : '';
  595. return <<<EOT
  596. /**
  597. * $inheritDoc
  598. */
  599. public function __clone()
  600. {
  601. \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []);
  602. $callParentClone }
  603. EOT;
  604. }
  605. /**
  606. * Generates decorated methods by picking those available in the parent class.
  607. *
  608. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  609. *
  610. * @return string
  611. */
  612. private function generateMethods(ClassMetadata $class)
  613. {
  614. $methods = '';
  615. $methodNames = [];
  616. $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC);
  617. $skippedMethods = [
  618. '__sleep' => true,
  619. '__clone' => true,
  620. '__wakeup' => true,
  621. '__get' => true,
  622. '__set' => true,
  623. '__isset' => true,
  624. ];
  625. foreach ($reflectionMethods as $method) {
  626. $name = $method->getName();
  627. if ($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($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 \ReflectionParameter[] $parameters
  742. *
  743. * @return string
  744. */
  745. private function buildParametersString(array $parameters)
  746. {
  747. $parameterDefinitions = [];
  748. /* @var $param \ReflectionParameter */
  749. foreach ($parameters as $param) {
  750. $parameterDefinition = '';
  751. if ($parameterType = $this->getParameterType($param)) {
  752. $parameterDefinition .= $parameterType . ' ';
  753. }
  754. if ($param->isPassedByReference()) {
  755. $parameterDefinition .= '&';
  756. }
  757. if ($param->isVariadic()) {
  758. $parameterDefinition .= '...';
  759. }
  760. $parameterDefinition .= '$' . $param->getName();
  761. if ($param->isDefaultValueAvailable()) {
  762. $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
  763. }
  764. $parameterDefinitions[] = $parameterDefinition;
  765. }
  766. return implode(', ', $parameterDefinitions);
  767. }
  768. /**
  769. * @param ClassMetadata $class
  770. * @param \ReflectionMethod $method
  771. * @param \ReflectionParameter $parameter
  772. *
  773. * @return string|null
  774. */
  775. private function getParameterType(\ReflectionParameter $parameter)
  776. {
  777. if ( ! $parameter->hasType()) {
  778. return null;
  779. }
  780. return $this->formatType($parameter->getType(), $parameter->getDeclaringFunction(), $parameter);
  781. }
  782. /**
  783. * @param \ReflectionParameter[] $parameters
  784. *
  785. * @return string[]
  786. */
  787. private function getParameterNamesForInvoke(array $parameters)
  788. {
  789. return array_map(
  790. function (\ReflectionParameter $parameter) {
  791. return '$' . $parameter->getName();
  792. },
  793. $parameters
  794. );
  795. }
  796. /**
  797. * @param \ReflectionParameter[] $parameters
  798. *
  799. * @return string[]
  800. */
  801. private function getParameterNamesForParentCall(array $parameters)
  802. {
  803. return array_map(
  804. function (\ReflectionParameter $parameter) {
  805. $name = '';
  806. if ($parameter->isVariadic()) {
  807. $name .= '...';
  808. }
  809. $name .= '$' . $parameter->getName();
  810. return $name;
  811. },
  812. $parameters
  813. );
  814. }
  815. /**
  816. * @param \ReflectionMethod $method
  817. *
  818. * @return string
  819. */
  820. private function getMethodReturnType(\ReflectionMethod $method)
  821. {
  822. if ( ! $method->hasReturnType()) {
  823. return '';
  824. }
  825. return ': ' . $this->formatType($method->getReturnType(), $method);
  826. }
  827. /**
  828. * @param \ReflectionMethod $method
  829. *
  830. * @return bool
  831. */
  832. private function shouldProxiedMethodReturn(\ReflectionMethod $method)
  833. {
  834. if ( ! $method->hasReturnType()) {
  835. return true;
  836. }
  837. return 'void' !== strtolower($this->formatType($method->getReturnType(), $method));
  838. }
  839. /**
  840. * @param \ReflectionType $type
  841. * @param \ReflectionMethod $method
  842. * @param \ReflectionParameter|null $parameter
  843. *
  844. * @return string
  845. */
  846. private function formatType(
  847. \ReflectionType $type,
  848. \ReflectionMethod $method,
  849. \ReflectionParameter $parameter = null
  850. ) {
  851. $name = (string) $type;
  852. $nameLower = strtolower($name);
  853. if ('self' === $nameLower) {
  854. $name = $method->getDeclaringClass()->getName();
  855. }
  856. if ('parent' === $nameLower) {
  857. $name = $method->getDeclaringClass()->getParentClass()->getName();
  858. }
  859. if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) {
  860. if (null !== $parameter) {
  861. throw UnexpectedValueException::invalidParameterTypeHint(
  862. $method->getDeclaringClass()->getName(),
  863. $method->getName(),
  864. $parameter->getName()
  865. );
  866. }
  867. throw UnexpectedValueException::invalidReturnTypeHint(
  868. $method->getDeclaringClass()->getName(),
  869. $method->getName()
  870. );
  871. }
  872. if ( ! $type->isBuiltin()) {
  873. $name = '\\' . $name;
  874. }
  875. if ($type->allowsNull()
  876. && (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue())
  877. ) {
  878. $name = '?' . $name;
  879. }
  880. return $name;
  881. }
  882. }