diff --git a/src/Types/Array_.php b/src/Types/Array_.php index ffb364a..cf5d7aa 100644 --- a/src/Types/Array_.php +++ b/src/Types/Array_.php @@ -10,12 +10,21 @@ * @link http://phpdoc.org */ - namespace phpDocumentor\Reflection\Types; +use phpDocumentor\Reflection\Fqsen; 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 */ private $valueType; @@ -23,6 +32,12 @@ class Array_ implements Type /** @var Type */ 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) { if ($keyType === null) { @@ -37,6 +52,8 @@ class Array_ implements Type } /** + * Returns the type for the keys of this array. + * * @return Type */ public function getKeyType() @@ -45,6 +62,8 @@ class Array_ implements Type } /** + * Returns the value for the keys of this array. + * * @return Type */ public function getValueType() @@ -52,6 +71,11 @@ class Array_ implements Type return $this->valueType; } + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ public function __toString() { if ($this->valueType instanceof Mixed) { diff --git a/src/Types/Boolean.php b/src/Types/Boolean.php index 08db91c..f82b19e 100644 --- a/src/Types/Boolean.php +++ b/src/Types/Boolean.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; +/** + * Value Object representing a Boolean 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() { return 'bool'; diff --git a/src/Types/Callable_.php b/src/Types/Callable_.php index 4479a93..68ebfbd 100644 --- a/src/Types/Callable_.php +++ b/src/Types/Callable_.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; +/** + * Value Object representing a Callable 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() { return 'callable'; diff --git a/src/Types/Collection.php b/src/Types/Collection.php deleted file mode 100644 index 796d9e7..0000000 --- a/src/Types/Collection.php +++ /dev/null @@ -1,113 +0,0 @@ - - * @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()); - } -} diff --git a/src/Types/Compound.php b/src/Types/Compound.php index 5421ede..ac099c8 100644 --- a/src/Types/Compound.php +++ b/src/Types/Compound.php @@ -10,29 +10,44 @@ * @link http://phpdoc.org */ - namespace phpDocumentor\Reflection\Types; -use phpDocumentor\Reflection\Fqsen; 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 { /** @var Type[] */ 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) { + foreach ($types as $type) { + if (!$type instanceof Type) { + throw new \InvalidArgumentException('A compound type can only have other types as elements'); + } + } + $this->types = $types; } /** + * Returns the type at the given index. + * * @param integer $index * - * @return null|Type|Fqsen + * @return Type|null */ 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 * * @return bool @@ -53,6 +70,11 @@ final class Compound implements Type 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() { return implode('|', $this->types); diff --git a/src/Types/Float.php b/src/Types/Float.php index a51dcaa..bc32978 100644 --- a/src/Types/Float.php +++ b/src/Types/Float.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; +/** + * Value Object representing a Float. + */ 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() { return 'float'; diff --git a/src/Types/Integer.php b/src/Types/Integer.php index 99c8f96..be4555e 100644 --- a/src/Types/Integer.php +++ b/src/Types/Integer.php @@ -16,6 +16,11 @@ use phpDocumentor\Reflection\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() { return 'int'; diff --git a/src/Types/Mixed.php b/src/Types/Mixed.php index 904315b..79695f4 100644 --- a/src/Types/Mixed.php +++ b/src/Types/Mixed.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; +/** + * Value Object representing an unknown, or mixed, 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() { return 'mixed'; diff --git a/src/Types/Null_.php b/src/Types/Null_.php index 6a78cb8..203b422 100644 --- a/src/Types/Null_.php +++ b/src/Types/Null_.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; +/** + * Value Object representing a null value or 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() { return 'null'; diff --git a/src/Types/Object_.php b/src/Types/Object_.php index 77cecb0..b337c71 100644 --- a/src/Types/Object_.php +++ b/src/Types/Object_.php @@ -12,12 +12,59 @@ namespace phpDocumentor\Reflection\Types; +use phpDocumentor\Reflection\Fqsen; 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 { + /** @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() { + if ($this->fqsen) { + return (string)$this->fqsen; + } + return 'object'; } } diff --git a/src/Types/Resolver.php b/src/Types/Resolver.php index 451bbb0..b0ed9ea 100644 --- a/src/Types/Resolver.php +++ b/src/Types/Resolver.php @@ -65,7 +65,7 @@ class Resolver * @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 + * @return Type|null */ public function resolve($type, Context $context) { @@ -101,6 +101,32 @@ class Resolver // @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. * @@ -193,11 +219,11 @@ class Resolver * * @param string $type * - * @return Fqsen + * @return Object_ */ private function resolveFqsen($type) { - return new Fqsen($type); + return new Object_(new Fqsen($type)); } /** @@ -207,7 +233,7 @@ class Resolver * @param string $type * @param Context $context * - * @return Fqsen + * @return Object_ */ private function resolvePartialStructuralElementName($type, Context $context) { @@ -222,12 +248,12 @@ class Resolver $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]]; - return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)); + return new Object_(new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts))); } /** diff --git a/src/Types/Resource.php b/src/Types/Resource.php index 2a8a201..2c2526b 100644 --- a/src/Types/Resource.php +++ b/src/Types/Resource.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; +/** + * Value Object representing the 'resource' 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() { return 'resource'; diff --git a/src/Types/Scalar.php b/src/Types/Scalar.php index 5dadde8..1e2a660 100644 --- a/src/Types/Scalar.php +++ b/src/Types/Scalar.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; 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() { return 'scalar'; diff --git a/src/Types/Self_.php b/src/Types/Self_.php index 933b4b8..1ba3fc5 100644 --- a/src/Types/Self_.php +++ b/src/Types/Self_.php @@ -14,8 +14,18 @@ namespace phpDocumentor\Reflection\Types; 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 { + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ public function __toString() { return 'self'; diff --git a/src/Types/Static_.php b/src/Types/Static_.php index 4922fd3..9eb6729 100644 --- a/src/Types/Static_.php +++ b/src/Types/Static_.php @@ -14,8 +14,23 @@ namespace phpDocumentor\Reflection\Types; 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 { + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ public function __toString() { return 'static'; diff --git a/src/Types/String.php b/src/Types/String.php index 8e4e5a2..ad2c842 100644 --- a/src/Types/String.php +++ b/src/Types/String.php @@ -14,8 +14,16 @@ namespace phpDocumentor\Reflection\Types; use phpDocumentor\Reflection\Type; +/** + * Value Object representing the type 'string'. + */ 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() { return 'string'; diff --git a/src/Types/This.php b/src/Types/This.php index d0aec28..c098a93 100644 --- a/src/Types/This.php +++ b/src/Types/This.php @@ -14,8 +14,19 @@ namespace phpDocumentor\Reflection\Types; 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 { + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ public function __toString() { return '$this'; diff --git a/src/Types/Void.php b/src/Types/Void.php index f323349..6a6156b 100644 --- a/src/Types/Void.php +++ b/src/Types/Void.php @@ -14,8 +14,19 @@ namespace phpDocumentor\Reflection\Types; 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 { + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ public function __toString() { return 'void'; diff --git a/src/phpDocumentor/Reflection/DocBlock/Type/Collection.php b/src/phpDocumentor/Reflection/DocBlock/Type/Collection.php deleted file mode 100644 index 90ead3f..0000000 --- a/src/phpDocumentor/Reflection/DocBlock/Type/Collection.php +++ /dev/null @@ -1,221 +0,0 @@ - - * @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 - * @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); - } -} diff --git a/tests/phpDocumentor/Reflection/Types/ResolverTest.php b/tests/phpDocumentor/Reflection/Types/ResolverTest.php index 2c07b9e..6e90643 100644 --- a/tests/phpDocumentor/Reflection/Types/ResolverTest.php +++ b/tests/phpDocumentor/Reflection/Types/ResolverTest.php @@ -12,7 +12,9 @@ namespace phpDocumentor\Reflection\Types; +use Mockery as m; use phpDocumentor\Reflection\DocBlock\Context; +use phpDocumentor\Reflection\Type; /** * @coversDefaultClass phpDocumentor\Reflection\Types\Resolver @@ -26,8 +28,9 @@ class ResolverTest extends \PHPUnit_Framework_TestCase * @covers ::resolve * @covers :: * - * @uses phpDocumentor\Reflection\DocBlock\Context - * @uses phpDocumentor\Reflection\Types\Array_ + * @uses phpDocumentor\Reflection\DocBlock\Context + * @uses phpDocumentor\Reflection\Types\Array_ + * @uses phpDocumentor\Reflection\Types\Object_ * * @dataProvider provideKeywords */ @@ -46,8 +49,9 @@ class ResolverTest extends \PHPUnit_Framework_TestCase * @covers ::resolve * @covers :: * - * @uses phpDocumentor\Reflection\DocBlock\Context - * @uses phpDocumentor\Reflection\Fqsen + * @uses phpDocumentor\Reflection\DocBlock\Context + * @uses phpDocumentor\Reflection\Types\Object_ + * @uses phpDocumentor\Reflection\Fqsen * * @dataProvider provideFqsen */ @@ -55,9 +59,11 @@ class ResolverTest extends \PHPUnit_Framework_TestCase { $fixture = new Resolver(); + /** @var Object_ $resolvedType */ $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); } @@ -67,15 +73,18 @@ class ResolverTest extends \PHPUnit_Framework_TestCase * @covers :: * * @uses phpDocumentor\Reflection\DocBlock\Context + * @uses phpDocumentor\Reflection\Types\Object_ * @uses phpDocumentor\Reflection\Fqsen */ public function testResolvingRelativeQSENsBasedOnNamespace() { $fixture = new Resolver(); + /** @var Object_ $resolvedType */ $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); } @@ -84,18 +93,21 @@ class ResolverTest extends \PHPUnit_Framework_TestCase * @covers :: * * @uses phpDocumentor\Reflection\DocBlock\Context + * @uses phpDocumentor\Reflection\Types\Object_ * @uses phpDocumentor\Reflection\Fqsen */ public function testResolvingRelativeQSENsBasedOnNamespaceAlias() { $fixture = new Resolver(); + /** @var Object_ $resolvedType */ $resolvedType = $fixture->resolve( 'm\MockInterface', 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); } @@ -156,6 +168,7 @@ class ResolverTest extends \PHPUnit_Framework_TestCase * @uses phpDocumentor\Reflection\DocBlock\Context * @uses phpDocumentor\Reflection\Types\Compound * @uses phpDocumentor\Reflection\Types\String + * @uses phpDocumentor\Reflection\Types\Object_ * @uses phpDocumentor\Reflection\Fqsen */ public function testResolvingCompoundTypes() @@ -167,8 +180,98 @@ class ResolverTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('phpDocumentor\Reflection\Types\Compound', $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 :: + * + * @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:: + * @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('')); } + /** + * Returns a list of keywords and expected classes that are created from them. + * + * @return string[][] + */ public function provideKeywords() { 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() { return [