vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php line 164

Open in your IDE?
  1. <?php
  2. namespace Doctrine\Common\Annotations;
  3. use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
  4. use Doctrine\Common\Annotations\Annotation\Target;
  5. use ReflectionClass;
  6. use ReflectionFunction;
  7. use ReflectionMethod;
  8. use ReflectionProperty;
  9. use function array_merge;
  10. use function class_exists;
  11. use function extension_loaded;
  12. use function ini_get;
  13. /**
  14.  * A reader for docblock annotations.
  15.  */
  16. class AnnotationReader implements Reader
  17. {
  18.     /**
  19.      * Global map for imports.
  20.      *
  21.      * @var array<string, class-string>
  22.      */
  23.     private static $globalImports = [
  24.         'ignoreannotation' => Annotation\IgnoreAnnotation::class,
  25.     ];
  26.     /**
  27.      * A list with annotations that are not causing exceptions when not resolved to an annotation class.
  28.      *
  29.      * The names are case sensitive.
  30.      *
  31.      * @var array<string, true>
  32.      */
  33.     private static $globalIgnoredNames ImplicitlyIgnoredAnnotationNames::LIST;
  34.     /**
  35.      * A list with annotations that are not causing exceptions when not resolved to an annotation class.
  36.      *
  37.      * The names are case sensitive.
  38.      *
  39.      * @var array<string, true>
  40.      */
  41.     private static $globalIgnoredNamespaces = [];
  42.     /**
  43.      * Add a new annotation to the globally ignored annotation names with regard to exception handling.
  44.      */
  45.     public static function addGlobalIgnoredName(string $name)
  46.     {
  47.         self::$globalIgnoredNames[$name] = true;
  48.     }
  49.     /**
  50.      * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
  51.      */
  52.     public static function addGlobalIgnoredNamespace(string $namespace)
  53.     {
  54.         self::$globalIgnoredNamespaces[$namespace] = true;
  55.     }
  56.     /**
  57.      * Annotations parser.
  58.      *
  59.      * @var DocParser
  60.      */
  61.     private $parser;
  62.     /**
  63.      * Annotations parser used to collect parsing metadata.
  64.      *
  65.      * @var DocParser
  66.      */
  67.     private $preParser;
  68.     /**
  69.      * PHP parser used to collect imports.
  70.      *
  71.      * @var PhpParser
  72.      */
  73.     private $phpParser;
  74.     /**
  75.      * In-memory cache mechanism to store imported annotations per class.
  76.      *
  77.      * @psalm-var array<'class'|'function', array<string, array<string, class-string>>>
  78.      */
  79.     private $imports = [];
  80.     /**
  81.      * In-memory cache mechanism to store ignored annotations per class.
  82.      *
  83.      * @psalm-var array<'class'|'function', array<string, array<string, true>>>
  84.      */
  85.     private $ignoredAnnotationNames = [];
  86.     /**
  87.      * Initializes a new AnnotationReader.
  88.      *
  89.      * @throws AnnotationException
  90.      */
  91.     public function __construct(?DocParser $parser null)
  92.     {
  93.         if (
  94.             extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' ||
  95.             ini_get('opcache.save_comments') === '0')
  96.         ) {
  97.             throw AnnotationException::optimizerPlusSaveComments();
  98.         }
  99.         if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) {
  100.             throw AnnotationException::optimizerPlusSaveComments();
  101.         }
  102.         // Make sure that the IgnoreAnnotation annotation is loaded
  103.         class_exists(IgnoreAnnotation::class);
  104.         $this->parser $parser ?: new DocParser();
  105.         $this->preParser = new DocParser();
  106.         $this->preParser->setImports(self::$globalImports);
  107.         $this->preParser->setIgnoreNotImportedAnnotations(true);
  108.         $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
  109.         $this->phpParser = new PhpParser();
  110.     }
  111.     /**
  112.      * {@inheritDoc}
  113.      */
  114.     public function getClassAnnotations(ReflectionClass $class)
  115.     {
  116.         $this->parser->setTarget(Target::TARGET_CLASS);
  117.         $this->parser->setImports($this->getImports($class));
  118.         $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
  119.         $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
  120.         return $this->parser->parse($class->getDocComment(), 'class ' $class->getName());
  121.     }
  122.     /**
  123.      * {@inheritDoc}
  124.      */
  125.     public function getClassAnnotation(ReflectionClass $class$annotationName)
  126.     {
  127.         $annotations $this->getClassAnnotations($class);
  128.         foreach ($annotations as $annotation) {
  129.             if ($annotation instanceof $annotationName) {
  130.                 return $annotation;
  131.             }
  132.         }
  133.         return null;
  134.     }
  135.     /**
  136.      * {@inheritDoc}
  137.      */
  138.     public function getPropertyAnnotations(ReflectionProperty $property)
  139.     {
  140.         $class   $property->getDeclaringClass();
  141.         $context 'property ' $class->getName() . '::$' $property->getName();
  142.         $this->parser->setTarget(Target::TARGET_PROPERTY);
  143.         $this->parser->setImports($this->getPropertyImports($property));
  144.         $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
  145.         $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
  146.         return $this->parser->parse($property->getDocComment(), $context);
  147.     }
  148.     /**
  149.      * {@inheritDoc}
  150.      */
  151.     public function getPropertyAnnotation(ReflectionProperty $property$annotationName)
  152.     {
  153.         $annotations $this->getPropertyAnnotations($property);
  154.         foreach ($annotations as $annotation) {
  155.             if ($annotation instanceof $annotationName) {
  156.                 return $annotation;
  157.             }
  158.         }
  159.         return null;
  160.     }
  161.     /**
  162.      * {@inheritDoc}
  163.      */
  164.     public function getMethodAnnotations(ReflectionMethod $method)
  165.     {
  166.         $class   $method->getDeclaringClass();
  167.         $context 'method ' $class->getName() . '::' $method->getName() . '()';
  168.         $this->parser->setTarget(Target::TARGET_METHOD);
  169.         $this->parser->setImports($this->getMethodImports($method));
  170.         $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
  171.         $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
  172.         return $this->parser->parse($method->getDocComment(), $context);
  173.     }
  174.     /**
  175.      * {@inheritDoc}
  176.      */
  177.     public function getMethodAnnotation(ReflectionMethod $method$annotationName)
  178.     {
  179.         $annotations $this->getMethodAnnotations($method);
  180.         foreach ($annotations as $annotation) {
  181.             if ($annotation instanceof $annotationName) {
  182.                 return $annotation;
  183.             }
  184.         }
  185.         return null;
  186.     }
  187.     /**
  188.      * Gets the annotations applied to a function.
  189.      *
  190.      * @phpstan-return list<object> An array of Annotations.
  191.      */
  192.     public function getFunctionAnnotations(ReflectionFunction $function): array
  193.     {
  194.         $context 'function ' $function->getName();
  195.         $this->parser->setTarget(Target::TARGET_FUNCTION);
  196.         $this->parser->setImports($this->getImports($function));
  197.         $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function));
  198.         $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
  199.         return $this->parser->parse($function->getDocComment(), $context);
  200.     }
  201.     /**
  202.      * Gets a function annotation.
  203.      *
  204.      * @return object|null The Annotation or NULL, if the requested annotation does not exist.
  205.      */
  206.     public function getFunctionAnnotation(ReflectionFunction $functionstring $annotationName)
  207.     {
  208.         $annotations $this->getFunctionAnnotations($function);
  209.         foreach ($annotations as $annotation) {
  210.             if ($annotation instanceof $annotationName) {
  211.                 return $annotation;
  212.             }
  213.         }
  214.         return null;
  215.     }
  216.     /**
  217.      * Returns the ignored annotations for the given class or function.
  218.      *
  219.      * @param ReflectionClass|ReflectionFunction $reflection
  220.      *
  221.      * @return array<string, true>
  222.      */
  223.     private function getIgnoredAnnotationNames($reflection): array
  224.     {
  225.         $type $reflection instanceof ReflectionClass 'class' 'function';
  226.         $name $reflection->getName();
  227.         if (isset($this->ignoredAnnotationNames[$type][$name])) {
  228.             return $this->ignoredAnnotationNames[$type][$name];
  229.         }
  230.         $this->collectParsingMetadata($reflection);
  231.         return $this->ignoredAnnotationNames[$type][$name];
  232.     }
  233.     /**
  234.      * Retrieves imports for a class or a function.
  235.      *
  236.      * @param ReflectionClass|ReflectionFunction $reflection
  237.      *
  238.      * @return array<string, class-string>
  239.      */
  240.     private function getImports($reflection): array
  241.     {
  242.         $type $reflection instanceof ReflectionClass 'class' 'function';
  243.         $name $reflection->getName();
  244.         if (isset($this->imports[$type][$name])) {
  245.             return $this->imports[$type][$name];
  246.         }
  247.         $this->collectParsingMetadata($reflection);
  248.         return $this->imports[$type][$name];
  249.     }
  250.     /**
  251.      * Retrieves imports for methods.
  252.      *
  253.      * @return array<string, class-string>
  254.      */
  255.     private function getMethodImports(ReflectionMethod $method)
  256.     {
  257.         $class        $method->getDeclaringClass();
  258.         $classImports $this->getImports($class);
  259.         $traitImports = [];
  260.         foreach ($class->getTraits() as $trait) {
  261.             if (
  262.                 ! $trait->hasMethod($method->getName())
  263.                 || $trait->getFileName() !== $method->getFileName()
  264.             ) {
  265.                 continue;
  266.             }
  267.             $traitImports array_merge($traitImports$this->phpParser->parseUseStatements($trait));
  268.         }
  269.         return array_merge($classImports$traitImports);
  270.     }
  271.     /**
  272.      * Retrieves imports for properties.
  273.      *
  274.      * @return array<string, class-string>
  275.      */
  276.     private function getPropertyImports(ReflectionProperty $property)
  277.     {
  278.         $class        $property->getDeclaringClass();
  279.         $classImports $this->getImports($class);
  280.         $traitImports = [];
  281.         foreach ($class->getTraits() as $trait) {
  282.             if (! $trait->hasProperty($property->getName())) {
  283.                 continue;
  284.             }
  285.             $traitImports array_merge($traitImports$this->phpParser->parseUseStatements($trait));
  286.         }
  287.         return array_merge($classImports$traitImports);
  288.     }
  289.     /**
  290.      * Collects parsing metadata for a given class or function.
  291.      *
  292.      * @param ReflectionClass|ReflectionFunction $reflection
  293.      */
  294.     private function collectParsingMetadata($reflection): void
  295.     {
  296.         $type $reflection instanceof ReflectionClass 'class' 'function';
  297.         $name $reflection->getName();
  298.         $ignoredAnnotationNames self::$globalIgnoredNames;
  299.         $annotations            $this->preParser->parse($reflection->getDocComment(), $type ' ' $name);
  300.         foreach ($annotations as $annotation) {
  301.             if (! ($annotation instanceof IgnoreAnnotation)) {
  302.                 continue;
  303.             }
  304.             foreach ($annotation->names as $annot) {
  305.                 $ignoredAnnotationNames[$annot] = true;
  306.             }
  307.         }
  308.         $this->imports[$type][$name] = array_merge(
  309.             self::$globalImports,
  310.             $this->phpParser->parseUseStatements($reflection),
  311.             [
  312.                 '__NAMESPACE__' => $reflection->getNamespaceName(),
  313.                 'self' => $name,
  314.             ]
  315.         );
  316.         $this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames;
  317.     }
  318. }