vendor/doctrine/common/src/Proxy/AbstractProxyFactory.php line 184

Open in your IDE?
  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 filemtime;
  11. use function in_array;
  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. /**
  43. * Autogenerate the proxy class when the proxy file does not exist or
  44. * when the proxied file changed.
  45. *
  46. * This strategy causes a file_exists() call whenever any proxy is used the
  47. * first time in a request. When the proxied file is changed, the proxy will
  48. * be updated.
  49. */
  50. public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4;
  51. private const AUTOGENERATE_MODES = [
  52. self::AUTOGENERATE_NEVER,
  53. self::AUTOGENERATE_ALWAYS,
  54. self::AUTOGENERATE_FILE_NOT_EXISTS,
  55. self::AUTOGENERATE_EVAL,
  56. self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED,
  57. ];
  58. /** @var ClassMetadataFactory */
  59. private $metadataFactory;
  60. /** @var ProxyGenerator the proxy generator responsible for creating the proxy classes/files. */
  61. private $proxyGenerator;
  62. /** @var int Whether to automatically (re)generate proxy classes. */
  63. private $autoGenerate;
  64. /** @var ProxyDefinition[] */
  65. private $definitions = [];
  66. /**
  67. * @param bool|int $autoGenerate
  68. *
  69. * @throws InvalidArgumentException When auto generate mode is not valid.
  70. */
  71. public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate)
  72. {
  73. $this->proxyGenerator = $proxyGenerator;
  74. $this->metadataFactory = $metadataFactory;
  75. $this->autoGenerate = (int) $autoGenerate;
  76. if (! in_array($this->autoGenerate, self::AUTOGENERATE_MODES, true)) {
  77. throw InvalidArgumentException::invalidAutoGenerateMode($autoGenerate);
  78. }
  79. }
  80. /**
  81. * Gets a reference proxy instance for the entity of the given type and identified by
  82. * the given identifier.
  83. *
  84. * @param string $className
  85. * @param array<mixed> $identifier
  86. *
  87. * @return Proxy
  88. *
  89. * @throws OutOfBoundsException
  90. */
  91. public function getProxy($className, array $identifier)
  92. {
  93. $definition = $this->definitions[$className] ?? $this->getProxyDefinition($className);
  94. $fqcn = $definition->proxyClassName;
  95. $proxy = new $fqcn($definition->initializer, $definition->cloner);
  96. foreach ($definition->identifierFields as $idField) {
  97. if (! isset($identifier[$idField])) {
  98. throw OutOfBoundsException::missingPrimaryKeyValue($className, $idField);
  99. }
  100. $definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]);
  101. }
  102. return $proxy;
  103. }
  104. /**
  105. * Generates proxy classes for all given classes.
  106. *
  107. * @param ClassMetadata[] $classes The classes (ClassMetadata instances)
  108. * for which to generate proxies.
  109. * @param string $proxyDir The target directory of the proxy classes. If not specified, the
  110. * directory configured on the Configuration of the EntityManager used
  111. * by this factory is used.
  112. *
  113. * @return int Number of generated proxies.
  114. */
  115. public function generateProxyClasses(array $classes, $proxyDir = null)
  116. {
  117. $generated = 0;
  118. foreach ($classes as $class) {
  119. if ($this->skipClass($class)) {
  120. continue;
  121. }
  122. $proxyFileName = $this->proxyGenerator->getProxyFileName($class->getName(), $proxyDir);
  123. $this->proxyGenerator->generateProxyClass($class, $proxyFileName);
  124. $generated += 1;
  125. }
  126. return $generated;
  127. }
  128. /**
  129. * Reset initialization/cloning logic for an un-initialized proxy
  130. *
  131. * @return Proxy
  132. *
  133. * @throws InvalidArgumentException
  134. */
  135. public function resetUninitializedProxy(Proxy $proxy)
  136. {
  137. if ($proxy->__isInitialized()) {
  138. throw InvalidArgumentException::unitializedProxyExpected($proxy);
  139. }
  140. $className = ClassUtils::getClass($proxy);
  141. $definition = $this->definitions[$className] ?? $this->getProxyDefinition($className);
  142. $proxy->__setInitializer($definition->initializer);
  143. $proxy->__setCloner($definition->cloner);
  144. return $proxy;
  145. }
  146. /**
  147. * Get a proxy definition for the given class name.
  148. *
  149. * @param string $className
  150. * @psalm-param class-string $className
  151. *
  152. * @return ProxyDefinition
  153. */
  154. private function getProxyDefinition($className)
  155. {
  156. $classMetadata = $this->metadataFactory->getMetadataFor($className);
  157. $className = $classMetadata->getName(); // aliases and case sensitivity
  158. $this->definitions[$className] = $this->createProxyDefinition($className);
  159. $proxyClassName = $this->definitions[$className]->proxyClassName;
  160. if (! class_exists($proxyClassName, false)) {
  161. $fileName = $this->proxyGenerator->getProxyFileName($className);
  162. switch ($this->autoGenerate) {
  163. case self::AUTOGENERATE_NEVER:
  164. require $fileName;
  165. break;
  166. case self::AUTOGENERATE_FILE_NOT_EXISTS:
  167. if (! file_exists($fileName)) {
  168. $this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
  169. }
  170. require $fileName;
  171. break;
  172. case self::AUTOGENERATE_ALWAYS:
  173. $this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
  174. require $fileName;
  175. break;
  176. case self::AUTOGENERATE_EVAL:
  177. $this->proxyGenerator->generateProxyClass($classMetadata, false);
  178. break;
  179. case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED:
  180. if (! file_exists($fileName) || filemtime($fileName) < filemtime($classMetadata->getReflectionClass()->getFileName())) {
  181. $this->proxyGenerator->generateProxyClass($classMetadata, $fileName);
  182. }
  183. require $fileName;
  184. break;
  185. }
  186. }
  187. return $this->definitions[$className];
  188. }
  189. /**
  190. * Determine if this class should be skipped during proxy generation.
  191. *
  192. * @return bool
  193. */
  194. abstract protected function skipClass(ClassMetadata $metadata);
  195. /**
  196. * @param string $className
  197. * @psalm-param class-string $className
  198. *
  199. * @return ProxyDefinition
  200. */
  201. abstract protected function createProxyDefinition($className);
  202. }