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

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