Add support for types and improve resolution

As part of the re-architecting of phpDocumentor and its Reflection component
we needed to move the type classes from the \phpDocumentor\Descriptor
namespace to \phpDocumentor\Reflection\Types.

In addition to this I have removed the resolution / type creation from the
Collection class into a new Resolver class that is capable of resolving any
type into a Type or Fqsen object.

This is the first commit with the base classes and a test for the Resolver
functionality in subsequent commits we need to add tests for the other
classes and to refactor the collection object.
This commit is contained in:
Mike van Riel
2015-06-07 11:03:28 +02:00
committed by Mike van Riel
parent f3f4d07808
commit fab7169255
20 changed files with 1062 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
interface Type
{
public function __toString();
}
+63
View File
@@ -0,0 +1,63 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
class Array_ implements Type
{
/** @var Type */
private $valueType;
/** @var Type */
private $keyType;
public function __construct(Type $valueType = null, Type $keyType = null)
{
if ($keyType === null) {
$keyType = new Mixed();
}
if ($valueType === null) {
$valueType = new Mixed();
}
$this->valueType = $valueType;
$this->keyType = $keyType;
}
/**
* @return Type
*/
public function getKeyType()
{
return $this->keyType;
}
/**
* @return Type
*/
public function getValueType()
{
return $this->valueType;
}
public function __toString()
{
if ($this->valueType instanceof Mixed) {
return 'array';
}
return $this->valueType . '[]';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Boolean implements Type
{
public function __toString()
{
return 'bool';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Callable_ implements Type
{
public function __toString()
{
return 'callable';
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Types\Resolver;
use phpDocumentor\Reflection\DocBlock\Context;
/**
* Collection
*/
class Collection extends \ArrayObject
{
/** @var string Definition of the OR operator for types */
const OPERATOR_OR = '|';
/**
* Current invoking location.
*
* This is used to prepend to type with a relative location.
* May also be 'default' or 'global', in which case they are ignored.
*
* @var Context
*/
protected $context = null;
/**
* @var Resolver
*/
private $resolver;
/**
* Registers the namespace and aliases; uses that to add and expand the given types.
*
* @param string[] $types Array containing a list of types to add to this container.
* @param Context $context
* @param Resolver $resolver
*/
public function __construct(array $types = array(), Context $context = null, Resolver $resolver = null)
{
$this->context = $context ?: new Context('');
$this->resolver = $resolver ?: new Resolver();
foreach ($types as $type) {
$this->add($type);
}
}
/**
* Returns the current invoking location.
*
* @return Context
*/
public function getContext()
{
return $this->context;
}
/**
* Adds a new type to the collection and expands it if it contains a
* relative namespace.
*
* If a class in the type contains a relative namespace than this collection
* will try to expand that into a FQCN.
*
* @param string $type A 'Type' as defined in the phpDocumentor
* documentation.
*
* @throws \InvalidArgumentException if a non-string argument is passed.
*
* @see http://phpdoc.org/docs/latest/for-users/types.html for the
* definition of a type.
*
* @return void
*/
public function add($type)
{
if (!is_string($type)) {
throw new \InvalidArgumentException(
'A type should be represented by a string, received: '
. var_export($type, true)
);
}
// separate the type by the OR operator
$type_parts = explode(self::OPERATOR_OR, $type);
foreach ($type_parts as $part) {
$expanded_type = $this->resolver->resolve($part, $this->context);
if ($expanded_type) {
$this[] = $expanded_type;
}
}
}
/**
* Returns a string representation of the collection.
*
* @return string The resolved types across the collection, separated with
* {@link self::OPERATOR_OR}.
*/
public function __toString()
{
return implode(self::OPERATOR_OR, $this->getArrayCopy());
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
final class Compound implements Type
{
/** @var Type[] */
private $types = [];
/**
* @param Type[]|Fqsen[] $types
*/
public function __construct($types)
{
$this->types = $types;
}
/**
* @param integer $index
*
* @return null|Type|Fqsen
*/
public function get($index)
{
if (!$this->has($index)) {
return null;
}
return $this->types[$index];
}
/**
* @param integer $index
*
* @return bool
*/
public function has($index)
{
return isset($this->types[$index]);
}
public function __toString()
{
return implode('|', $this->types);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Float implements Type
{
public function __toString()
{
return 'float';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Integer implements Type
{
public function __toString()
{
return 'int';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Mixed implements Type
{
public function __toString()
{
return 'mixed';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Null_ implements Type
{
public function __toString()
{
return 'null';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Object_ implements Type
{
public function __toString()
{
return 'object';
}
}
+251
View File
@@ -0,0 +1,251 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\DocBlock\Context;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
class Resolver
{
/** @var string Definition of the ARRAY operator for types */
const OPERATOR_ARRAY = '[]';
/** @var string Definition of the NAMESPACE operator in PHP */
const OPERATOR_NAMESPACE = '\\';
/** @var string[] List of recognized keywords and unto which Value Object they map */
private $keywords = array(
'string' => 'phpDocumentor\Reflection\Types\String',
'int' => 'phpDocumentor\Reflection\Types\Integer',
'integer' => 'phpDocumentor\Reflection\Types\Integer',
'bool' => 'phpDocumentor\Reflection\Types\Boolean',
'boolean' => 'phpDocumentor\Reflection\Types\Boolean',
'float' => 'phpDocumentor\Reflection\Types\Float',
'double' => 'phpDocumentor\Reflection\Types\Float',
'object' => 'phpDocumentor\Reflection\Types\Object_',
'mixed' => 'phpDocumentor\Reflection\Types\Mixed',
'array' => 'phpDocumentor\Reflection\Types\Array_',
'resource' => 'phpDocumentor\Reflection\Types\Resource',
'void' => 'phpDocumentor\Reflection\Types\Void',
'null' => 'phpDocumentor\Reflection\Types\Null_',
'scalar' => 'phpDocumentor\Reflection\Types\Scalar',
'callback' => 'phpDocumentor\Reflection\Types\Callable_',
'callable' => 'phpDocumentor\Reflection\Types\Callable_',
'false' => 'phpDocumentor\Reflection\Types\Boolean',
'true' => 'phpDocumentor\Reflection\Types\Boolean',
'self' => 'phpDocumentor\Reflection\Types\Self_',
'$this' => 'phpDocumentor\Reflection\Types\This',
'static' => 'phpDocumentor\Reflection\Types\Static_'
);
/**
* Analyzes the given type and returns the FQCN variant.
*
* When a type is provided this method checks whether it is not a keyword or
* Fully Qualified Class Name. If so it will use the given namespace and
* aliases to expand the type to a FQCN representation.
*
* This method only works as expected if the namespace and aliases are set;
* no dynamic reflection is being performed here.
*
* @param string $type The relative or absolute type.
*
* @uses Context::getNamespace() to determine with what to prefix the type name.
* @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
* replaced with another namespace.
*
* @return Fqsen|Type|null
*/
public function resolve($type, Context $context)
{
if (!is_string($type)) {
throw new \InvalidArgumentException(
'Attempted to resolve type but it appeared not to be a string, received: ' . var_export($type, true)
);
}
$type = trim($type);
if (!$type) {
throw new \InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty');
}
switch (true) {
case $this->isKeyword($type):
return $this->resolveKeyword($type);
case ($this->isCompoundType($type)):
return $this->resolveCompoundType($type, $context);
case $this->isFqsen($type):
return $this->resolveFqsen($type);
case $this->isTypedArray($type):
return $this->resolveTypedArray($type, $context);
case $this->isPartialStructuralElementName($type):
return $this->resolvePartialStructuralElementName($type, $context);
// @codeCoverageIgnoreStart
default:
// I haven't got the foggiest how the logic would come here but added this as a defense.
throw new \RuntimeException(
'Unable to resolve type "' . $type . '", there is no known method to resolve it'
);
}
// @codeCoverageIgnoreEnd
}
/**
* Detects whether the given type represents an array.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @return bool
*/
private function isTypedArray($type)
{
return substr($type, -2) === self::OPERATOR_ARRAY;
}
/**
* Detects whether the given type represents a PHPDoc keyword.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @return bool
*/
private function isKeyword($type)
{
return in_array(strtolower($type), array_keys($this->keywords), true);
}
/**
* Detects whether the given type represents a relative structural element name.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @return bool
*/
private function isPartialStructuralElementName($type)
{
return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type);
}
/**
* Tests whether the given type is a Fully Qualified Structural Element Name.
*
* @param string $type
*
* @return bool
*/
private function isFqsen($type)
{
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
}
/**
* Tests whether the given type is a compound type (i.e. `string|int`).
*
* @param string $type
*
* @return bool
*/
private function isCompoundType($type)
{
return strpos($type, '|') !== false;
}
/**
* Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set.
*
* @param string $type
* @param Context $context
*
* @return Array_
*/
private function resolveTypedArray($type, Context $context)
{
return new Array_($this->resolve(substr($type, 0, -2), $context));
}
/**
* Resolves the given keyword (such as `string`) into a Type object representing that keyword.
*
* @param string $type
*
* @return Type
*/
private function resolveKeyword($type)
{
$className = $this->keywords[strtolower($type)];
return new $className();
}
/**
* Resolves the given FQSEN string into an FQSEN object.
*
* @param string $type
*
* @return Fqsen
*/
private function resolveFqsen($type)
{
return new Fqsen($type);
}
/**
* Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation
* (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context.
*
* @param string $type
* @param Context $context
*
* @return Fqsen
*/
private function resolvePartialStructuralElementName($type, Context $context)
{
$typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2);
$namespaceAliases = $context->getNamespaceAliases();
// if the first segment is not an alias; prepend namespace name and return
if (!isset($namespaceAliases[$typeParts[0]])) {
$namespace = $context->getNamespace();
if ('' !== $namespace) {
$namespace .= self::OPERATOR_NAMESPACE;
}
return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type);
}
$typeParts[0] = $namespaceAliases[$typeParts[0]];
return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts));
}
/**
* Resolves a compound type (i.e. `string|int`) into the appropriate Type objects or FQSEN.
*
* @param string $type
* @param Context $context
*
* @return Compound
*/
private function resolveCompoundType($type, Context $context)
{
$types = [];
foreach (explode('|', $type) as $part) {
$types[] = $this->resolve($part, $context);
}
return new Compound($types);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Resource implements Type
{
public function __toString()
{
return 'resource';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
class Scalar implements Type
{
public function __toString()
{
return 'scalar';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Self_ implements Type
{
public function __toString()
{
return 'self';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Static_ implements Type
{
public function __toString()
{
return 'static';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class String implements Type
{
public function __toString()
{
return 'string';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class This implements Type
{
public function __toString()
{
return '$this';
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright 2010-2015 Mike van Riel<[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
final class Void implements Type
{
public function __toString()
{
return 'void';
}
}