CachedReader.php 7.1 KB

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