AbstractProxyFactory.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <?php
  2. namespace Doctrine\Common\Proxy;
  3. use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
  4. use Doctrine\Common\Proxy\Exception\OutOfBoundsException;
  5. use Doctrine\Common\Util\ClassUtils;
  6. use Doctrine\Persistence\Mapping\ClassMetadata;
  7. use Doctrine\Persistence\Mapping\ClassMetadataFactory;
  8. use function class_exists;
  9. use function file_exists;
  10. use function in_array;
  11. use function interface_exists;
  12. /**
  13. * Abstract factory for proxy objects.
  14. */
  15. abstract class AbstractProxyFactory
  16. {
  17. /**
  18. * Never autogenerate a proxy and rely that it was generated by some
  19. * process before deployment.
  20. */
  21. public const AUTOGENERATE_NEVER = 0;
  22. /**
  23. * Always generates a new proxy in every request.
  24. *
  25. * This is only sane during development.
  26. */
  27. public const AUTOGENERATE_ALWAYS = 1;
  28. /**
  29. * Autogenerate the proxy class when the proxy file does not exist.
  30. *
  31. * This strategy causes a file exists call whenever any proxy is used the
  32. * first time in a request.
  33. */
  34. public const AUTOGENERATE_FILE_NOT_EXISTS = 2;
  35. /**
  36. * Generate the proxy classes using eval().
  37. *
  38. * This strategy is only sane for development, and even then it gives me
  39. * the creeps a little.
  40. */
  41. public const AUTOGENERATE_EVAL = 3;
  42. private const AUTOGENERATE_MODES = [
  43. self::AUTOGENERATE_NEVER,
  44. self::AUTOGENERATE_ALWAYS,
  45. self::AUTOGENERATE_FILE_NOT_EXISTS,
  46. self::AUTOGENERATE_EVAL,
  47. ];
  48. /** @var ClassMetadataFactory */
  49. private $metadataFactory;
  50. /** @var ProxyGenerator the proxy generator responsible for creating the proxy classes/files. */
  51. private $proxyGenerator;
  52. /** @var int Whether to automatically (re)generate proxy classes. */
  53. private $autoGenerate;
  54. /** @var ProxyDefinition[] */
  55. private $definitions = [];
  56. /**
  57. * @param bool|int $autoGenerate
  58. *
  59. * @throws InvalidArgumentException When auto generate mode is not valid.
  60. */
  61. public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate)
  62. {
  63. $this->proxyGenerator = $proxyGenerator;
  64. $this->metadataFactory = $metadataFactory;
  65. $this->autoGenerate = (int) $autoGenerate;
  66. if (! in_array($this->autoGenerate, self::AUTOGENERATE_MODES, true)) {
  67. throw InvalidArgumentException::invalidAutoGenerateMode($autoGenerate);
  68. }
  69. }
  70. /**
  71. * Gets a reference proxy instance for the entity of the given type and identified by
  72. * the given identifier.
  73. *
  74. * @param string $className
  75. * @param array<mixed> $identifier
  76. *
  77. * @return Proxy
  78. *
  79. * @throws OutOfBoundsException
  80. */
  81. public function getProxy($className, array $identifier)
  82. {
  83. $definition = $this->definitions[$className] ?? $this->getProxyDefinition($className);
  84. $fqcn = $definition->proxyClassName;
  85. $proxy = new $fqcn($definition->initializer, $definition->cloner);
  86. foreach ($definition->identifierFields as $idField) {
  87. if (! isset($identifier[$idField])) {
  88. throw OutOfBoundsException::missingPrimaryKeyValue($className, $idField);
  89. }
  90. $definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]);
  91. }
  92. return $proxy;
  93. }
  94. /**
  95. * Generates proxy classes for all given classes.
  96. *
  97. * @param ClassMetadata[] $classes The classes (ClassMetadata instances)
  98. * for which to generate proxies.
  99. * @param string $proxyDir The target directory of the proxy classes. If not specified, the
  100. * directory configured on the Configuration of the EntityManager used
  101. * by this factory is used.
  102. *
  103. * @return int Number of generated proxies.
  104. */
  105. public function generateProxyClasses(array $classes, $proxyDir = null)
  106. {
  107. $generated = 0;
  108. foreach ($classes as $class) {
  109. if ($this->skipClass($class)) {
  110. continue;
  111. }
  112. $proxyFileName = $this->proxyGenerator->getProxyFileName($class->getName(), $proxyDir);
  113. $this->proxyGenerator->generateProxyClass($class, $proxyFileName);
  114. $generated += 1;
  115. }
  116. return $generated;
  117. }
  118. /**
  119. * Reset initialization/cloning logic for an un-initialized proxy
  120. *
  121. * @return Proxy
  122. *
  123. * @throws InvalidArgumentException
  124. */
  125. public function resetUninitializedProxy(Proxy $proxy)
  126. {
  127. if ($proxy->__isInitialized()) {
  128. throw InvalidArgumentException::unitializedProxyExpected($proxy);
  129. }
  130. $className = ClassUtils::getClass($proxy);
  131. $definition = $this->definitions[$className] ?? $this->getProxyDefinition($className);
  132. $proxy->__setInitializer($definition->initializer);
  133. $proxy->__setCloner($definition->cloner);
  134. return $proxy;
  135. }
  136. /**
  137. * Get a proxy definition for the given class name.
  138. *
  139. * @param string $className
  140. *
  141. * @return ProxyDefinition
  142. *
  143. * @psalm-param class-string $className
  144. */
  145. private function getProxyDefinition($className)
  146. {
  147. $classMetadata = $this->metadataFactory->getMetadataFor($className);
  148. $className = $classMetadata->getName(); // aliases and case sensitivity
  149. $this->definitions[$className] = $this->createProxyDefinition($className);
  150. $proxyClassName = $this->definitions[$className]->proxyClassName;
  151. if (! class_exists($proxyClassName, false)) {
  152. $fileName = $this->proxyGenerator->getProxyFileName($className);
  153. switch ($this->autoGenerate) {
  154. case self::AUTOGENERATE_NEVER:
  155. require $fileName;
  156. break;
  157. case self::AUTOGENERATE_FILE_NOT_EXISTS:
  158. if (! file_exists($fileName)) {
  159. $this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
  160. }
  161. require $fileName;
  162. break;
  163. case self::AUTOGENERATE_ALWAYS:
  164. $this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
  165. require $fileName;
  166. break;
  167. case self::AUTOGENERATE_EVAL:
  168. $this->proxyGenerator->generateProxyClass($classMetadata, false);
  169. break;
  170. }
  171. }
  172. return $this->definitions[$className];
  173. }
  174. /**
  175. * Determine if this class should be skipped during proxy generation.
  176. *
  177. * @return bool
  178. */
  179. abstract protected function skipClass(ClassMetadata $metadata);
  180. /**
  181. * @param string $className
  182. *
  183. * @return ProxyDefinition
  184. *
  185. * @psalm-param class-string $className
  186. */
  187. abstract protected function createProxyDefinition($className);
  188. }
  189. interface_exists(ClassMetadata::class);
  190. interface_exists(ClassMetadataFactory::class);