/** * Annotation ::= "@" AnnotationName ["(" [Values] ")"] * AnnotationName ::= QualifiedName | SimpleName | AliasedName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * AliasedName ::= Alias ":" SimpleName * NameSpacePart ::= identifier * SimpleName ::= identifier * Alias ::= identifier * * @return mixed False if it is not a valid annotation. */ private function Annotation() { $this->match(DocLexer::T_AT); // check if we have an annotation if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { $this->lexer->moveNext(); $name = $this->lexer->token['value']; } else { if ($this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $name = ''; } else { $this->syntaxError('namespace separator or identifier'); } } while ($this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->match(DocLexer::T_IDENTIFIER); $name .= '\\' . $this->lexer->token['value']; } // check if name is supposed to be ignored if (!$this->isNestedAnnotation && in_array($name, $this->ignoredAnnotationNames, true)) { return false; } // only process names which are not fully qualified, yet if ('\\' !== $name[0] && !$this->classExists($name)) { $alias = false === ($pos = strpos($name, '\\')) ? $name : substr($name, 0, $pos); if (isset($this->imports[$loweredAlias = strtolower($alias)])) { if (false !== $pos) { $name = $this->imports[$loweredAlias] . substr($name, $pos); } else { $name = $this->imports[$loweredAlias]; } } elseif (isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)) { $name = $this->imports['__NAMESPACE__'] . '\\' . $name; } else { if ($this->ignoreNotImportedAnnotations) { return false; } throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported.', $name, $this->context)); } } if (!$this->classExists($name)) { throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); } // at this point, $name contains the fully qualified class name of the // annotation, and it is also guaranteed that this class exists, and // that it is loaded // Next will be nested $this->isNestedAnnotation = true; $values = array(); if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { $this->match(DocLexer::T_OPEN_PARENTHESIS); if (!$this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(DocLexer::T_CLOSE_PARENTHESIS); } return new $name($values); }
/** * Identifier ::= string * * @return string */ private function Identifier() { // check if we have an annotation if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) { $this->lexer->moveNext(); $className = $this->lexer->token['value']; } else { $this->syntaxError('namespace separator or identifier'); } while ($this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->matchAny(self::$classIdentifiers); $className .= '\\' . $this->lexer->token['value']; } return $className; }
/** * Annotation ::= "@" AnnotationName ["(" [Values] ")"] * AnnotationName ::= QualifiedName | SimpleName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * NameSpacePart ::= identifier | null | false | true * SimpleName ::= identifier | null | false | true * * @return mixed False if it is not a valid annotation. */ private function Annotation() { $this->match(DocLexer::T_AT); // check if we have an annotation if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) { $this->lexer->moveNext(); $name = $this->lexer->token['value']; } else { if ($this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $name = ''; } else { $this->syntaxError('namespace separator or identifier'); } } while ($this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->matchAny(self::$classIdentifiers); $name .= '\\' . $this->lexer->token['value']; } // only process names which are not fully qualified, yet // fully qualified names must start with a \ $originalName = $name; if ('\\' !== $name[0]) { $alias = false === ($pos = strpos($name, '\\')) ? $name : substr($name, 0, $pos); $found = false; if ($this->namespaces) { foreach ($this->namespaces as $namespace) { if ($this->classExists($namespace . '\\' . $name)) { $name = $namespace . '\\' . $name; $found = true; break; } } } elseif (isset($this->imports[$loweredAlias = strtolower($alias)])) { if (false !== $pos) { $name = $this->imports[$loweredAlias] . substr($name, $pos); } else { $name = $this->imports[$loweredAlias]; } $found = true; } elseif (isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)) { $name = $this->imports['__NAMESPACE__'] . '\\' . $name; $found = true; } elseif ($this->classExists($name)) { $found = true; } if (!$found) { if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { return false; } throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?', $name, $this->context)); } } if (!$this->classExists($name)) { throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); } // at this point, $name contains the fully qualified class name of the // annotation, and it is also guaranteed that this class exists, and // that it is loaded // collects the metadata annotation only if there is not yet if (!isset(self::$annotationMetadata[$name])) { $this->collectAnnotationMetadata($name); } // verify that the class is really meant to be an annotation and not just any ordinary class if (self::$annotationMetadata[$name]['is_annotation'] === false) { if (isset($this->ignoredAnnotationNames[$originalName])) { return false; } throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context)); } //if target is nested annotation $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; // Next will be nested $this->isNestedAnnotation = true; //if anotation does not support current target if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) { throw AnnotationException::semanticalError(sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.', $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal'])); } $values = array(); if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { $this->match(DocLexer::T_OPEN_PARENTHESIS); if (!$this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(DocLexer::T_CLOSE_PARENTHESIS); } // checks all declared attributes foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { if ($property === self::$annotationMetadata[$name]['default_property'] && !isset($values[$property]) && isset($values['value'])) { $property = 'value'; } // handle a not given attribute or null value if (!isset($values[$property])) { if ($type['required']) { throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) ' . $type['value']); } continue; } if ($type['type'] === 'array') { // handle the case of a single value if (!is_array($values[$property])) { $values[$property] = array($values[$property]); } // checks if the attribute has array type declaration, such as "array<string>" if (isset($type['array_type'])) { foreach ($values[$property] as $item) { if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) { throw AnnotationException::typeError($property, $originalName, $this->context, 'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's', $item); } } } } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) { throw AnnotationException::typeError($property, $originalName, $this->context, 'a(n) ' . $type['value'], $values[$property]); } } // check if the annotation expects values via the constructor, // or directly injected into public properties if (self::$annotationMetadata[$name]['has_constructor'] === true) { return new $name($values); } $instance = new $name(); foreach ($values as $property => $value) { if (!isset(self::$annotationMetadata[$name]['properties'][$property])) { if ('value' !== $property) { throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not have a property named "%s". Available properties: %s', $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties']))); } // handle the case if the property has no annotations if (!($property = self::$annotationMetadata[$name]['default_property'])) { throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values))); } } $instance->{$property} = $value; } return $instance; }
/** * Annotation ::= "@" AnnotationName ["(" [Values] ")"] * AnnotationName ::= QualifiedName | SimpleName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * NameSpacePart ::= identifier | null | false | true * SimpleName ::= identifier | null | false | true * * @return mixed False if it is not a valid annotation. */ private function Annotation() { $this->match(DocLexer::T_AT); // check if we have an annotation if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) { $this->lexer->moveNext(); $name = $this->lexer->token['value']; } else if ($this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $name = ''; } else { $this->syntaxError('namespace separator or identifier'); } while ($this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->matchAny(self::$classIdentifiers); $name .= '\\'.$this->lexer->token['value']; } // only process names which are not fully qualified, yet $originalName = $name; if ('\\' !== $name[0] && !$this->classExists($name)) { $alias = (false === $pos = strpos($name, '\\'))? $name : substr($name, 0, $pos); if (isset($this->imports[$loweredAlias = strtolower($alias)])) { if (false !== $pos) { $name = $this->imports[$loweredAlias].substr($name, $pos); } else { $name = $this->imports[$loweredAlias]; } } elseif (isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'].'\\'.$name)) { $name = $this->imports['__NAMESPACE__'].'\\'.$name; } else { if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { return false; } throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported.', $name, $this->context)); } } if (!$this->classExists($name)) { throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); } // at this point, $name contains the fully qualified class name of the // annotation, and it is also guaranteed that this class exists, and // that it is loaded // verify that the class is really meant to be an annotation and not just any ordinary class if (!isset($this->isAnnotation[$name])) { $ref = new \ReflectionClass($name); if (false === strpos($ref->getDocComment(), '@Annotation')) { if (isset($this->ignoredAnnotationNames[$originalName])) { return false; } throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context)); } $this->isAnnotation[$name] = true; } // Next will be nested $this->isNestedAnnotation = true; $values = array(); if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { $this->match(DocLexer::T_OPEN_PARENTHESIS); if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(DocLexer::T_CLOSE_PARENTHESIS); } return new $name($values); }
/** * @test */ public function test_parse_return_primitive_alias() { $comment = ' /** * @Select * * foo, bar, baz * * @return int */ '; $lexer = new \Doctrine\Common\Annotations\DocLexer(); $lexer->setInput($comment); $tokens = []; while ($lexer->moveNext()) { $tokens[] = $lexer->lookahead; } }
/** * Annotation ::= "@" AnnotationName ["(" [Values] ")"] * AnnotationName ::= QualifiedName | SimpleName * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName * NameSpacePart ::= identifier | null | false | true * SimpleName ::= identifier | null | false | true * * @return mixed False if it is not a valid annotation. */ private function Annotation() { $this->match(DocLexer::T_AT); // check if we have an annotation if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) { $this->lexer->moveNext(); $name = $this->lexer->token['value']; } else { if ($this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $name = ''; } else { $this->syntaxError('namespace separator or identifier'); } } while ($this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { $this->match(DocLexer::T_NAMESPACE_SEPARATOR); $this->matchAny(self::$classIdentifiers); $name .= '\\' . $this->lexer->token['value']; } if (strpos($name, ":") !== false) { list($alias, $name) = explode(':', $name); // If the namespace alias doesnt exist, skip until next annotation if (!isset($this->namespaceAliases[$alias])) { $this->lexer->skipUntil(DocLexer::T_AT); return false; } $name = $this->namespaceAliases[$alias] . $name; } // only process names which are not fully qualified, yet if ('\\' !== $name[0] && !$this->classExists($name)) { $alias = false === ($pos = strpos($name, '\\')) ? $name : substr($name, 0, $pos); if (isset($this->imports[$loweredAlias = strtolower($alias)])) { if (false !== $pos) { $name = $this->imports[$loweredAlias] . substr($name, $pos); } else { $name = $this->imports[$loweredAlias]; } } elseif (isset($this->imports['__DEFAULT__']) && $this->classExists($this->imports['__DEFAULT__'] . $name)) { $name = $this->imports['__DEFAULT__'] . $name; } elseif (isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)) { $name = $this->imports['__NAMESPACE__'] . '\\' . $name; } else { if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { return false; } throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported.', $name, $this->context)); } } if (!$this->classExists($name)) { throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); } if (!$this->isAnnotation($name)) { return false; } // Verifies that the annotation class extends any class that contains "Annotation". // This is done to avoid coupling of Doctrine Annotations against other libraries. // at this point, $name contains the fully qualified class name of the // annotation, and it is also guaranteed that this class exists, and // that it is loaded // Next will be nested $this->isNestedAnnotation = true; $values = array(); if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { $this->match(DocLexer::T_OPEN_PARENTHESIS); if (!$this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { $values = $this->Values(); } $this->match(DocLexer::T_CLOSE_PARENTHESIS); } return $this->newAnnotation($name, $values); }