ProxyGenerator.php 35 KB

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