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

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