Chris@0: . Chris@0: */ Chris@0: Chris@0: namespace Doctrine\Common\Annotations; Chris@0: Chris@0: use Doctrine\Common\Cache\Cache; Chris@12: use ReflectionClass; Chris@0: Chris@0: /** Chris@0: * A cache aware annotation reader. Chris@0: * Chris@0: * @author Johannes M. Schmitt Chris@0: * @author Benjamin Eberlei Chris@0: */ Chris@0: final class CachedReader implements Reader Chris@0: { Chris@0: /** Chris@0: * @var Reader Chris@0: */ Chris@0: private $delegate; Chris@0: Chris@0: /** Chris@0: * @var Cache Chris@0: */ Chris@0: private $cache; Chris@0: Chris@0: /** Chris@0: * @var boolean Chris@0: */ Chris@0: private $debug; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@0: private $loadedAnnotations = array(); Chris@0: Chris@0: /** Chris@0: * Constructor. Chris@0: * Chris@0: * @param Reader $reader Chris@0: * @param Cache $cache Chris@0: * @param bool $debug Chris@0: */ Chris@0: public function __construct(Reader $reader, Cache $cache, $debug = false) Chris@0: { Chris@0: $this->delegate = $reader; Chris@0: $this->cache = $cache; Chris@0: $this->debug = (boolean) $debug; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritDoc} Chris@0: */ Chris@12: public function getClassAnnotations(ReflectionClass $class) Chris@0: { Chris@0: $cacheKey = $class->getName(); Chris@0: Chris@0: if (isset($this->loadedAnnotations[$cacheKey])) { Chris@0: return $this->loadedAnnotations[$cacheKey]; Chris@0: } Chris@0: Chris@0: if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { Chris@0: $annots = $this->delegate->getClassAnnotations($class); Chris@0: $this->saveToCache($cacheKey, $annots); Chris@0: } Chris@0: Chris@0: return $this->loadedAnnotations[$cacheKey] = $annots; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritDoc} Chris@0: */ Chris@12: public function getClassAnnotation(ReflectionClass $class, $annotationName) Chris@0: { Chris@0: foreach ($this->getClassAnnotations($class) as $annot) { Chris@0: if ($annot instanceof $annotationName) { Chris@0: return $annot; Chris@0: } Chris@0: } Chris@0: Chris@0: return null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritDoc} Chris@0: */ Chris@0: public function getPropertyAnnotations(\ReflectionProperty $property) Chris@0: { Chris@0: $class = $property->getDeclaringClass(); Chris@0: $cacheKey = $class->getName().'$'.$property->getName(); Chris@0: Chris@0: if (isset($this->loadedAnnotations[$cacheKey])) { Chris@0: return $this->loadedAnnotations[$cacheKey]; Chris@0: } Chris@0: Chris@0: if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { Chris@0: $annots = $this->delegate->getPropertyAnnotations($property); Chris@0: $this->saveToCache($cacheKey, $annots); Chris@0: } Chris@0: Chris@0: return $this->loadedAnnotations[$cacheKey] = $annots; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritDoc} Chris@0: */ Chris@0: public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) Chris@0: { Chris@0: foreach ($this->getPropertyAnnotations($property) as $annot) { Chris@0: if ($annot instanceof $annotationName) { Chris@0: return $annot; Chris@0: } Chris@0: } Chris@0: Chris@0: return null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritDoc} Chris@0: */ Chris@0: public function getMethodAnnotations(\ReflectionMethod $method) Chris@0: { Chris@0: $class = $method->getDeclaringClass(); Chris@0: $cacheKey = $class->getName().'#'.$method->getName(); Chris@0: Chris@0: if (isset($this->loadedAnnotations[$cacheKey])) { Chris@0: return $this->loadedAnnotations[$cacheKey]; Chris@0: } Chris@0: Chris@0: if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { Chris@0: $annots = $this->delegate->getMethodAnnotations($method); Chris@0: $this->saveToCache($cacheKey, $annots); Chris@0: } Chris@0: Chris@0: return $this->loadedAnnotations[$cacheKey] = $annots; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritDoc} Chris@0: */ Chris@0: public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) Chris@0: { Chris@0: foreach ($this->getMethodAnnotations($method) as $annot) { Chris@0: if ($annot instanceof $annotationName) { Chris@0: return $annot; Chris@0: } Chris@0: } Chris@0: Chris@0: return null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Clears loaded annotations. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: public function clearLoadedAnnotations() Chris@0: { Chris@0: $this->loadedAnnotations = array(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Fetches a value from the cache. Chris@0: * Chris@12: * @param string $cacheKey The cache key. Chris@12: * @param ReflectionClass $class The related class. Chris@0: * Chris@0: * @return mixed The cached value or false when the value is not in cache. Chris@0: */ Chris@12: private function fetchFromCache($cacheKey, ReflectionClass $class) Chris@0: { Chris@0: if (($data = $this->cache->fetch($cacheKey)) !== false) { Chris@0: if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) { Chris@0: return $data; Chris@0: } Chris@0: } Chris@0: Chris@0: return false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Saves a value to the cache. Chris@0: * Chris@12: * @param string $cacheKey The cache key. Chris@12: * @param mixed $value The value. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@12: private function saveToCache($cacheKey, $value) Chris@0: { Chris@0: $this->cache->save($cacheKey, $value); Chris@0: if ($this->debug) { Chris@0: $this->cache->save('[C]'.$cacheKey, time()); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if the cache is fresh. Chris@0: * Chris@0: * @param string $cacheKey Chris@12: * @param ReflectionClass $class Chris@0: * Chris@0: * @return boolean Chris@0: */ Chris@12: private function isCacheFresh($cacheKey, ReflectionClass $class) Chris@0: { Chris@12: if (null === $lastModification = $this->getLastModification($class)) { Chris@0: return true; Chris@0: } Chris@0: Chris@12: return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification; Chris@12: } Chris@12: Chris@12: /** Chris@12: * Returns the time the class was last modified, testing traits and parents Chris@12: * Chris@12: * @param ReflectionClass $class Chris@12: * @return int Chris@12: */ Chris@12: private function getLastModification(ReflectionClass $class) Chris@12: { Chris@12: $filename = $class->getFileName(); Chris@12: $parent = $class->getParentClass(); Chris@12: Chris@12: return max(array_merge( Chris@12: [$filename ? filemtime($filename) : 0], Chris@12: array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()), Chris@12: array_map([$this, 'getLastModification'], $class->getInterfaces()), Chris@12: $parent ? [$this->getLastModification($parent)] : [] Chris@12: )); Chris@12: } Chris@12: Chris@12: /** Chris@12: * @param ReflectionClass $reflectionTrait Chris@12: * @return int Chris@12: */ Chris@12: private function getTraitLastModificationTime(ReflectionClass $reflectionTrait) Chris@12: { Chris@12: $fileName = $reflectionTrait->getFileName(); Chris@12: Chris@12: return max(array_merge( Chris@12: [$fileName ? filemtime($fileName) : 0], Chris@12: array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits()) Chris@12: )); Chris@0: } Chris@0: }