PageRenderTime 42ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Symfony/Component/Config/Resource/ClassExistenceResource.php

https://github.com/FabienD/symfony
PHP | 231 lines | 181 code | 23 blank | 27 comment | 26 complexity | 94d7a1dd03a786963068c265d88277c5 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Config\Resource;
  11. /**
  12. * ClassExistenceResource represents a class existence.
  13. * Freshness is only evaluated against resource existence.
  14. *
  15. * The resource must be a fully-qualified class name.
  16. *
  17. * @author Fabien Potencier <fabien@symfony.com>
  18. *
  19. * @final
  20. */
  21. class ClassExistenceResource implements SelfCheckingResourceInterface
  22. {
  23. private string $resource;
  24. private ?array $exists = null;
  25. private static int $autoloadLevel = 0;
  26. private static ?string $autoloadedClass = null;
  27. private static array $existsCache = [];
  28. /**
  29. * @param string $resource The fully-qualified class name
  30. * @param bool|null $exists Boolean when the existency check has already been done
  31. */
  32. public function __construct(string $resource, bool $exists = null)
  33. {
  34. $this->resource = $resource;
  35. if (null !== $exists) {
  36. $this->exists = [$exists, null];
  37. }
  38. }
  39. public function __toString(): string
  40. {
  41. return $this->resource;
  42. }
  43. public function getResource(): string
  44. {
  45. return $this->resource;
  46. }
  47. /**
  48. * {@inheritdoc}
  49. *
  50. * @throws \ReflectionException when a parent class/interface/trait is not found
  51. */
  52. public function isFresh(int $timestamp): bool
  53. {
  54. $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
  55. if (null !== $exists = &self::$existsCache[$this->resource]) {
  56. if ($loaded) {
  57. $exists = [true, null];
  58. } elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) {
  59. throw new \ReflectionException($exists[1]);
  60. }
  61. } elseif ([false, null] === $exists = [$loaded, null]) {
  62. if (!self::$autoloadLevel++) {
  63. spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
  64. }
  65. $autoloadedClass = self::$autoloadedClass;
  66. self::$autoloadedClass = ltrim($this->resource, '\\');
  67. try {
  68. $exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
  69. } catch (\Exception $e) {
  70. $exists[1] = $e->getMessage();
  71. try {
  72. self::throwOnRequiredClass($this->resource, $e);
  73. } catch (\ReflectionException $e) {
  74. if (0 >= $timestamp) {
  75. throw $e;
  76. }
  77. }
  78. } catch (\Throwable $e) {
  79. $exists[1] = $e->getMessage();
  80. throw $e;
  81. } finally {
  82. self::$autoloadedClass = $autoloadedClass;
  83. if (!--self::$autoloadLevel) {
  84. spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
  85. }
  86. }
  87. }
  88. if (null === $this->exists) {
  89. $this->exists = $exists;
  90. }
  91. return $this->exists[0] xor !$exists[0];
  92. }
  93. /**
  94. * @internal
  95. */
  96. public function __sleep(): array
  97. {
  98. if (null === $this->exists) {
  99. $this->isFresh(0);
  100. }
  101. return ['resource', 'exists'];
  102. }
  103. /**
  104. * @internal
  105. */
  106. public function __wakeup()
  107. {
  108. if (\is_bool($this->exists)) {
  109. $this->exists = [$this->exists, null];
  110. }
  111. }
  112. /**
  113. * Throws a reflection exception when the passed class does not exist but is required.
  114. *
  115. * A class is considered "not required" when it's loaded as part of a "class_exists" or similar check.
  116. *
  117. * This function can be used as an autoload function to throw a reflection
  118. * exception if the class was not found by previous autoload functions.
  119. *
  120. * A previous exception can be passed. In this case, the class is considered as being
  121. * required totally, so if it doesn't exist, a reflection exception is always thrown.
  122. * If it exists, the previous exception is rethrown.
  123. *
  124. * @throws \ReflectionException
  125. *
  126. * @internal
  127. */
  128. public static function throwOnRequiredClass(string $class, \Exception $previous = null)
  129. {
  130. // If the passed class is the resource being checked, we shouldn't throw.
  131. if (null === $previous && self::$autoloadedClass === $class) {
  132. return;
  133. }
  134. if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) {
  135. if (null !== $previous) {
  136. throw $previous;
  137. }
  138. return;
  139. }
  140. if ($previous instanceof \ReflectionException) {
  141. throw $previous;
  142. }
  143. $message = sprintf('Class "%s" not found.', $class);
  144. if (self::$autoloadedClass !== $class) {
  145. $message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0);
  146. }
  147. if (null !== $previous) {
  148. $message = $previous->getMessage();
  149. }
  150. $e = new \ReflectionException($message, 0, $previous);
  151. if (null !== $previous) {
  152. throw $e;
  153. }
  154. $trace = debug_backtrace();
  155. $autoloadFrame = [
  156. 'function' => 'spl_autoload_call',
  157. 'args' => [$class],
  158. ];
  159. if (isset($trace[1])) {
  160. $callerFrame = $trace[1];
  161. $i = 2;
  162. } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
  163. $callerFrame = $trace[++$i];
  164. } else {
  165. throw $e;
  166. }
  167. if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
  168. switch ($callerFrame['function']) {
  169. case 'get_class_methods':
  170. case 'get_class_vars':
  171. case 'get_parent_class':
  172. case 'is_a':
  173. case 'is_subclass_of':
  174. case 'class_exists':
  175. case 'class_implements':
  176. case 'class_parents':
  177. case 'trait_exists':
  178. case 'defined':
  179. case 'interface_exists':
  180. case 'method_exists':
  181. case 'property_exists':
  182. case 'is_callable':
  183. return;
  184. }
  185. $props = [
  186. 'file' => $callerFrame['file'] ?? null,
  187. 'line' => $callerFrame['line'] ?? null,
  188. 'trace' => \array_slice($trace, 1 + $i),
  189. ];
  190. foreach ($props as $p => $v) {
  191. if (null !== $v) {
  192. $r = new \ReflectionProperty(\Exception::class, $p);
  193. $r->setValue($e, $v);
  194. }
  195. }
  196. }
  197. throw $e;
  198. }
  199. }