PsrCachedReader.php 6.3 KB

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