vendor/symfony/config/Resource/ClassExistenceResource.php line 76

Open in your IDE?
  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 existence 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. * @throws \ReflectionException when a parent class/interface/trait is not found
  49. */
  50. public function isFresh(int $timestamp): bool
  51. {
  52. $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
  53. if (null !== $exists = &self::$existsCache[$this->resource]) {
  54. if ($loaded) {
  55. $exists = [true, null];
  56. } elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) {
  57. throw new \ReflectionException($exists[1]);
  58. }
  59. } elseif ([false, null] === $exists = [$loaded, null]) {
  60. if (!self::$autoloadLevel++) {
  61. spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
  62. }
  63. $autoloadedClass = self::$autoloadedClass;
  64. self::$autoloadedClass = ltrim($this->resource, '\\');
  65. try {
  66. $exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
  67. } catch (\Exception $e) {
  68. $exists[1] = $e->getMessage();
  69. try {
  70. self::throwOnRequiredClass($this->resource, $e);
  71. } catch (\ReflectionException $e) {
  72. if (0 >= $timestamp) {
  73. throw $e;
  74. }
  75. }
  76. } catch (\Throwable $e) {
  77. $exists[1] = $e->getMessage();
  78. throw $e;
  79. } finally {
  80. self::$autoloadedClass = $autoloadedClass;
  81. if (!--self::$autoloadLevel) {
  82. spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
  83. }
  84. }
  85. }
  86. $this->exists ??= $exists;
  87. return $this->exists[0] xor !$exists[0];
  88. }
  89. public function __serialize(): array
  90. {
  91. if (null === $this->exists) {
  92. $this->isFresh(0);
  93. }
  94. return [
  95. 'resource' => $this->resource,
  96. 'exists' => $this->exists,
  97. ];
  98. }
  99. public function __unserialize(array $data): void
  100. {
  101. $this->resource = array_shift($data);
  102. $this->exists = array_shift($data);
  103. if (\is_bool($this->exists)) {
  104. $this->exists = [$this->exists, null];
  105. }
  106. }
  107. /**
  108. * Throws a reflection exception when the passed class does not exist but is required.
  109. *
  110. * A class is considered "not required" when it's loaded as part of a "class_exists" or similar check.
  111. *
  112. * This function can be used as an autoload function to throw a reflection
  113. * exception if the class was not found by previous autoload functions.
  114. *
  115. * A previous exception can be passed. In this case, the class is considered as being
  116. * required totally, so if it doesn't exist, a reflection exception is always thrown.
  117. * If it exists, the previous exception is rethrown.
  118. *
  119. * @throws \ReflectionException
  120. *
  121. * @internal
  122. */
  123. public static function throwOnRequiredClass(string $class, ?\Exception $previous = null): void
  124. {
  125. // If the passed class is the resource being checked, we shouldn't throw.
  126. if (null === $previous && self::$autoloadedClass === $class) {
  127. return;
  128. }
  129. if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) {
  130. if (null !== $previous) {
  131. throw $previous;
  132. }
  133. return;
  134. }
  135. if ($previous instanceof \ReflectionException) {
  136. throw $previous;
  137. }
  138. $message = \sprintf('Class "%s" not found.', $class);
  139. if ($class !== (self::$autoloadedClass ?? $class)) {
  140. $message = substr_replace($message, \sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0);
  141. }
  142. if (null !== $previous) {
  143. $message = $previous->getMessage();
  144. }
  145. $e = new \ReflectionException($message, 0, $previous);
  146. if (null !== $previous) {
  147. throw $e;
  148. }
  149. $trace = debug_backtrace();
  150. $autoloadFrame = [
  151. 'function' => 'spl_autoload_call',
  152. 'args' => [$class],
  153. ];
  154. if (isset($trace[1])) {
  155. $callerFrame = $trace[1];
  156. $i = 2;
  157. } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
  158. $callerFrame = $trace[++$i];
  159. } else {
  160. throw $e;
  161. }
  162. if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
  163. switch ($callerFrame['function']) {
  164. case 'get_class_methods':
  165. case 'get_class_vars':
  166. case 'get_parent_class':
  167. case 'is_a':
  168. case 'is_subclass_of':
  169. case 'class_exists':
  170. case 'class_implements':
  171. case 'class_parents':
  172. case 'trait_exists':
  173. case 'defined':
  174. case 'interface_exists':
  175. case 'method_exists':
  176. case 'property_exists':
  177. case 'is_callable':
  178. return;
  179. }
  180. $props = [
  181. 'file' => $callerFrame['file'] ?? null,
  182. 'line' => $callerFrame['line'] ?? null,
  183. 'trace' => \array_slice($trace, 1 + $i),
  184. ];
  185. foreach ($props as $p => $v) {
  186. if (null !== $v) {
  187. $r = new \ReflectionProperty(\Exception::class, $p);
  188. $r->setValue($e, $v);
  189. }
  190. }
  191. }
  192. throw $e;
  193. }
  194. }