Introduce Types

phpDocumentor is capable of representing a whole series of types, more
than PHP supports in fact, and this commit represents those types and a
Resolver that is capable of taking a type Expression and resolving that
to a series of Value Objects representing those types.
This commit is contained in:
Mike van Riel
2015-06-08 07:05:32 +02:00
committed by Mike van Riel
parent fab7169255
commit b54671f51f
20 changed files with 370 additions and 356 deletions
+26 -2
View File
@@ -10,12 +10,21 @@
* @link http://phpdoc.org * @link http://phpdoc.org
*/ */
namespace phpDocumentor\Reflection\Types; namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
class Array_ implements Type /**
* Represents an array type as described in the PSR-5, the PHPDoc Standard.
*
* An array can be represented in two forms:
*
* 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed'.
* 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a
* type name.
*/
final class Array_ implements Type
{ {
/** @var Type */ /** @var Type */
private $valueType; private $valueType;
@@ -23,6 +32,12 @@ class Array_ implements Type
/** @var Type */ /** @var Type */
private $keyType; private $keyType;
/**
* Initializes this representation of an array with the given Type or Fqsen.
*
* @param Type $valueType
* @param Type $keyType
*/
public function __construct(Type $valueType = null, Type $keyType = null) public function __construct(Type $valueType = null, Type $keyType = null)
{ {
if ($keyType === null) { if ($keyType === null) {
@@ -37,6 +52,8 @@ class Array_ implements Type
} }
/** /**
* Returns the type for the keys of this array.
*
* @return Type * @return Type
*/ */
public function getKeyType() public function getKeyType()
@@ -45,6 +62,8 @@ class Array_ implements Type
} }
/** /**
* Returns the value for the keys of this array.
*
* @return Type * @return Type
*/ */
public function getValueType() public function getValueType()
@@ -52,6 +71,11 @@ class Array_ implements Type
return $this->valueType; return $this->valueType;
} }
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
if ($this->valueType instanceof Mixed) { if ($this->valueType instanceof Mixed) {
+8
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Boolean type.
*/
final class Boolean implements Type final class Boolean implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'bool'; return 'bool';
+8
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Callable type.
*/
final class Callable_ implements Type final class Callable_ implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'callable'; return 'callable';
-113
View File
@@ -1,113 +0,0 @@
<?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());
}
}
+26 -4
View File
@@ -10,29 +10,44 @@
* @link http://phpdoc.org * @link http://phpdoc.org
*/ */
namespace phpDocumentor\Reflection\Types; namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Compound Type.
*
* A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated
* using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type
* may contain a value with any of the given types.
*/
final class Compound implements Type final class Compound implements Type
{ {
/** @var Type[] */ /** @var Type[] */
private $types = []; private $types = [];
/** /**
* @param Type[]|Fqsen[] $types * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
*
* @param Type[] $types
*/ */
public function __construct($types) public function __construct($types)
{ {
foreach ($types as $type) {
if (!$type instanceof Type) {
throw new \InvalidArgumentException('A compound type can only have other types as elements');
}
}
$this->types = $types; $this->types = $types;
} }
/** /**
* Returns the type at the given index.
*
* @param integer $index * @param integer $index
* *
* @return null|Type|Fqsen * @return Type|null
*/ */
public function get($index) public function get($index)
{ {
@@ -44,6 +59,8 @@ final class Compound implements Type
} }
/** /**
* Tests if this compound type has a type with the given index.
*
* @param integer $index * @param integer $index
* *
* @return bool * @return bool
@@ -53,6 +70,11 @@ final class Compound implements Type
return isset($this->types[$index]); return isset($this->types[$index]);
} }
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return implode('|', $this->types); return implode('|', $this->types);
+8
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Float.
*/
final class Float implements Type final class Float implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'float'; return 'float';
+5
View File
@@ -16,6 +16,11 @@ use phpDocumentor\Reflection\Type;
final class Integer implements Type final class Integer implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'int'; return 'int';
+8
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing an unknown, or mixed, type.
*/
final class Mixed implements Type final class Mixed implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'mixed'; return 'mixed';
+8
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing a null value or type.
*/
final class Null_ implements Type final class Null_ implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'null'; return 'null';
+47
View File
@@ -12,12 +12,59 @@
namespace phpDocumentor\Reflection\Types; namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing an object.
*
* An object can be either typed or untyped. When an object is typed it means that it has an identifier, the FQSEN,
* pointing to an element in PHP. Object types that are untyped do not refer to a specific class but represent objects
* in general.
*/
final class Object_ implements Type final class Object_ implements Type
{ {
/** @var Fqsen|null */
private $fqsen;
/**
* Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'.
*
* @param Fqsen $fqsen
*/
public function __construct(Fqsen $fqsen = null)
{
if (strpos((string)$fqsen, '::') !== false || strpos((string)$fqsen, '()') !== false) {
throw new \InvalidArgumentException(
'Object types can only refer to a class, interface or trait but a method, function, constant or '
. 'property was received: ' . (string)$fqsen
);
}
$this->fqsen = $fqsen;
}
/**
* Returns the FQSEN associated with this object.
*
* @return Fqsen|null
*/
public function getFqsen()
{
return $this->fqsen;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
if ($this->fqsen) {
return (string)$this->fqsen;
}
return 'object'; return 'object';
} }
} }
+32 -6
View File
@@ -65,7 +65,7 @@ class Resolver
* @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
* replaced with another namespace. * replaced with another namespace.
* *
* @return Fqsen|Type|null * @return Type|null
*/ */
public function resolve($type, Context $context) public function resolve($type, Context $context)
{ {
@@ -101,6 +101,32 @@ class Resolver
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
/**
* Adds a keyword to the list of Keywords and associates it with a specific Value Object.
*
* @param string $keyword
* @param string $typeClassName
*
* @return void
*/
public function addKeyword($keyword, $typeClassName)
{
if (!class_exists($typeClassName)) {
throw new \InvalidArgumentException(
'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
. ' but we could not find the class ' . $typeClassName
);
}
if (!in_array(Type::class, class_implements($typeClassName))) {
throw new \InvalidArgumentException(
'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"'
);
}
$this->keywords[$keyword] = $typeClassName;
}
/** /**
* Detects whether the given type represents an array. * Detects whether the given type represents an array.
* *
@@ -193,11 +219,11 @@ class Resolver
* *
* @param string $type * @param string $type
* *
* @return Fqsen * @return Object_
*/ */
private function resolveFqsen($type) private function resolveFqsen($type)
{ {
return new Fqsen($type); return new Object_(new Fqsen($type));
} }
/** /**
@@ -207,7 +233,7 @@ class Resolver
* @param string $type * @param string $type
* @param Context $context * @param Context $context
* *
* @return Fqsen * @return Object_
*/ */
private function resolvePartialStructuralElementName($type, Context $context) private function resolvePartialStructuralElementName($type, Context $context)
{ {
@@ -222,12 +248,12 @@ class Resolver
$namespace .= self::OPERATOR_NAMESPACE; $namespace .= self::OPERATOR_NAMESPACE;
} }
return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type); return new Object_(new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type));
} }
$typeParts[0] = $namespaceAliases[$typeParts[0]]; $typeParts[0] = $namespaceAliases[$typeParts[0]];
return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)); return new Object_(new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)));
} }
/** /**
+8
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'resource' Type.
*/
final class Resource implements Type final class Resource implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'resource'; return 'resource';
+9 -1
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
class Scalar implements Type /**
* Value Object representing the 'scalar' pseudo-type, which is either a string, integer, float or boolean.
*/
final class Scalar implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'scalar'; return 'scalar';
+10
View File
@@ -14,8 +14,18 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'self' type.
*
* Self, as a Type, represents the class in which the associated element was defined.
*/
final class Self_ implements Type final class Self_ implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'self'; return 'self';
+15
View File
@@ -14,8 +14,23 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'static' type.
*
* Self, as a Type, represents the class in which the associated element was called. This differs from self as self does
* not take inheritance into account but static means that the return type is always that of the class of the called
* element.
*
* See the documentation on late static binding in the PHP Documentation for more information on the difference between
* static and self.
*/
final class Static_ implements Type final class Static_ implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'static'; return 'static';
+8
View File
@@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing the type 'string'.
*/
final class String implements Type final class String implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'string'; return 'string';
+11
View File
@@ -14,8 +14,19 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing the '$this' pseudo-type.
*
* $this, as a Type, represents the instance of the class associated with the element as it was called. $this is
* commonly used when documenting fluent interfaces since it represents that the same object is returned.
*/
final class This implements Type final class This implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return '$this'; return '$this';
+11
View File
@@ -14,8 +14,19 @@ namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\Type;
/**
* Value Object representing the pseudo-type 'void'.
*
* Void is generally only used when working with return types as it signifies that the method intentionally does not
* return any value.
*/
final class Void implements Type final class Void implements Type
{ {
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString() public function __toString()
{ {
return 'void'; return 'void';
@@ -1,221 +0,0 @@
<?php
/**
* phpDocumentor
*
* PHP Version 5.3
*
* @author Mike van Riel <[email protected]>
* @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Type;
use phpDocumentor\Reflection\DocBlock\Context;
/**
* Collection
*
* @author Mike van Riel <[email protected]>
* @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
class Collection extends \ArrayObject
{
/** @var string Definition of the OR operator for types */
const OPERATOR_OR = '|';
/** @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 */
protected static $keywords = array(
'string', 'int', 'integer', 'bool', 'boolean', 'float', 'double',
'object', 'mixed', 'array', 'resource', 'void', 'null', 'scalar',
'callback', 'callable', 'false', 'true', 'self', '$this', 'static'
);
/**
* 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;
/**
* 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 $location The current invoking location.
*/
public function __construct(
array $types = array(),
Context $context = null
) {
$this->context = null === $context ? new Context() : $context;
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->expand($part);
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());
}
/**
* 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 getNamespace to determine with what to prefix the type name.
* @uses getNamespaceAliases to check whether the first part of the relative
* type name should not be replaced with another namespace.
*
* @return string
*/
protected function expand($type)
{
$type = trim($type);
if (!$type) {
return '';
}
if ($this->isTypeAnArray($type)) {
return $this->expand(substr($type, 0, -2)) . self::OPERATOR_ARRAY;
}
if ($this->isRelativeType($type) && !$this->isTypeAKeyword($type)) {
$type_parts = explode(self::OPERATOR_NAMESPACE, $type, 2);
$namespace_aliases = $this->context->getNamespaceAliases();
// if the first segment is not an alias; prepend namespace name and
// return
if (!isset($namespace_aliases[$type_parts[0]])) {
$namespace = $this->context->getNamespace();
if ('' !== $namespace) {
$namespace .= self::OPERATOR_NAMESPACE;
}
return self::OPERATOR_NAMESPACE . $namespace . $type;
}
$type_parts[0] = $namespace_aliases[$type_parts[0]];
$type = implode(self::OPERATOR_NAMESPACE, $type_parts);
}
return $type;
}
/**
* Detects whether the given type represents an array.
*
* @param string $type A relative or absolute type as defined in the
* phpDocumentor documentation.
*
* @return bool
*/
protected function isTypeAnArray($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
*/
protected function isTypeAKeyword($type)
{
return in_array(strtolower($type), static::$keywords, true);
}
/**
* Detects whether the given type represents a relative or absolute path.
*
* This method will detect keywords as being absolute; even though they are
* not preceeded by a namespace separator.
*
* @param string $type A relative or absolute type as defined in the
* phpDocumentor documentation.
*
* @return bool
*/
protected function isRelativeType($type)
{
return ($type[0] !== self::OPERATOR_NAMESPACE)
|| $this->isTypeAKeyword($type);
}
}
@@ -12,7 +12,9 @@
namespace phpDocumentor\Reflection\Types; namespace phpDocumentor\Reflection\Types;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock\Context; use phpDocumentor\Reflection\DocBlock\Context;
use phpDocumentor\Reflection\Type;
/** /**
* @coversDefaultClass phpDocumentor\Reflection\Types\Resolver * @coversDefaultClass phpDocumentor\Reflection\Types\Resolver
@@ -26,8 +28,9 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
* @covers ::resolve * @covers ::resolve
* @covers ::<private> * @covers ::<private>
* *
* @uses phpDocumentor\Reflection\DocBlock\Context * @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Array_ * @uses phpDocumentor\Reflection\Types\Array_
* @uses phpDocumentor\Reflection\Types\Object_
* *
* @dataProvider provideKeywords * @dataProvider provideKeywords
*/ */
@@ -46,8 +49,9 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
* @covers ::resolve * @covers ::resolve
* @covers ::<private> * @covers ::<private>
* *
* @uses phpDocumentor\Reflection\DocBlock\Context * @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Fqsen * @uses phpDocumentor\Reflection\Types\Object_
* @uses phpDocumentor\Reflection\Fqsen
* *
* @dataProvider provideFqsen * @dataProvider provideFqsen
*/ */
@@ -55,9 +59,11 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
{ {
$fixture = new Resolver(); $fixture = new Resolver();
/** @var Object_ $resolvedType */
$resolvedType = $fixture->resolve($fqsen, new Context('')); $resolvedType = $fixture->resolve($fqsen, new Context(''));
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType); $this->assertInstanceOf('phpDocumentor\Reflection\Types\Object_', $resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType->getFqsen());
$this->assertSame($fqsen, (string)$resolvedType); $this->assertSame($fqsen, (string)$resolvedType);
} }
@@ -67,15 +73,18 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
* @covers ::<private> * @covers ::<private>
* *
* @uses phpDocumentor\Reflection\DocBlock\Context * @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Object_
* @uses phpDocumentor\Reflection\Fqsen * @uses phpDocumentor\Reflection\Fqsen
*/ */
public function testResolvingRelativeQSENsBasedOnNamespace() public function testResolvingRelativeQSENsBasedOnNamespace()
{ {
$fixture = new Resolver(); $fixture = new Resolver();
/** @var Object_ $resolvedType */
$resolvedType = $fixture->resolve('DocBlock', new Context('phpDocumentor\Reflection')); $resolvedType = $fixture->resolve('DocBlock', new Context('phpDocumentor\Reflection'));
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType); $this->assertInstanceOf('phpDocumentor\Reflection\Types\Object_', $resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType->getFqsen());
$this->assertSame('\phpDocumentor\Reflection\DocBlock', (string)$resolvedType); $this->assertSame('\phpDocumentor\Reflection\DocBlock', (string)$resolvedType);
} }
@@ -84,18 +93,21 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
* @covers ::<private> * @covers ::<private>
* *
* @uses phpDocumentor\Reflection\DocBlock\Context * @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Object_
* @uses phpDocumentor\Reflection\Fqsen * @uses phpDocumentor\Reflection\Fqsen
*/ */
public function testResolvingRelativeQSENsBasedOnNamespaceAlias() public function testResolvingRelativeQSENsBasedOnNamespaceAlias()
{ {
$fixture = new Resolver(); $fixture = new Resolver();
/** @var Object_ $resolvedType */
$resolvedType = $fixture->resolve( $resolvedType = $fixture->resolve(
'm\MockInterface', 'm\MockInterface',
new Context('phpDocumentor\Reflection', ['m' => '\Mockery']) new Context('phpDocumentor\Reflection', ['m' => '\Mockery'])
); );
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType); $this->assertInstanceOf('phpDocumentor\Reflection\Types\Object_', $resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType->getFqsen());
$this->assertSame('\Mockery\MockInterface', (string)$resolvedType); $this->assertSame('\Mockery\MockInterface', (string)$resolvedType);
} }
@@ -156,6 +168,7 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
* @uses phpDocumentor\Reflection\DocBlock\Context * @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Compound * @uses phpDocumentor\Reflection\Types\Compound
* @uses phpDocumentor\Reflection\Types\String * @uses phpDocumentor\Reflection\Types\String
* @uses phpDocumentor\Reflection\Types\Object_
* @uses phpDocumentor\Reflection\Fqsen * @uses phpDocumentor\Reflection\Fqsen
*/ */
public function testResolvingCompoundTypes() public function testResolvingCompoundTypes()
@@ -167,8 +180,98 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Compound', $resolvedType); $this->assertInstanceOf('phpDocumentor\Reflection\Types\Compound', $resolvedType);
$this->assertSame('string|\phpDocumentor\Reflection\DocBlock', (string)$resolvedType); $this->assertSame('string|\phpDocumentor\Reflection\DocBlock', (string)$resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\String', $resolvedType->get(0));
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType->get(1)); /** @var String $secondType */
$firstType = $resolvedType->get(0);
/** @var Object_ $secondType */
$secondType = $resolvedType->get(1);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\String', $firstType);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Object_', $secondType);
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $secondType->getFqsen());
}
/**
* This test asserts that the parameter order is correct.
*
* When you pass two arrays separated by the compound operator (i.e. 'integer[]|string[]') then we always split the
* expression in its compound parts and then we parse the types with the array operators. If we were to switch the
* order around then 'integer[]|string[]' would read as an array of string or integer array; which is something
* other than what we intend.
*
* @covers ::resolve
* @covers ::<private>
*
* @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Compound
* @uses phpDocumentor\Reflection\Types\Array_
* @uses phpDocumentor\Reflection\Types\Integer
* @uses phpDocumentor\Reflection\Types\String
*/
public function testResolvingCompoundTypesWithTwoArrays()
{
$fixture = new Resolver();
/** @var Compound $resolvedType */
$resolvedType = $fixture->resolve('integer[]|string[]', new Context(''));
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Compound', $resolvedType);
$this->assertSame('int[]|string[]', (string)$resolvedType);
/** @var Array_ $firstType */
$firstType = $resolvedType->get(0);
/** @var Array_ $secondType */
$secondType = $resolvedType->get(1);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Array_', $firstType);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Integer', $firstType->getValueType());
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Array_', $secondType);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\String', $secondType->getValueType());
}
/**
* @covers ::addKeyword
* @uses phpDocumentor\Reflection\Types\Resolver::resolve
* @uses phpDocumentor\Reflection\Types\Resolver::<private>
* @uses phpDocumentor\Reflection\DocBlock\Context
*/
public function testAddingAKeyword()
{
// Assign
$typeMock = m::mock(Type::class);
// Act
$fixture = new Resolver();
$fixture->addKeyword('mock', get_class($typeMock));
// Assert
$result = $fixture->resolve('mock', new Context(''));
$this->assertInstanceOf(get_class($typeMock), $result);
$this->assertNotSame($typeMock, $result);
}
/**
* @covers ::addKeyword
* @uses phpDocumentor\Reflection\DocBlock\Context
* @expectedException \InvalidArgumentException
*/
public function testAddingAKeywordFailsIfTypeClassDoesNotExist()
{
$fixture = new Resolver();
$fixture->addKeyword('mock', 'IDoNotExist');
}
/**
* @covers ::addKeyword
* @uses phpDocumentor\Reflection\DocBlock\Context
* @expectedException \InvalidArgumentException
*/
public function testAddingAKeywordFailsIfTypeClassDoesNotImplementTypeInterface()
{
$fixture = new Resolver();
$fixture->addKeyword('mock', 'stdClass');
} }
/** /**
@@ -195,6 +298,11 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
$fixture->resolve(['a'], new Context('')); $fixture->resolve(['a'], new Context(''));
} }
/**
* Returns a list of keywords and expected classes that are created from them.
*
* @return string[][]
*/
public function provideKeywords() public function provideKeywords()
{ {
return [ return [
@@ -220,6 +328,11 @@ class ResolverTest extends \PHPUnit_Framework_TestCase
]; ];
} }
/**
* Provides a list of FQSENs to test the resolution patterns with.
*
* @return string[][]
*/
public function provideFqsen() public function provideFqsen()
{ {
return [ return [