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

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