269 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			269 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			PHP
		
	
	
|  | <?php | ||
|  | 
 | ||
|  | namespace Doctrine\Common\Annotations; | ||
|  | 
 | ||
|  | use Doctrine\Common\Cache\Cache; | ||
|  | use ReflectionClass; | ||
|  | use ReflectionMethod; | ||
|  | use ReflectionProperty; | ||
|  | 
 | ||
|  | use function array_map; | ||
|  | use function array_merge; | ||
|  | use function assert; | ||
|  | use function filemtime; | ||
|  | use function max; | ||
|  | use function time; | ||
|  | 
 | ||
|  | /** | ||
|  |  * A cache aware annotation reader. | ||
|  |  * | ||
|  |  * @deprecated the CachedReader is deprecated and will be removed | ||
|  |  *             in version 2.0.0 of doctrine/annotations. Please use the | ||
|  |  *             {@see \Doctrine\Common\Annotations\PsrCachedReader} instead. | ||
|  |  */ | ||
|  | final class CachedReader implements Reader | ||
|  | { | ||
|  |     /** @var Reader */ | ||
|  |     private $delegate; | ||
|  | 
 | ||
|  |     /** @var Cache */ | ||
|  |     private $cache; | ||
|  | 
 | ||
|  |     /** @var bool */ | ||
|  |     private $debug; | ||
|  | 
 | ||
|  |     /** @var array<string, array<object>> */ | ||
|  |     private $loadedAnnotations = []; | ||
|  | 
 | ||
|  |     /** @var int[] */ | ||
|  |     private $loadedFilemtimes = []; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param bool $debug | ||
|  |      */ | ||
|  |     public function __construct(Reader $reader, Cache $cache, $debug = false) | ||
|  |     { | ||
|  |         $this->delegate = $reader; | ||
|  |         $this->cache    = $cache; | ||
|  |         $this->debug    = (bool) $debug; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      */ | ||
|  |     public function getClassAnnotations(ReflectionClass $class) | ||
|  |     { | ||
|  |         $cacheKey = $class->getName(); | ||
|  | 
 | ||
|  |         if (isset($this->loadedAnnotations[$cacheKey])) { | ||
|  |             return $this->loadedAnnotations[$cacheKey]; | ||
|  |         } | ||
|  | 
 | ||
|  |         $annots = $this->fetchFromCache($cacheKey, $class); | ||
|  |         if ($annots === false) { | ||
|  |             $annots = $this->delegate->getClassAnnotations($class); | ||
|  |             $this->saveToCache($cacheKey, $annots); | ||
|  |         } | ||
|  | 
 | ||
|  |         return $this->loadedAnnotations[$cacheKey] = $annots; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      */ | ||
|  |     public function getClassAnnotation(ReflectionClass $class, $annotationName) | ||
|  |     { | ||
|  |         foreach ($this->getClassAnnotations($class) as $annot) { | ||
|  |             if ($annot instanceof $annotationName) { | ||
|  |                 return $annot; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return null; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      */ | ||
|  |     public function getPropertyAnnotations(ReflectionProperty $property) | ||
|  |     { | ||
|  |         $class    = $property->getDeclaringClass(); | ||
|  |         $cacheKey = $class->getName() . '$' . $property->getName(); | ||
|  | 
 | ||
|  |         if (isset($this->loadedAnnotations[$cacheKey])) { | ||
|  |             return $this->loadedAnnotations[$cacheKey]; | ||
|  |         } | ||
|  | 
 | ||
|  |         $annots = $this->fetchFromCache($cacheKey, $class); | ||
|  |         if ($annots === false) { | ||
|  |             $annots = $this->delegate->getPropertyAnnotations($property); | ||
|  |             $this->saveToCache($cacheKey, $annots); | ||
|  |         } | ||
|  | 
 | ||
|  |         return $this->loadedAnnotations[$cacheKey] = $annots; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      */ | ||
|  |     public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) | ||
|  |     { | ||
|  |         foreach ($this->getPropertyAnnotations($property) as $annot) { | ||
|  |             if ($annot instanceof $annotationName) { | ||
|  |                 return $annot; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return null; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      */ | ||
|  |     public function getMethodAnnotations(ReflectionMethod $method) | ||
|  |     { | ||
|  |         $class    = $method->getDeclaringClass(); | ||
|  |         $cacheKey = $class->getName() . '#' . $method->getName(); | ||
|  | 
 | ||
|  |         if (isset($this->loadedAnnotations[$cacheKey])) { | ||
|  |             return $this->loadedAnnotations[$cacheKey]; | ||
|  |         } | ||
|  | 
 | ||
|  |         $annots = $this->fetchFromCache($cacheKey, $class); | ||
|  |         if ($annots === false) { | ||
|  |             $annots = $this->delegate->getMethodAnnotations($method); | ||
|  |             $this->saveToCache($cacheKey, $annots); | ||
|  |         } | ||
|  | 
 | ||
|  |         return $this->loadedAnnotations[$cacheKey] = $annots; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      */ | ||
|  |     public function getMethodAnnotation(ReflectionMethod $method, $annotationName) | ||
|  |     { | ||
|  |         foreach ($this->getMethodAnnotations($method) as $annot) { | ||
|  |             if ($annot instanceof $annotationName) { | ||
|  |                 return $annot; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return null; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Clears loaded annotations. | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     public function clearLoadedAnnotations() | ||
|  |     { | ||
|  |         $this->loadedAnnotations = []; | ||
|  |         $this->loadedFilemtimes  = []; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Fetches a value from the cache. | ||
|  |      * | ||
|  |      * @param string $cacheKey The cache key. | ||
|  |      * | ||
|  |      * @return mixed The cached value or false when the value is not in cache. | ||
|  |      */ | ||
|  |     private function fetchFromCache($cacheKey, ReflectionClass $class) | ||
|  |     { | ||
|  |         $data = $this->cache->fetch($cacheKey); | ||
|  |         if ($data !== false) { | ||
|  |             if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) { | ||
|  |                 return $data; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Saves a value to the cache. | ||
|  |      * | ||
|  |      * @param string $cacheKey The cache key. | ||
|  |      * @param mixed  $value    The value. | ||
|  |      * | ||
|  |      * @return void | ||
|  |      */ | ||
|  |     private function saveToCache($cacheKey, $value) | ||
|  |     { | ||
|  |         $this->cache->save($cacheKey, $value); | ||
|  |         if (! $this->debug) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         $this->cache->save('[C]' . $cacheKey, time()); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Checks if the cache is fresh. | ||
|  |      * | ||
|  |      * @param string $cacheKey | ||
|  |      * | ||
|  |      * @return bool | ||
|  |      */ | ||
|  |     private function isCacheFresh($cacheKey, ReflectionClass $class) | ||
|  |     { | ||
|  |         $lastModification = $this->getLastModification($class); | ||
|  |         if ($lastModification === 0) { | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Returns the time the class was last modified, testing traits and parents | ||
|  |      */ | ||
|  |     private function getLastModification(ReflectionClass $class): int | ||
|  |     { | ||
|  |         $filename = $class->getFileName(); | ||
|  | 
 | ||
|  |         if (isset($this->loadedFilemtimes[$filename])) { | ||
|  |             return $this->loadedFilemtimes[$filename]; | ||
|  |         } | ||
|  | 
 | ||
|  |         $parent = $class->getParentClass(); | ||
|  | 
 | ||
|  |         $lastModification =  max(array_merge( | ||
|  |             [$filename ? filemtime($filename) : 0], | ||
|  |             array_map(function (ReflectionClass $reflectionTrait): int { | ||
|  |                 return $this->getTraitLastModificationTime($reflectionTrait); | ||
|  |             }, $class->getTraits()), | ||
|  |             array_map(function (ReflectionClass $class): int { | ||
|  |                 return $this->getLastModification($class); | ||
|  |             }, $class->getInterfaces()), | ||
|  |             $parent ? [$this->getLastModification($parent)] : [] | ||
|  |         )); | ||
|  | 
 | ||
|  |         assert($lastModification !== false); | ||
|  | 
 | ||
|  |         return $this->loadedFilemtimes[$filename] = $lastModification; | ||
|  |     } | ||
|  | 
 | ||
|  |     private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int | ||
|  |     { | ||
|  |         $fileName = $reflectionTrait->getFileName(); | ||
|  | 
 | ||
|  |         if (isset($this->loadedFilemtimes[$fileName])) { | ||
|  |             return $this->loadedFilemtimes[$fileName]; | ||
|  |         } | ||
|  | 
 | ||
|  |         $lastModificationTime = max(array_merge( | ||
|  |             [$fileName ? filemtime($fileName) : 0], | ||
|  |             array_map(function (ReflectionClass $reflectionTrait): int { | ||
|  |                 return $this->getTraitLastModificationTime($reflectionTrait); | ||
|  |             }, $reflectionTrait->getTraits()) | ||
|  |         )); | ||
|  | 
 | ||
|  |         assert($lastModificationTime !== false); | ||
|  | 
 | ||
|  |         return $this->loadedFilemtimes[$fileName] = $lastModificationTime; | ||
|  |     } | ||
|  | } |