vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PsrCachedReader.php line 80

Open in your IDE?
  1. <?php
  2. namespace Doctrine\Common\Annotations;
  3. use Psr\Cache\CacheItemPoolInterface;
  4. use ReflectionClass;
  5. use ReflectionMethod;
  6. use ReflectionProperty;
  7. use Reflector;
  8. use function array_map;
  9. use function array_merge;
  10. use function assert;
  11. use function filemtime;
  12. use function is_file;
  13. use function max;
  14. use function rawurlencode;
  15. use function time;
  16. /**
  17. * A cache aware annotation reader.
  18. */
  19. final class PsrCachedReader implements Reader
  20. {
  21. /** @var Reader */
  22. private $delegate;
  23. /** @var CacheItemPoolInterface */
  24. private $cache;
  25. /** @var bool */
  26. private $debug;
  27. /** @var array<string, array<object>> */
  28. private $loadedAnnotations = [];
  29. /** @var int[] */
  30. private $loadedFilemtimes = [];
  31. public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false)
  32. {
  33. $this->delegate = $reader;
  34. $this->cache = $cache;
  35. $this->debug = (bool) $debug;
  36. }
  37. /**
  38. * {@inheritDoc}
  39. */
  40. public function getClassAnnotations(ReflectionClass $class)
  41. {
  42. $cacheKey = $class->getName();
  43. if (isset($this->loadedAnnotations[$cacheKey])) {
  44. return $this->loadedAnnotations[$cacheKey];
  45. }
  46. $annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class);
  47. return $this->loadedAnnotations[$cacheKey] = $annots;
  48. }
  49. /**
  50. * {@inheritDoc}
  51. */
  52. public function getClassAnnotation(ReflectionClass $class, $annotationName)
  53. {
  54. foreach ($this->getClassAnnotations($class) as $annot) {
  55. if ($annot instanceof $annotationName) {
  56. return $annot;
  57. }
  58. }
  59. return null;
  60. }
  61. /**
  62. * {@inheritDoc}
  63. */
  64. public function getPropertyAnnotations(ReflectionProperty $property)
  65. {
  66. $class = $property->getDeclaringClass();
  67. $cacheKey = $class->getName() . '$' . $property->getName();
  68. if (isset($this->loadedAnnotations[$cacheKey])) {
  69. return $this->loadedAnnotations[$cacheKey];
  70. }
  71. $annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property);
  72. return $this->loadedAnnotations[$cacheKey] = $annots;
  73. }
  74. /**
  75. * {@inheritDoc}
  76. */
  77. public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
  78. {
  79. foreach ($this->getPropertyAnnotations($property) as $annot) {
  80. if ($annot instanceof $annotationName) {
  81. return $annot;
  82. }
  83. }
  84. return null;
  85. }
  86. /**
  87. * {@inheritDoc}
  88. */
  89. public function getMethodAnnotations(ReflectionMethod $method)
  90. {
  91. $class = $method->getDeclaringClass();
  92. $cacheKey = $class->getName() . '#' . $method->getName();
  93. if (isset($this->loadedAnnotations[$cacheKey])) {
  94. return $this->loadedAnnotations[$cacheKey];
  95. }
  96. $annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method);
  97. return $this->loadedAnnotations[$cacheKey] = $annots;
  98. }
  99. /**
  100. * {@inheritDoc}
  101. */
  102. public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
  103. {
  104. foreach ($this->getMethodAnnotations($method) as $annot) {
  105. if ($annot instanceof $annotationName) {
  106. return $annot;
  107. }
  108. }
  109. return null;
  110. }
  111. public function clearLoadedAnnotations(): void
  112. {
  113. $this->loadedAnnotations = [];
  114. $this->loadedFilemtimes = [];
  115. }
  116. /** @return mixed[] */
  117. private function fetchFromCache(
  118. string $cacheKey,
  119. ReflectionClass $class,
  120. string $method,
  121. Reflector $reflector
  122. ): array {
  123. $cacheKey = rawurlencode($cacheKey);
  124. $item = $this->cache->getItem($cacheKey);
  125. if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) {
  126. $this->cache->save($item->set($this->delegate->{$method}($reflector)));
  127. }
  128. return $item->get();
  129. }
  130. /**
  131. * Used in debug mode to check if the cache is fresh.
  132. *
  133. * @return bool Returns true if the cache was fresh, or false if the class
  134. * being read was modified since writing to the cache.
  135. */
  136. private function refresh(string $cacheKey, ReflectionClass $class): bool
  137. {
  138. $lastModification = $this->getLastModification($class);
  139. if ($lastModification === 0) {
  140. return true;
  141. }
  142. $item = $this->cache->getItem('[C]' . $cacheKey);
  143. if ($item->isHit() && $item->get() >= $lastModification) {
  144. return true;
  145. }
  146. $this->cache->save($item->set(time()));
  147. return false;
  148. }
  149. /**
  150. * Returns the time the class was last modified, testing traits and parents
  151. */
  152. private function getLastModification(ReflectionClass $class): int
  153. {
  154. $filename = $class->getFileName();
  155. if (isset($this->loadedFilemtimes[$filename])) {
  156. return $this->loadedFilemtimes[$filename];
  157. }
  158. $parent = $class->getParentClass();
  159. $lastModification = max(array_merge(
  160. [$filename !== false && is_file($filename) ? filemtime($filename) : 0],
  161. array_map(function (ReflectionClass $reflectionTrait): int {
  162. return $this->getTraitLastModificationTime($reflectionTrait);
  163. }, $class->getTraits()),
  164. array_map(function (ReflectionClass $class): int {
  165. return $this->getLastModification($class);
  166. }, $class->getInterfaces()),
  167. $parent ? [$this->getLastModification($parent)] : []
  168. ));
  169. assert($lastModification !== false);
  170. return $this->loadedFilemtimes[$filename] = $lastModification;
  171. }
  172. private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
  173. {
  174. $fileName = $reflectionTrait->getFileName();
  175. if (isset($this->loadedFilemtimes[$fileName])) {
  176. return $this->loadedFilemtimes[$fileName];
  177. }
  178. $lastModificationTime = max(array_merge(
  179. [$fileName !== false && is_file($fileName) ? filemtime($fileName) : 0],
  180. array_map(function (ReflectionClass $reflectionTrait): int {
  181. return $this->getTraitLastModificationTime($reflectionTrait);
  182. }, $reflectionTrait->getTraits())
  183. ));
  184. assert($lastModificationTime !== false);
  185. return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
  186. }
  187. }