CachedReader.php 7.1 KB

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