/** * Tries to parse a definition of a class/method/property/constant/function and returns the appropriate instance if successful. * * @param string $definition Definition * @param \ApiGen\ReflectionElement|\ApiGen\ReflectionParameter $context Link context * @param string $expectedName Expected element name * @return \ApiGen\Reflection\ReflectionElement|null */ public function resolveElement($definition, $context, &$expectedName = null) { // No simple type resolving static $types = array('boolean' => 1, 'integer' => 1, 'float' => 1, 'string' => 1, 'array' => 1, 'object' => 1, 'resource' => 1, 'callback' => 1, 'callable' => 1, 'null' => 1, 'false' => 1, 'true' => 1, 'mixed' => 1); if (empty($definition) || isset($types[$definition])) { return null; } $originalContext = $context; if ($context instanceof Reflection\ReflectionParameter && null === $context->getDeclaringClassName()) { // Parameter of function in namespace or global space $context = $this->getFunction($context->getDeclaringFunctionName()); } elseif ($context instanceof Reflection\ReflectionMethod || $context instanceof Reflection\ReflectionParameter || $context instanceof Reflection\ReflectionConstant && null !== $context->getDeclaringClassName() || $context instanceof Reflection\ReflectionProperty) { // Member of a class $context = $this->getClass($context->getDeclaringClassName()); } if (null === $context) { return null; } // self, $this references if ('self' === $definition || '$this' === $definition) { return $context instanceof ReflectionClass ? $context : null; } $definitionBase = substr($definition, 0, strcspn($definition, '\\:')); $namespaceAliases = $context->getNamespaceAliases(); if (!empty($definitionBase) && isset($namespaceAliases[$definitionBase]) && $definition !== ($className = \TokenReflection\Resolver::resolveClassFQN($definition, $namespaceAliases, $context->getNamespaceName()))) { // Aliased class $expectedName = $className; if (false === strpos($className, ':')) { return $this->getClass($className, $context->getNamespaceName()); } else { $definition = $className; } } elseif ($class = $this->getClass($definition, $context->getNamespaceName())) { // Class return $class; } elseif ($constant = $this->getConstant($definition, $context->getNamespaceName())) { // Constant return $constant; } elseif (($function = $this->getFunction($definition, $context->getNamespaceName())) || '()' === substr($definition, -2) && ($function = $this->getFunction(substr($definition, 0, -2), $context->getNamespaceName()))) { // Function return $function; } if (($pos = strpos($definition, '::')) || ($pos = strpos($definition, '->'))) { // Class::something or Class->something if (0 === strpos($definition, 'parent::') && ($parentClassName = $context->getParentClassName())) { $context = $this->getClass($parentClassName); } elseif (0 !== strpos($definition, 'self::')) { $class = $this->getClass(substr($definition, 0, $pos), $context->getNamespaceName()); if (null === $class) { $class = $this->getClass(\TokenReflection\Resolver::resolveClassFQN(substr($definition, 0, $pos), $context->getNamespaceAliases(), $context->getNamespaceName())); } $context = $class; } $definition = substr($definition, $pos + 2); } elseif ($originalContext instanceof Reflection\ReflectionParameter) { return null; } // No usable context if (null === $context || $context instanceof Reflection\ReflectionConstant || $context instanceof Reflection\ReflectionFunction) { return null; } if ($context->hasProperty($definition)) { // Class property return $context->getProperty($definition); } elseif ('$' === $definition[0] && $context->hasProperty(substr($definition, 1))) { // Class $property return $context->getProperty(substr($definition, 1)); } elseif ($context->hasMethod($definition)) { // Class method return $context->getMethod($definition); } elseif ('()' === substr($definition, -2) && $context->hasMethod(substr($definition, 0, -2))) { // Class method() return $context->getMethod(substr($definition, 0, -2)); } elseif ($context->hasConstant($definition)) { // Class constant return $context->getConstant($definition); } return null; }
/** * 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; }
/** * Returns static variables. * * @return array */ public function getStaticVariables() { if (empty($this->staticVariables) && !empty($this->staticVariablesDefinition)) { foreach ($this->staticVariablesDefinition as $variableName => $variableDefinition) { $this->staticVariables[$variableName] = Resolver::getValueDefinition($variableDefinition, $this); } } return $this->staticVariables; }
/** * Returns the required class name of the value. * * @return string|null * @throws \TokenReflection\Exception\RuntimeException If the type hint class FQN could not be determined. */ public function getClassName() { if ($this->isArray() || $this->isCallable()) { return null; } if (null === $this->typeHint && null !== $this->originalTypeHint) { if (null !== $this->declaringClassName) { $parent = $this->getDeclaringClass(); if (null === $parent) { throw new Exception\RuntimeException('Could not load class reflection.', Exception\RuntimeException::DOES_NOT_EXIST, $this); } } else { $parent = $this->getDeclaringFunction(); if (null === $parent || !$parent->isTokenized()) { throw new Exception\RuntimeException('Could not load function reflection.', Exception\RuntimeException::DOES_NOT_EXIST, $this); } } $lTypeHint = strtolower($this->originalTypeHint); if ('parent' === $lTypeHint || 'self' === $lTypeHint) { if (null === $this->declaringClassName) { throw new Exception\RuntimeException('Parameter type hint cannot be "self" nor "parent" when not a method.', Exception\RuntimeException::UNSUPPORTED, $this); } if ('parent' === $lTypeHint) { if ($parent->isInterface() || null === $parent->getParentClassName()) { throw new Exception\RuntimeException('Class has no parent.', Exception\RuntimeException::DOES_NOT_EXIST, $this); } $this->typeHint = $parent->getParentClassName(); } else { $this->typeHint = $this->declaringClassName; } } else { $this->typeHint = ltrim(Resolver::resolveClassFQN($this->originalTypeHint, $parent->getNamespaceAliases(), $parent->getNamespaceName()), '\\'); } } return $this->typeHint; }
/** * @param string $definition * @param int $pos * @param ElementReflectionInterface $reflectionElement * @return ClassReflectionInterface */ private function resolveContextForSelfProperty($definition, $pos, ElementReflectionInterface $reflectionElement) { $class = $this->getClass(substr($definition, 0, $pos), $reflectionElement->getNamespaceName()); if ($class === null) { $fqnName = Resolver::resolveClassFqn(substr($definition, 0, $pos), $reflectionElement->getNamespaceAliases(), $reflectionElement->getNamespaceName()); $class = $this->getClass($fqnName); } return $class; }
/** * Returns the part of the source code defining the property default value. * * @return string */ public function getDefaultValueDefinition() { return is_array($this->defaultValueDefinition) ? Resolver::getSourceCode($this->defaultValueDefinition) : $this->defaultValueDefinition; }
/** * Processes a function/method and adds classes from annotations to the overall class array. * * @param array $declared Array of declared classes * @param array $allClasses Array with all classes parsed so far * @param \ApiGen\Reflection\ReflectionFunction|\TokenReflection\IReflectionFunctionBase $function Function/method reflection * @return array */ private function processFunction(array $declared, array $allClasses, $function) { static $parsedAnnotations = array('param', 'return', 'throws'); $annotations = $function->getAnnotations(); foreach ($parsedAnnotations as $annotation) { if (!isset($annotations[$annotation])) { continue; } foreach ($annotations[$annotation] as $doc) { foreach (explode('|', preg_replace('~\\s.*~', '', $doc)) as $name) { if ($name) { $name = Resolver::resolveClassFQN(rtrim($name, '[]'), $function->getNamespaceAliases(), $function->getNamespaceName()); $allClasses = $this->addClass($declared, $allClasses, $name); } } } } foreach ($function->getParameters() as $param) { if ($hint = $param->getClassName()) { $allClasses = $this->addClass($declared, $allClasses, $hint); } } return $allClasses; }
/** * @param string $name * @param ReflectionClass|ReflectionMethod $reflection * @return string */ private function getClassFqn($name, $reflection) { return Resolver::resolveClassFqn($name, $reflection->getNamespaceAliases(), $reflection->getNamespaceName()); }
/** * Returns the default value. * * @return mixed * @throws \TokenReflection\Exception\RuntimeException If the property is not optional. * @throws \TokenReflection\Exception\RuntimeException If the property has no default value. */ public function getDefaultValue() { if (!$this->isOptional()) { throw new Exception\RuntimeException('Property is not optional.', Exception\RuntimeException::UNSUPPORTED, $this); } if (is_array($this->defaultValueDefinition)) { if (0 === count($this->defaultValueDefinition)) { throw new Exception\RuntimeException('Property has no default value.', Exception\RuntimeException::DOES_NOT_EXIST, $this); } $this->defaultValue = Resolver::getValueDefinition($this->defaultValueDefinition, $this); $this->defaultValueDefinition = Resolver::getSourceCode($this->defaultValueDefinition); } return $this->defaultValue; }