CachedReader.php 7.2 KB

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