/** * Parses child reflection objects from the token stream. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * @param \TokenReflection\IReflection $parent Parent reflection object * @return \TokenReflection\ReflectionClass * @throws \TokenReflection\Exception\ParseException If a parse error was detected. */ protected function parseChildren(Stream $tokenStream, IReflection $parent) { while (true) { switch ($type = $tokenStream->getType()) { case null: break 2; case T_COMMENT: case T_DOC_COMMENT: $docblock = $tokenStream->getTokenValue(); if (preg_match('~^' . preg_quote(self::DOCBLOCK_TEMPLATE_START, '~') . '~', $docblock)) { array_unshift($this->docblockTemplates, new ReflectionAnnotation($this, $docblock)); } elseif (self::DOCBLOCK_TEMPLATE_END === $docblock) { array_shift($this->docblockTemplates); } $tokenStream->next(); break; case '}': break 2; case T_PUBLIC: case T_PRIVATE: case T_PROTECTED: case T_STATIC: case T_VAR: case T_VARIABLE: static $searching = array(T_VARIABLE => true, T_FUNCTION => true); if (T_VAR !== $tokenStream->getType()) { $position = $tokenStream->key(); while (null !== ($type = $tokenStream->getType($position)) && !isset($searching[$type])) { $position++; } } if (T_VARIABLE === $type || T_VAR === $type) { $property = new ReflectionProperty($tokenStream, $this->getBroker(), $this); $this->properties[$property->getName()] = $property; $tokenStream->next(); break; } // Break missing on purpose // Break missing on purpose case T_FINAL: case T_ABSTRACT: case T_FUNCTION: $method = new ReflectionMethod($tokenStream, $this->getBroker(), $this); $this->methods[$method->getName()] = $method; $tokenStream->next(); break; case T_CONST: $tokenStream->skipWhitespaces(true); while ($tokenStream->is(T_STRING)) { $constant = new ReflectionConstant($tokenStream, $this->getBroker(), $this); $this->constants[$constant->getName()] = $constant; if ($tokenStream->is(',')) { $tokenStream->skipWhitespaces(true); } else { $tokenStream->next(); } } break; case T_USE: $tokenStream->skipWhitespaces(true); while (true) { $traitName = ''; $type = $tokenStream->getType(); while (T_STRING === $type || T_NS_SEPARATOR === $type) { $traitName .= $tokenStream->getTokenValue(); $type = $tokenStream->skipWhitespaces(true)->getType(); } if ('' === trim($traitName, '\\')) { throw new Exception\ParseException($this, $tokenStream, 'An empty trait name found.', Exception\ParseException::LOGICAL_ERROR); } $this->traits[] = Resolver::resolveClassFQN($traitName, $this->aliases, $this->namespaceName); if (';' === $type) { // End of "use" $tokenStream->skipWhitespaces(); break; } elseif (',' === $type) { // Next trait name follows $tokenStream->skipWhitespaces(); continue; } elseif ('{' !== $type) { // Unexpected token throw new Exception\ParseException($this, $tokenStream, 'Unexpected token found: "%s".', Exception\ParseException::UNEXPECTED_TOKEN); } // Aliases definition $type = $tokenStream->skipWhitespaces(true)->getType(); while (true) { if ('}' === $type) { $tokenStream->skipWhitespaces(); break 2; } $leftSide = ''; $rightSide = array('', null); $alias = true; while (T_STRING === $type || T_NS_SEPARATOR === $type || T_DOUBLE_COLON === $type) { $leftSide .= $tokenStream->getTokenValue(); $type = $tokenStream->skipWhitespaces(true)->getType(); } if (T_INSTEADOF === $type) { $alias = false; } elseif (T_AS !== $type) { throw new Exception\ParseException($this, $tokenStream, 'Unexpected token found.', Exception\ParseException::UNEXPECTED_TOKEN); } $type = $tokenStream->skipWhitespaces(true)->getType(); if (T_PUBLIC === $type || T_PROTECTED === $type || T_PRIVATE === $type) { if (!$alias) { throw new Exception\ParseException($this, $tokenStream, 'Unexpected token found.', Exception\ParseException::UNEXPECTED_TOKEN); } switch ($type) { case T_PUBLIC: $type = InternalReflectionMethod::IS_PUBLIC; break; case T_PROTECTED: $type = InternalReflectionMethod::IS_PROTECTED; break; case T_PRIVATE: $type = InternalReflectionMethod::IS_PRIVATE; break; default: break; } $rightSide[1] = $type; $type = $tokenStream->skipWhitespaces(true)->getType(); } while (T_STRING === $type || T_NS_SEPARATOR === $type && !$alias) { $rightSide[0] .= $tokenStream->getTokenValue(); $type = $tokenStream->skipWhitespaces(true)->getType(); } if (empty($leftSide)) { throw new Exception\ParseException($this, $tokenStream, 'An empty method name was found.', Exception\ParseException::LOGICAL_ERROR); } if ($alias) { // Alias if ($pos = strpos($leftSide, '::')) { $methodName = substr($leftSide, $pos + 2); $className = Resolver::resolveClassFQN(substr($leftSide, 0, $pos), $this->aliases, $this->namespaceName); $leftSide = $className . '::' . $methodName; $this->traitAliases[$rightSide[0]] = $leftSide; } else { $this->traitAliases[$rightSide[0]] = '(null)::' . $leftSide; } $this->traitImports[$leftSide][] = $rightSide; } else { // Insteadof if ($pos = strpos($leftSide, '::')) { $methodName = substr($leftSide, $pos + 2); } else { throw new Exception\ParseException($this, $tokenStream, 'A T_DOUBLE_COLON has to be present when using T_INSTEADOF.', Exception\ParseException::UNEXPECTED_TOKEN); } $this->traitImports[Resolver::resolveClassFQN($rightSide[0], $this->aliases, $this->namespaceName) . '::' . $methodName][] = null; } if (',' === $type) { $tokenStream->skipWhitespaces(true); continue; } elseif (';' !== $type) { throw new Exception\ParseException($this, $tokenStream, 'Unexpected token found.', Exception\ParseException::UNEXPECTED_TOKEN); } $type = $tokenStream->skipWhitespaces()->getType(); } } break; default: $tokenStream->next(); break; } } return $this; }
/** * Parses base method modifiers (abstract, final, public, ...). * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * * @return \TokenReflection\ReflectionMethod */ private function parseBaseModifiers(Stream $tokenStream) { while (true) { switch ($tokenStream->getType()) { case T_ABSTRACT: $this->modifiers |= InternalReflectionMethod::IS_ABSTRACT; break; case T_FINAL: $this->modifiers |= InternalReflectionMethod::IS_FINAL; break; case T_PUBLIC: $this->modifiers |= InternalReflectionMethod::IS_PUBLIC; break; case T_PRIVATE: $this->modifiers |= InternalReflectionMethod::IS_PRIVATE; break; case T_PROTECTED: $this->modifiers |= InternalReflectionMethod::IS_PROTECTED; break; case T_STATIC: $this->modifiers |= InternalReflectionMethod::IS_STATIC; break; case T_FUNCTION: case null: break 2; default: break; } $tokenStream->skipWhitespaces(); } if (!($this->modifiers & (InternalReflectionMethod::IS_PRIVATE | InternalReflectionMethod::IS_PROTECTED))) { $this->modifiers |= InternalReflectionMethod::IS_PUBLIC; } return $this; }
/** * Parses the token substream and prepares namespace reflections from the file. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * @param \TokenReflection\IReflection $parent Parent reflection object * * @return \TokenReflection\ReflectionFile */ protected function parseStream(Stream $tokenStream, IReflection $parent = null) { $this->name = $tokenStream->getFileName(); if (1 >= $tokenStream->count()) { // No PHP content $this->docComment = new ReflectionAnnotation($this, null); return $this; } $docCommentPosition = null; if (!$tokenStream->is(T_OPEN_TAG)) { $this->namespaces[] = new ReflectionFileNamespace($tokenStream, $this->broker, $this); } else { $tokenStream->skipWhitespaces(); while (null !== ($type = $tokenStream->getType())) { switch ($type) { case T_DOC_COMMENT: if (null === $docCommentPosition) { $docCommentPosition = $tokenStream->key(); } case T_WHITESPACE: case T_COMMENT: break; case T_DECLARE: // Intentionally twice call of skipWhitespaces() $tokenStream->skipWhitespaces()->findMatchingBracket()->skipWhitespaces()->skipWhitespaces(); break; case T_NAMESPACE: $docCommentPosition = $docCommentPosition ?: -1; break 2; default: $docCommentPosition = $docCommentPosition ?: -1; $this->namespaces[] = new ReflectionFileNamespace($tokenStream, $this->broker, $this); break 2; } $tokenStream->skipWhitespaces(); } while (null !== ($type = $tokenStream->getType())) { if (T_NAMESPACE === $type) { $this->namespaces[] = new ReflectionFileNamespace($tokenStream, $this->broker, $this); } else { $tokenStream->skipWhitespaces(); } } } if (null !== $docCommentPosition && !empty($this->namespaces) && $docCommentPosition === $this->namespaces[0]->getStartPosition()) { $docCommentPosition = null; } $this->docComment = new ReflectionAnnotation($this, null !== $docCommentPosition ? $tokenStream->getTokenValue($docCommentPosition) : null); return $this; }
/** * Parses the parameter default value. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * @return \TokenReflection\ReflectionParameter * @throws \TokenReflection\Exception\ParseException If the default value could not be determined. */ private function parseDefaultValue(Stream $tokenStream) { if ($tokenStream->is('=')) { $tokenStream->skipWhitespaces(true); $level = 0; while (null !== ($type = $tokenStream->getType())) { switch ($type) { case ')': if (0 === $level) { break 2; } case '}': case ']': $level--; break; case '(': case '{': case '[': $level++; break; case ',': if (0 === $level) { break 2; } break; default: break; } $this->defaultValueDefinition[] = $tokenStream->current(); $tokenStream->next(); } if (')' !== $type && ',' !== $type) { throw new Exception\ParseException($this, $tokenStream, 'The property default value is not terminated properly. Expected "," or ")".', Exception\ParseException::UNEXPECTED_TOKEN); } } return $this; }
/** * Parses function/method parameters. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * * @return \TokenReflection\ReflectionFunctionBase * @throws \TokenReflection\Exception\ParseException If parameters could not be parsed. */ protected final function parseParameters(Stream $tokenStream) { if (!$tokenStream->is('(')) { throw new Exception\ParseException($this, $tokenStream, 'Could find the start token.', Exception\ParseException::UNEXPECTED_TOKEN); } static $accepted = array(T_NS_SEPARATOR => true, T_STRING => true, T_ARRAY => true, T_CALLABLE => true, T_VARIABLE => true, '&' => true); $tokenStream->skipWhitespaces(true); while (null !== ($type = $tokenStream->getType()) && ')' !== $type) { if (isset($accepted[$type])) { $parameter = new ReflectionParameter($tokenStream, $this->getBroker(), $this); $this->parameters[] = $parameter; } if ($tokenStream->is(')')) { break; } $tokenStream->skipWhitespaces(true); } $tokenStream->skipWhitespaces(); return $this; }
/** * Parses child reflection objects from the token stream. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * @param \TokenReflection\IReflection $parent Parent reflection object * @return \TokenReflection\ReflectionFileNamespace * @throws \TokenReflection\Exception\ParseException If child elements could not be parsed. */ protected function parseChildren(Stream $tokenStream, IReflection $parent) { static $skipped = array(T_WHITESPACE => true, T_COMMENT => true, T_DOC_COMMENT => true); $depth = 0; $firstChild = null; while (true) { switch ($tokenStream->getType()) { case T_USE: while (true) { $namespaceName = ''; $alias = null; $tokenStream->skipWhitespaces(true); while (true) { switch ($tokenStream->getType()) { case T_STRING: case T_NS_SEPARATOR: $namespaceName .= $tokenStream->getTokenValue(); break; case T_FUNCTION: // "use function" in 5.6+ break; default: break 2; } $tokenStream->skipWhitespaces(true); } $namespaceName = ltrim($namespaceName, '\\'); if (empty($namespaceName)) { throw new Exception\ParseException($this, $tokenStream, 'Imported namespace name could not be determined.', Exception\ParseException::LOGICAL_ERROR); } elseif ('\\' === substr($namespaceName, -1)) { throw new Exception\ParseException($this, $tokenStream, sprintf('Invalid namespace name "%s".', $namespaceName), Exception\ParseException::LOGICAL_ERROR); } if ($tokenStream->is(T_AS)) { // Alias defined $tokenStream->skipWhitespaces(true); if (!$tokenStream->is(T_STRING)) { throw new Exception\ParseException($this, $tokenStream, sprintf('The imported namespace "%s" seems aliased but the alias name could not be determined.', $namespaceName), Exception\ParseException::LOGICAL_ERROR); } $alias = $tokenStream->getTokenValue(); $tokenStream->skipWhitespaces(true); } else { // No explicit alias if (false !== ($pos = strrpos($namespaceName, '\\'))) { $alias = substr($namespaceName, $pos + 1); } else { $alias = $namespaceName; } } if (isset($this->aliases[$alias])) { throw new Exception\ParseException($this, $tokenStream, sprintf('Namespace alias "%s" already defined.', $alias), Exception\ParseException::LOGICAL_ERROR); } $this->aliases[$alias] = $namespaceName; $type = $tokenStream->getType(); if (';' === $type) { $tokenStream->skipWhitespaces(); break 2; } elseif (',' === $type) { // Next namespace in the current "use" definition continue; } throw new Exception\ParseException($this, $tokenStream, 'Unexpected token found.', Exception\ParseException::UNEXPECTED_TOKEN); } case T_COMMENT: case T_DOC_COMMENT: $docblock = $tokenStream->getTokenValue(); if (preg_match('~^' . preg_quote(self::DOCBLOCK_TEMPLATE_START, '~') . '~', $docblock)) { array_unshift($this->docblockTemplates, new ReflectionAnnotation($this, $docblock)); } elseif (self::DOCBLOCK_TEMPLATE_END === $docblock) { array_shift($this->docblockTemplates); } $tokenStream->next(); break; case '{': $tokenStream->next(); $depth++; break; case '}': if (0 === $depth--) { break 2; } $tokenStream->next(); break; case null: case T_NAMESPACE: break 2; case T_ABSTRACT: case T_FINAL: case T_CLASS: case T_TRAIT: case T_INTERFACE: $class = new ReflectionClass($tokenStream, $this->getBroker(), $this); $firstChild = $firstChild ?: $class; $className = $class->getName(); if (isset($this->classes[$className])) { if (!$this->classes[$className] instanceof Invalid\ReflectionClass) { $this->classes[$className] = new Invalid\ReflectionClass($className, $this->classes[$className]->getFileName(), $this->getBroker()); } if (!$this->classes[$className]->hasReasons()) { $this->classes[$className]->addReason(new Exception\ParseException($this, $tokenStream, sprintf('Class %s is defined multiple times in the file.', $className), Exception\ParseException::ALREADY_EXISTS)); } } else { $this->classes[$className] = $class; } $tokenStream->next(); break; case T_CONST: $tokenStream->skipWhitespaces(true); do { $constant = new ReflectionConstant($tokenStream, $this->getBroker(), $this); $firstChild = $firstChild ?: $constant; $constantName = $constant->getName(); if (isset($this->constants[$constantName])) { if (!$this->constants[$constantName] instanceof Invalid\ReflectionConstant) { $this->constants[$constantName] = new Invalid\ReflectionConstant($constantName, $this->constants[$constantName]->getFileName(), $this->getBroker()); } if (!$this->constants[$constantName]->hasReasons()) { $this->constants[$constantName]->addReason(new Exception\ParseException($this, $tokenStream, sprintf('Constant %s is defined multiple times in the file.', $constantName), Exception\ParseException::ALREADY_EXISTS)); } } else { $this->constants[$constantName] = $constant; } if ($tokenStream->is(',')) { $tokenStream->skipWhitespaces(true); } else { $tokenStream->next(); } } while ($tokenStream->is(T_STRING)); break; case T_STRING: switch ($tokenStream->getTokenValue()) { case 'define': // definition case // definition case default: break; } $tokenStream->next(); break; case T_FUNCTION: $position = $tokenStream->key() + 1; while (isset($skipped[$type = $tokenStream->getType($position)])) { $position++; } if ('(' === $type) { // Skipping anonymous functions $tokenStream->seek($position)->findMatchingBracket()->skipWhiteSpaces(true); if ($tokenStream->is(T_USE)) { $tokenStream->skipWhitespaces(true)->findMatchingBracket()->skipWhitespaces(true); } $tokenStream->findMatchingBracket()->next(); continue; } $function = new ReflectionFunction($tokenStream, $this->getBroker(), $this); $firstChild = $firstChild ?: $function; $functionName = $function->getName(); if (isset($this->functions[$functionName])) { if (!$this->functions[$functionName] instanceof Invalid\ReflectionFunction) { $this->functions[$functionName] = new Invalid\ReflectionFunction($functionName, $this->functions[$functionName]->getFileName(), $this->getBroker()); } if (!$this->functions[$functionName]->hasReasons()) { $this->functions[$functionName]->addReason(new Exception\ParseException($this, $tokenStream, sprintf('Function %s is defined multiple times in the file.', $functionName), Exception\ParseException::ALREADY_EXISTS)); } } else { $this->functions[$functionName] = $function; } $tokenStream->next(); break; default: $tokenStream->next(); break; } } if ($firstChild) { $this->startPosition = min($this->startPosition, $firstChild->getStartPosition()); } return $this; }
/** * Parses static variables. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * @return \TokenReflection\ReflectionFunctionBase * @throws \TokenReflection\Exception\ParseException If static variables could not be parsed. */ protected final function parseStaticVariables(Stream $tokenStream) { $type = $tokenStream->getType(); if ('{' === $type) { if ($this->getBroker()->isOptionSet(Broker::OPTION_PARSE_FUNCTION_BODY)) { $tokenStream->skipWhitespaces(true); while ('}' !== ($type = $tokenStream->getType())) { switch ($type) { case T_STATIC: $type = $tokenStream->skipWhitespaces(true)->getType(); if (T_VARIABLE !== $type) { // Late static binding break; } while (T_VARIABLE === $type) { $variableName = $tokenStream->getTokenValue(); $variableDefinition = array(); $type = $tokenStream->skipWhitespaces(true)->getType(); if ('=' === $type) { $type = $tokenStream->skipWhitespaces(true)->getType(); $level = 0; while ($tokenStream->valid()) { switch ($type) { case '(': case '[': case '{': case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: $level++; break; case ')': case ']': case '}': $level--; break; case ';': case ',': if (0 === $level) { break 2; } default: break; } $variableDefinition[] = $tokenStream->current(); $type = $tokenStream->skipWhitespaces(true)->getType(); } if (!$tokenStream->valid()) { throw new Exception\ParseException($this, $tokenStream, 'Invalid end of token stream.', Exception\ParseException::READ_BEYOND_EOS); } } $this->staticVariablesDefinition[substr($variableName, 1)] = $variableDefinition; if (',' === $type) { $type = $tokenStream->skipWhitespaces(true)->getType(); } else { break; } } break; case T_FUNCTION: // Anonymous function -> skip to its end if (!$tokenStream->find('{')) { throw new Exception\ParseException($this, $tokenStream, 'Could not find beginning of the anonymous function.', Exception\ParseException::UNEXPECTED_TOKEN); } // Break missing intentionally // Break missing intentionally case '{': case '[': case '(': case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: $tokenStream->findMatchingBracket()->skipWhitespaces(true); break; default: $tokenStream->skipWhitespaces(); break; } } } else { $tokenStream->findMatchingBracket(); } } elseif (';' !== $type) { throw new Exception\ParseException($this, $tokenStream, 'Unexpected token found.', Exception\ParseException::UNEXPECTED_TOKEN); } return $this; }
/** * Parses the constant value. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * @param \TokenReflection\IReflection $parent Parent reflection object * @return \TokenReflection\ReflectionConstant * @throws \TokenReflection\Exception\ParseException If the constant value could not be determined. */ private function parseValue(Stream $tokenStream, IReflection $parent) { if (!$tokenStream->is('=')) { throw new Exception\ParseException($this, $tokenStream, 'Could not find the definition start.', Exception\ParseException::UNEXPECTED_TOKEN); } $tokenStream->skipWhitespaces(true); static $acceptedTokens = array('-' => true, '+' => true, T_STRING => true, T_NS_SEPARATOR => true, T_CONSTANT_ENCAPSED_STRING => true, T_DNUMBER => true, T_LNUMBER => true, T_DOUBLE_COLON => true, T_CLASS_C => true, T_DIR => true, T_FILE => true, T_FUNC_C => true, T_LINE => true, T_METHOD_C => true, T_NS_C => true, T_TRAIT_C => true); while (null !== ($type = $tokenStream->getType())) { if (T_START_HEREDOC === $type) { $this->valueDefinition[] = $tokenStream->current(); while (null !== $type && T_END_HEREDOC !== $type) { $tokenStream->next(); $this->valueDefinition[] = $tokenStream->current(); $type = $tokenStream->getType(); } $tokenStream->next(); } elseif (isset($acceptedTokens[$type])) { $this->valueDefinition[] = $tokenStream->current(); $tokenStream->next(); } elseif ($tokenStream->isWhitespace(true)) { $tokenStream->skipWhitespaces(true); } else { break; } } if (empty($this->valueDefinition)) { throw new Exception\ParseException($this, $tokenStream, 'Value definition is empty.', Exception\ParseException::LOGICAL_ERROR); } $value = $tokenStream->getTokenValue(); if (null === $type || ',' !== $value && ';' !== $value) { throw new Exception\ParseException($this, $tokenStream, 'Invalid value definition.', Exception\ParseException::LOGICAL_ERROR); } return $this; }
/** * Parses the type hint. * * @param \TokenReflection\Stream\StreamBase $tokenStream Token substream * * @return \TokenReflection\ReflectionParameter * @throws \TokenReflection\Exception\ParseException If the type hint class name could not be determined. */ private function parseTypeHint(Stream $tokenStream) { $type = $tokenStream->getType(); if (T_ARRAY === $type) { $this->typeHint = self::ARRAY_TYPE_HINT; $this->originalTypeHint = self::ARRAY_TYPE_HINT; $tokenStream->skipWhitespaces(true); } elseif (T_CALLABLE === $type) { $this->typeHint = self::CALLABLE_TYPE_HINT; $this->originalTypeHint = self::CALLABLE_TYPE_HINT; $tokenStream->skipWhitespaces(true); } elseif (T_STRING === $type || T_NS_SEPARATOR === $type) { $className = ''; do { $className .= $tokenStream->getTokenValue(); $tokenStream->skipWhitespaces(true); $type = $tokenStream->getType(); } while (T_STRING === $type || T_NS_SEPARATOR === $type); if ('' === ltrim($className, '\\')) { throw new Exception\ParseException($this, $tokenStream, sprintf('Invalid class name definition: "%s".', $className), Exception\ParseException::LOGICAL_ERROR); } $this->originalTypeHint = $className; } return $this; }