/** * 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 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; }