CachedReader.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. namespace Doctrine\Common\Annotations;
  3. use Doctrine\Common\Cache\Cache;
  4. use ReflectionClass;
  5. use ReflectionMethod;
  6. use ReflectionProperty;
  7. use function array_map;
  8. use function array_merge;
  9. use function assert;
  10. use function filemtime;
  11. use function max;
  12. use function time;
  13. /**
  14. * A cache aware annotation reader.
  15. */
  16. final class CachedReader implements Reader
  17. {
  18. /** @var Reader */
  19. private $delegate;
  20. /** @var Cache */
  21. private $cache;
  22. /** @var bool */
  23. private $debug;
  24. /** @var array<string, array<object>> */
  25. private $loadedAnnotations = [];
  26. /** @var int[] */
  27. private $loadedFilemtimes = [];
  28. /**
  29. * @param bool $debug
  30. */
  31. public function __construct(Reader $reader, Cache $cache, $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);
  47. if ($annots === false) {
  48. $annots = $this->delegate->getClassAnnotations($class);
  49. $this->saveToCache($cacheKey, $annots);
  50. }
  51. return $this->loadedAnnotations[$cacheKey] = $annots;
  52. }
  53. /**
  54. * {@inheritDoc}
  55. */
  56. public function getClassAnnotation(ReflectionClass $class, $annotationName)
  57. {
  58. foreach ($this->getClassAnnotations($class) as $annot) {
  59. if ($annot instanceof $annotationName) {
  60. return $annot;
  61. }
  62. }
  63. return null;
  64. }
  65. /**
  66. * {@inheritDoc}
  67. */
  68. public function getPropertyAnnotations(ReflectionProperty $property)
  69. {
  70. $class = $property->getDeclaringClass();
  71. $cacheKey = $class->getName() . '$' . $property->getName();
  72. if (isset($this->loadedAnnotations[$cacheKey])) {
  73. return $this->loadedAnnotations[$cacheKey];
  74. }
  75. $annots = $this->fetchFromCache($cacheKey, $class);
  76. if ($annots === false) {
  77. $annots = $this->delegate->getPropertyAnnotations($property);
  78. $this->saveToCache($cacheKey, $annots);
  79. }
  80. return $this->loadedAnnotations[$cacheKey] = $annots;
  81. }
  82. /**
  83. * {@inheritDoc}
  84. */
  85. public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
  86. {
  87. foreach ($this->getPropertyAnnotations($property) as $annot) {
  88. if ($annot instanceof $annotationName) {
  89. return $annot;
  90. }
  91. }
  92. return null;
  93. }
  94. /**
  95. * {@inheritDoc}
  96. */
  97. public function getMethodAnnotations(ReflectionMethod $method)
  98. {
  99. $class = $method->getDeclaringClass();
  100. $cacheKey = $class->getName() . '#' . $method->getName();
  101. if (isset($this->loadedAnnotations[$cacheKey])) {
  102. return $this->loadedAnnotations[$cacheKey];
  103. }
  104. $annots = $this->fetchFromCache($cacheKey, $class);
  105. if ($annots === false) {
  106. $annots = $this->delegate->getMethodAnnotations($method);
  107. $this->saveToCache($cacheKey, $annots);
  108. }
  109. return $this->loadedAnnotations[$cacheKey] = $annots;
  110. }
  111. /**
  112. * {@inheritDoc}
  113. */
  114. public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
  115. {
  116. foreach ($this->getMethodAnnotations($method) as $annot) {
  117. if ($annot instanceof $annotationName) {
  118. return $annot;
  119. }
  120. }
  121. return null;
  122. }
  123. /**
  124. * Clears loaded annotations.
  125. *
  126. * @return void
  127. */
  128. public function clearLoadedAnnotations()
  129. {
  130. $this->loadedAnnotations = [];
  131. $this->loadedFilemtimes = [];
  132. }
  133. /**
  134. * Fetches a value from the cache.
  135. *
  136. * @param string $cacheKey The cache key.
  137. *
  138. * @return mixed The cached value or false when the value is not in cache.
  139. */
  140. private function fetchFromCache($cacheKey, ReflectionClass $class)
  141. {
  142. $data = $this->cache->fetch($cacheKey);
  143. if ($data !== false) {
  144. if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) {
  145. return $data;
  146. }
  147. }
  148. return false;
  149. }
  150. /**
  151. * Saves a value to the cache.
  152. *
  153. * @param string $cacheKey The cache key.
  154. * @param mixed $value The value.
  155. *
  156. * @return void
  157. */
  158. private function saveToCache($cacheKey, $value)
  159. {
  160. $this->cache->save($cacheKey, $value);
  161. if (! $this->debug) {
  162. return;
  163. }
  164. $this->cache->save('[C]' . $cacheKey, time());
  165. }
  166. /**
  167. * Checks if the cache is fresh.
  168. *
  169. * @param string $cacheKey
  170. *
  171. * @return bool
  172. */
  173. private function isCacheFresh($cacheKey, ReflectionClass $class)
  174. {
  175. $lastModification = $this->getLastModification($class);
  176. if ($lastModification === 0) {
  177. return true;
  178. }
  179. return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification;
  180. }
  181. /**
  182. * Returns the time the class was last modified, testing traits and parents
  183. */
  184. private function getLastModification(ReflectionClass $class): int
  185. {
  186. $filename = $class->getFileName();
  187. if (isset($this->loadedFilemtimes[$filename])) {
  188. return $this->loadedFilemtimes[$filename];
  189. }
  190. $parent = $class->getParentClass();
  191. $lastModification = max(array_merge(
  192. [$filename ? filemtime($filename) : 0],
  193. array_map(function (ReflectionClass $reflectionTrait): int {
  194. return $this->getTraitLastModificationTime($reflectionTrait);
  195. }, $class->getTraits()),
  196. array_map(function (ReflectionClass $class): int {
  197. return $this->getLastModification($class);
  198. }, $class->getInterfaces()),
  199. $parent ? [$this->getLastModification($parent)] : []
  200. ));
  201. assert($lastModification !== false);
  202. return $this->loadedFilemtimes[$filename] = $lastModification;
  203. }
  204. private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
  205. {
  206. $fileName = $reflectionTrait->getFileName();
  207. if (isset($this->loadedFilemtimes[$fileName])) {
  208. return $this->loadedFilemtimes[$fileName];
  209. }
  210. $lastModificationTime = max(array_merge(
  211. [$fileName ? filemtime($fileName) : 0],
  212. array_map(function (ReflectionClass $reflectionTrait): int {
  213. return $this->getTraitLastModificationTime($reflectionTrait);
  214. }, $reflectionTrait->getTraits())
  215. ));
  216. assert($lastModificationTime !== false);
  217. return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
  218. }
  219. }