ProxyGenerator.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928
  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\Util\ClassUtils;
  22. use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
  23. use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
  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{1,}%s\s?\(\)\s{1,})\s{0,}{\s{0,}return\s{0,}\$this->%s;\s{0,}})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 = array(
  56. 'baseProxyInterface' => 'Doctrine\Common\Proxy\Proxy',
  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 = array(<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\', array());
  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 = array();
  230. foreach ($placeholderMatches as $placeholder => $name) {
  231. $placeholders[$placeholder] = isset($this->placeholders[$name])
  232. ? $this->placeholders[$name]
  233. : array($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();
  251. }
  252. if ( ! is_writable($parentDirectory)) {
  253. throw UnexpectedValueException::proxyDirectoryNotWritable();
  254. }
  255. $tmpFileName = $fileName . '.' . uniqid('', true);
  256. file_put_contents($tmpFileName, $proxyCode);
  257. rename($tmpFileName, $fileName);
  258. }
  259. /**
  260. * Generates the proxy short class name to be used in the template.
  261. *
  262. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  263. *
  264. * @return string
  265. */
  266. private function generateProxyShortClassName(ClassMetadata $class)
  267. {
  268. $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
  269. $parts = explode('\\', strrev($proxyClassName), 2);
  270. return strrev($parts[0]);
  271. }
  272. /**
  273. * Generates the proxy namespace.
  274. *
  275. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  276. *
  277. * @return string
  278. */
  279. private function generateNamespace(ClassMetadata $class)
  280. {
  281. $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
  282. $parts = explode('\\', strrev($proxyClassName), 2);
  283. return strrev($parts[1]);
  284. }
  285. /**
  286. * Generates the original class name.
  287. *
  288. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  289. *
  290. * @return string
  291. */
  292. private function generateClassName(ClassMetadata $class)
  293. {
  294. return ltrim($class->getName(), '\\');
  295. }
  296. /**
  297. * Generates the array representation of lazy loaded public properties and their default values.
  298. *
  299. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  300. *
  301. * @return string
  302. */
  303. private function generateLazyPropertiesDefaults(ClassMetadata $class)
  304. {
  305. $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
  306. $values = array();
  307. foreach ($lazyPublicProperties as $key => $value) {
  308. $values[] = var_export($key, true) . ' => ' . var_export($value, true);
  309. }
  310. return implode(', ', $values);
  311. }
  312. /**
  313. * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values).
  314. *
  315. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  316. *
  317. * @return string
  318. */
  319. private function generateConstructorImpl(ClassMetadata $class)
  320. {
  321. $constructorImpl = <<<'EOT'
  322. /**
  323. * @param \Closure $initializer
  324. * @param \Closure $cloner
  325. */
  326. public function __construct($initializer = null, $cloner = null)
  327. {
  328. EOT;
  329. $toUnset = array();
  330. foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) {
  331. $toUnset[] = '$this->' . $lazyPublicProperty;
  332. }
  333. $constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n")
  334. . <<<'EOT'
  335. $this->__initializer__ = $initializer;
  336. $this->__cloner__ = $cloner;
  337. }
  338. EOT;
  339. return $constructorImpl;
  340. }
  341. /**
  342. * Generates the magic getter invoked when lazy loaded public properties are requested.
  343. *
  344. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  345. *
  346. * @return string
  347. */
  348. private function generateMagicGet(ClassMetadata $class)
  349. {
  350. $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
  351. $reflectionClass = $class->getReflectionClass();
  352. $hasParentGet = false;
  353. $returnReference = '';
  354. $inheritDoc = '';
  355. if ($reflectionClass->hasMethod('__get')) {
  356. $hasParentGet = true;
  357. $inheritDoc = '{@inheritDoc}';
  358. if ($reflectionClass->getMethod('__get')->returnsReference()) {
  359. $returnReference = '& ';
  360. }
  361. }
  362. if (empty($lazyPublicProperties) && ! $hasParentGet) {
  363. return '';
  364. }
  365. $magicGet = <<<EOT
  366. /**
  367. * $inheritDoc
  368. * @param string \$name
  369. */
  370. public function {$returnReference}__get(\$name)
  371. {
  372. EOT;
  373. if ( ! empty($lazyPublicProperties)) {
  374. $magicGet .= <<<'EOT'
  375. if (array_key_exists($name, $this->__getLazyProperties())) {
  376. $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name));
  377. return $this->$name;
  378. }
  379. EOT;
  380. }
  381. if ($hasParentGet) {
  382. $magicGet .= <<<'EOT'
  383. $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name));
  384. return parent::__get($name);
  385. EOT;
  386. } else {
  387. $magicGet .= <<<'EOT'
  388. trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE);
  389. EOT;
  390. }
  391. $magicGet .= " }";
  392. return $magicGet;
  393. }
  394. /**
  395. * Generates the magic setter (currently unused).
  396. *
  397. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  398. *
  399. * @return string
  400. */
  401. private function generateMagicSet(ClassMetadata $class)
  402. {
  403. $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
  404. $hasParentSet = $class->getReflectionClass()->hasMethod('__set');
  405. if (empty($lazyPublicProperties) && ! $hasParentSet) {
  406. return '';
  407. }
  408. $inheritDoc = $hasParentSet ? '{@inheritDoc}' : '';
  409. $magicSet = <<<EOT
  410. /**
  411. * $inheritDoc
  412. * @param string \$name
  413. * @param mixed \$value
  414. */
  415. public function __set(\$name, \$value)
  416. {
  417. EOT;
  418. if ( ! empty($lazyPublicProperties)) {
  419. $magicSet .= <<<'EOT'
  420. if (array_key_exists($name, $this->__getLazyProperties())) {
  421. $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value));
  422. $this->$name = $value;
  423. return;
  424. }
  425. EOT;
  426. }
  427. if ($hasParentSet) {
  428. $magicSet .= <<<'EOT'
  429. $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value));
  430. return parent::__set($name, $value);
  431. EOT;
  432. } else {
  433. $magicSet .= " \$this->\$name = \$value;";
  434. }
  435. $magicSet .= "\n }";
  436. return $magicSet;
  437. }
  438. /**
  439. * Generates the magic issetter invoked when lazy loaded public properties are checked against isset().
  440. *
  441. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  442. *
  443. * @return string
  444. */
  445. private function generateMagicIsset(ClassMetadata $class)
  446. {
  447. $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
  448. $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset');
  449. if (empty($lazyPublicProperties) && ! $hasParentIsset) {
  450. return '';
  451. }
  452. $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : '';
  453. $magicIsset = <<<EOT
  454. /**
  455. * $inheritDoc
  456. * @param string \$name
  457. * @return boolean
  458. */
  459. public function __isset(\$name)
  460. {
  461. EOT;
  462. if ( ! empty($lazyPublicProperties)) {
  463. $magicIsset .= <<<'EOT'
  464. if (array_key_exists($name, $this->__getLazyProperties())) {
  465. $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name));
  466. return isset($this->$name);
  467. }
  468. EOT;
  469. }
  470. if ($hasParentIsset) {
  471. $magicIsset .= <<<'EOT'
  472. $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name));
  473. return parent::__isset($name);
  474. EOT;
  475. } else {
  476. $magicIsset .= " return false;";
  477. }
  478. return $magicIsset . "\n }";
  479. }
  480. /**
  481. * Generates implementation for the `__sleep` method of proxies.
  482. *
  483. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  484. *
  485. * @return string
  486. */
  487. private function generateSleepImpl(ClassMetadata $class)
  488. {
  489. $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep');
  490. $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : '';
  491. $sleepImpl = <<<EOT
  492. /**
  493. * $inheritDoc
  494. * @return array
  495. */
  496. public function __sleep()
  497. {
  498. EOT;
  499. if ($hasParentSleep) {
  500. return $sleepImpl . <<<'EOT'
  501. $properties = array_merge(array('__isInitialized__'), parent::__sleep());
  502. if ($this->__isInitialized__) {
  503. $properties = array_diff($properties, array_keys($this->__getLazyProperties()));
  504. }
  505. return $properties;
  506. }
  507. EOT;
  508. }
  509. $allProperties = array('__isInitialized__');
  510. /* @var $prop \ReflectionProperty */
  511. foreach ($class->getReflectionClass()->getProperties() as $prop) {
  512. $allProperties[] = $prop->isPrivate()
  513. ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName()
  514. : $prop->getName();
  515. }
  516. $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
  517. $protectedProperties = array_diff($allProperties, $lazyPublicProperties);
  518. foreach ($allProperties as &$property) {
  519. $property = var_export($property, true);
  520. }
  521. foreach ($protectedProperties as &$property) {
  522. $property = var_export($property, true);
  523. }
  524. $allProperties = implode(', ', $allProperties);
  525. $protectedProperties = implode(', ', $protectedProperties);
  526. return $sleepImpl . <<<EOT
  527. if (\$this->__isInitialized__) {
  528. return array($allProperties);
  529. }
  530. return array($protectedProperties);
  531. }
  532. EOT;
  533. }
  534. /**
  535. * Generates implementation for the `__wakeup` method of proxies.
  536. *
  537. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  538. *
  539. * @return string
  540. */
  541. private function generateWakeupImpl(ClassMetadata $class)
  542. {
  543. $unsetPublicProperties = array();
  544. $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup');
  545. foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) {
  546. $unsetPublicProperties[] = '$this->' . $lazyPublicProperty;
  547. }
  548. $shortName = $this->generateProxyShortClassName($class);
  549. $inheritDoc = $hasWakeup ? '{@inheritDoc}' : '';
  550. $wakeupImpl = <<<EOT
  551. /**
  552. * $inheritDoc
  553. */
  554. public function __wakeup()
  555. {
  556. if ( ! \$this->__isInitialized__) {
  557. \$this->__initializer__ = function ($shortName \$proxy) {
  558. \$proxy->__setInitializer(null);
  559. \$proxy->__setCloner(null);
  560. \$existingProperties = get_object_vars(\$proxy);
  561. foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) {
  562. if ( ! array_key_exists(\$property, \$existingProperties)) {
  563. \$proxy->\$property = \$defaultValue;
  564. }
  565. }
  566. };
  567. EOT;
  568. if ( ! empty($unsetPublicProperties)) {
  569. $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");";
  570. }
  571. $wakeupImpl .= "\n }";
  572. if ($hasWakeup) {
  573. $wakeupImpl .= "\n parent::__wakeup();";
  574. }
  575. $wakeupImpl .= "\n }";
  576. return $wakeupImpl;
  577. }
  578. /**
  579. * Generates implementation for the `__clone` method of proxies.
  580. *
  581. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  582. *
  583. * @return string
  584. */
  585. private function generateCloneImpl(ClassMetadata $class)
  586. {
  587. $hasParentClone = $class->getReflectionClass()->hasMethod('__clone');
  588. $inheritDoc = $hasParentClone ? '{@inheritDoc}' : '';
  589. $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : '';
  590. return <<<EOT
  591. /**
  592. * $inheritDoc
  593. */
  594. public function __clone()
  595. {
  596. \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', array());
  597. $callParentClone }
  598. EOT;
  599. }
  600. /**
  601. * Generates decorated methods by picking those available in the parent class.
  602. *
  603. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  604. *
  605. * @return string
  606. */
  607. private function generateMethods(ClassMetadata $class)
  608. {
  609. $methods = '';
  610. $methodNames = array();
  611. $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC);
  612. $skippedMethods = array(
  613. '__sleep' => true,
  614. '__clone' => true,
  615. '__wakeup' => true,
  616. '__get' => true,
  617. '__set' => true,
  618. '__isset' => true,
  619. );
  620. foreach ($reflectionMethods as $method) {
  621. $name = $method->getName();
  622. if (
  623. $method->isConstructor() ||
  624. isset($skippedMethods[strtolower($name)]) ||
  625. isset($methodNames[$name]) ||
  626. $method->isFinal() ||
  627. $method->isStatic() ||
  628. ( ! $method->isPublic())
  629. ) {
  630. continue;
  631. }
  632. $methodNames[$name] = true;
  633. $methods .= "\n /**\n"
  634. . " * {@inheritDoc}\n"
  635. . " */\n"
  636. . ' public function ';
  637. if ($method->returnsReference()) {
  638. $methods .= '&';
  639. }
  640. $methods .= $name . '(';
  641. $firstParam = true;
  642. $parameterString = '';
  643. $argumentString = '';
  644. $parameters = array();
  645. foreach ($method->getParameters() as $param) {
  646. if ($firstParam) {
  647. $firstParam = false;
  648. } else {
  649. $parameterString .= ', ';
  650. $argumentString .= ', ';
  651. }
  652. try {
  653. $paramClass = $param->getClass();
  654. } catch (\ReflectionException $previous) {
  655. throw UnexpectedValueException::invalidParameterTypeHint(
  656. $class->getName(),
  657. $method->getName(),
  658. $param->getName(),
  659. $previous
  660. );
  661. }
  662. // We need to pick the type hint class too
  663. if (null !== $paramClass) {
  664. $parameterString .= '\\' . $paramClass->getName() . ' ';
  665. } elseif ($param->isArray()) {
  666. $parameterString .= 'array ';
  667. } elseif (method_exists($param, 'isCallable') && $param->isCallable()) {
  668. $parameterString .= 'callable ';
  669. }
  670. if ($param->isPassedByReference()) {
  671. $parameterString .= '&';
  672. }
  673. $parameters[] = '$' . $param->getName();
  674. $parameterString .= '$' . $param->getName();
  675. $argumentString .= '$' . $param->getName();
  676. if ($param->isDefaultValueAvailable()) {
  677. $parameterString .= ' = ' . var_export($param->getDefaultValue(), true);
  678. }
  679. }
  680. $methods .= $parameterString . ')';
  681. $methods .= "\n" . ' {' . "\n";
  682. if ($this->isShortIdentifierGetter($method, $class)) {
  683. $identifier = lcfirst(substr($name, 3));
  684. $fieldType = $class->getTypeOfField($identifier);
  685. $cast = in_array($fieldType, array('integer', 'smallint')) ? '(int) ' : '';
  686. $methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
  687. $methods .= ' return ' . $cast . ' parent::' . $method->getName() . "();\n";
  688. $methods .= ' }' . "\n\n";
  689. }
  690. $methods .= "\n \$this->__initializer__ "
  691. . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true)
  692. . ", array(" . implode(', ', $parameters) . "));"
  693. . "\n\n return parent::" . $name . '(' . $argumentString . ');'
  694. . "\n" . ' }' . "\n";
  695. }
  696. return $methods;
  697. }
  698. /**
  699. * Generates the Proxy file name.
  700. *
  701. * @param string $className
  702. * @param string $baseDirectory Optional base directory for proxy file name generation.
  703. * If not specified, the directory configured on the Configuration of the
  704. * EntityManager will be used by this factory.
  705. *
  706. * @return string
  707. */
  708. public function getProxyFileName($className, $baseDirectory = null)
  709. {
  710. $baseDirectory = $baseDirectory ?: $this->proxyDirectory;
  711. return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER
  712. . str_replace('\\', '', $className) . '.php';
  713. }
  714. /**
  715. * Checks if the method is a short identifier getter.
  716. *
  717. * What does this mean? For proxy objects the identifier is already known,
  718. * however accessing the getter for this identifier usually triggers the
  719. * lazy loading, leading to a query that may not be necessary if only the
  720. * ID is interesting for the userland code (for example in views that
  721. * generate links to the entity, but do not display anything else).
  722. *
  723. * @param \ReflectionMethod $method
  724. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  725. *
  726. * @return boolean
  727. */
  728. private function isShortIdentifierGetter($method, ClassMetadata $class)
  729. {
  730. $identifier = lcfirst(substr($method->getName(), 3));
  731. $startLine = $method->getStartLine();
  732. $endLine = $method->getEndLine();
  733. $cheapCheck = (
  734. $method->getNumberOfParameters() == 0
  735. && substr($method->getName(), 0, 3) == 'get'
  736. && in_array($identifier, $class->getIdentifier(), true)
  737. && $class->hasField($identifier)
  738. && (($endLine - $startLine) <= 4)
  739. );
  740. if ($cheapCheck) {
  741. $code = file($method->getDeclaringClass()->getFileName());
  742. $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1)));
  743. $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
  744. if (preg_match($pattern, $code)) {
  745. return true;
  746. }
  747. }
  748. return false;
  749. }
  750. /**
  751. * Generates the list of public properties to be lazy loaded, with their default values.
  752. *
  753. * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
  754. *
  755. * @return mixed[]
  756. */
  757. private function getLazyLoadedPublicProperties(ClassMetadata $class)
  758. {
  759. $defaultProperties = $class->getReflectionClass()->getDefaultProperties();
  760. $properties = array();
  761. foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
  762. $name = $property->getName();
  763. if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) {
  764. $properties[$name] = $defaultProperties[$name];
  765. }
  766. }
  767. return $properties;
  768. }
  769. }