Merge pull request #54 from mvriel/feature/refactor-to-v2

Add support for types and improve resolution
This commit is contained in:
Mike van Riel
2015-06-10 14:55:27 +02:00
21 changed files with 1319 additions and 221 deletions
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace phpDocumentor\Reflection;
interface DocBlockFactoryInterface
{
/**
* Factory method for easy instantiation.
*
* @param string[] $additionalTags
*
* @return DocBlockFactory
*/
public static function createInstance(array $additionalTags = []);
/**
* @param string $docblock
* @param DocBlock\Context $context
* @param DocBlock\Location $location
*
* @return DocBlock
*/
public function create($docblock, DocBlock\Context $context = null, DocBlock\Location $location = null);
}
+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();
}
+87
View File
@@ -0,0 +1,87 @@
<?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;
/**
* 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;
/** @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) {
$keyType = new Mixed();
}
if ($valueType === null) {
$valueType = new Mixed();
}
$this->valueType = $valueType;
$this->keyType = $keyType;
}
/**
* Returns the type for the keys of this array.
*
* @return Type
*/
public function getKeyType()
{
return $this->keyType;
}
/**
* Returns the value for the keys of this array.
*
* @return Type
*/
public function getValueType()
{
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) {
return 'array';
}
return $this->valueType . '[]';
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+82
View File
@@ -0,0 +1,82 @@
<?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;
/**
* 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 = [];
/**
* 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 Type|null
*/
public function get($index)
{
if (!$this->has($index)) {
return null;
}
return $this->types[$index];
}
/**
* Tests if this compound type has a type with the given index.
*
* @param integer $index
*
* @return bool
*/
public function has($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()
{
return implode('|', $this->types);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+28
View File
@@ -0,0 +1,28 @@
<?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
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*
* @return string
*/
public function __toString()
{
return 'int';
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+70
View File
@@ -0,0 +1,70 @@
<?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;
/**
* 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';
}
}
+276
View File
@@ -0,0 +1,276 @@
<?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\Type;
final 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 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
}
/**
* 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.
*
* @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 Object_
*/
private function resolveFqsen($type)
{
return new Object_(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 Object_
*/
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 Object_(new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type));
}
$typeParts[0] = $namespaceAliases[$typeParts[0]];
return new Object_(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);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+33
View File
@@ -0,0 +1,33 @@
<?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;
/**
* 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';
}
}
+38
View File
@@ -0,0 +1,38 @@
<?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;
/**
* 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';
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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;
/**
* 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';
}
}
+34
View File
@@ -0,0 +1,34 @@
<?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;
/**
* 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';
}
}
+34
View File
@@ -0,0 +1,34 @@
<?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;
/**
* 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';
}
}
@@ -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);
}
}
@@ -0,0 +1,348 @@
<?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 Mockery as m;
use phpDocumentor\Reflection\DocBlock\Context;
use phpDocumentor\Reflection\Type;
/**
* @coversDefaultClass phpDocumentor\Reflection\Types\Resolver
*/
class ResolverTest extends \PHPUnit_Framework_TestCase
{
/**
* @param string $keyword
* @param string $expectedClass
*
* @covers ::resolve
* @covers ::<private>
*
* @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Array_
* @uses phpDocumentor\Reflection\Types\Object_
*
* @dataProvider provideKeywords
*/
public function testResolvingKeywords($keyword, $expectedClass)
{
$fixture = new Resolver();
$resolvedType = $fixture->resolve($keyword, new Context(''));
$this->assertInstanceOf($expectedClass, $resolvedType);
}
/**
* @param string $fqsen
*
* @covers ::resolve
* @covers ::<private>
*
* @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Object_
* @uses phpDocumentor\Reflection\Fqsen
*
* @dataProvider provideFqsen
*/
public function testResolvingFQSENs($fqsen)
{
$fixture = new Resolver();
/** @var Object_ $resolvedType */
$resolvedType = $fixture->resolve($fqsen, new Context(''));
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Object_', $resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType->getFqsen());
$this->assertSame($fqsen, (string)$resolvedType);
}
/**
* @covers ::resolve
* @covers ::<private>
*
* @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\Types\Object_', $resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType->getFqsen());
$this->assertSame('\phpDocumentor\Reflection\DocBlock', (string)$resolvedType);
}
/**
* @covers ::resolve
* @covers ::<private>
*
* @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\Types\Object_', $resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Fqsen', $resolvedType->getFqsen());
$this->assertSame('\Mockery\MockInterface', (string)$resolvedType);
}
/**
* @covers ::resolve
* @covers ::<private>
*
* @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Array_
* @uses phpDocumentor\Reflection\Types\String
*/
public function testResolvingTypedArrays()
{
$fixture = new Resolver();
/** @var Array_ $resolvedType */
$resolvedType = $fixture->resolve('string[]', new Context(''));
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Array_', $resolvedType);
$this->assertSame('string[]', (string)$resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Mixed', $resolvedType->getKeyType());
$this->assertInstanceOf('phpDocumentor\Reflection\Types\String', $resolvedType->getValueType());
}
/**
* @covers ::resolve
* @covers ::<private>
*
* @uses phpDocumentor\Reflection\DocBlock\Context
* @uses phpDocumentor\Reflection\Types\Array_
* @uses phpDocumentor\Reflection\Types\String
*/
public function testResolvingNestedTypedArrays()
{
$fixture = new Resolver();
/** @var Array_ $resolvedType */
$resolvedType = $fixture->resolve('string[][]', new Context(''));
/** @var Array_ $childValueType */
$childValueType = $resolvedType->getValueType();
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Array_', $resolvedType);
$this->assertSame('string[][]', (string)$resolvedType);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Mixed', $resolvedType->getKeyType());
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Array_', $childValueType);
$this->assertSame('string[]', (string)$childValueType);
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Mixed', $childValueType->getKeyType());
$this->assertInstanceOf('phpDocumentor\Reflection\Types\String', $childValueType->getValueType());
}
/**
* @covers ::resolve
* @covers ::<private>
*
* @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()
{
$fixture = new Resolver();
/** @var Compound $resolvedType */
$resolvedType = $fixture->resolve('string|Reflection\DocBlock', new Context('phpDocumentor'));
$this->assertInstanceOf('phpDocumentor\Reflection\Types\Compound', $resolvedType);
$this->assertSame('string|\phpDocumentor\Reflection\DocBlock', (string)$resolvedType);
/** @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');
}
/**
* @covers ::resolve
* @uses phpDocumentor\Reflection\DocBlock\Context
*
* @expectedException \InvalidArgumentException
*/
public function testExceptionIsThrownIfTypeIsEmpty()
{
$fixture = new Resolver();
$fixture->resolve(' ', new Context(''));
}
/**
* @covers ::resolve
* @uses phpDocumentor\Reflection\DocBlock\Context
*
* @expectedException \InvalidArgumentException
*/
public function testExceptionIsThrownIfTypeIsNotAString()
{
$fixture = new Resolver();
$fixture->resolve(['a'], new Context(''));
}
/**
* Returns a list of keywords and expected classes that are created from them.
*
* @return string[][]
*/
public function provideKeywords()
{
return [
['string', 'phpDocumentor\Reflection\Types\String'],
['int', 'phpDocumentor\Reflection\Types\Integer'],
['integer', 'phpDocumentor\Reflection\Types\Integer'],
['float', 'phpDocumentor\Reflection\Types\Float'],
['double', 'phpDocumentor\Reflection\Types\Float'],
['bool', 'phpDocumentor\Reflection\Types\Boolean'],
['boolean', 'phpDocumentor\Reflection\Types\Boolean'],
['resource', 'phpDocumentor\Reflection\Types\Resource'],
['null', 'phpDocumentor\Reflection\Types\Null_'],
['callable', 'phpDocumentor\Reflection\Types\Callable_'],
['callback', 'phpDocumentor\Reflection\Types\Callable_'],
['array', 'phpDocumentor\Reflection\Types\Array_'],
['scalar', 'phpDocumentor\Reflection\Types\Scalar'],
['object', 'phpDocumentor\Reflection\Types\Object_'],
['mixed', 'phpDocumentor\Reflection\Types\Mixed'],
['void', 'phpDocumentor\Reflection\Types\Void'],
['$this', 'phpDocumentor\Reflection\Types\This'],
['static', 'phpDocumentor\Reflection\Types\Static_'],
['self', 'phpDocumentor\Reflection\Types\Self_'],
];
}
/**
* Provides a list of FQSENs to test the resolution patterns with.
*
* @return string[][]
*/
public function provideFqsen()
{
return [
'namespace' => ['\phpDocumentor\Reflection'],
'class' => ['\phpDocumentor\Reflection\DocBlock'],
'function' => ['\DI\object()'],
'constant' => ['\phpDocumentor\Reflection\GLOBAL_CONSTANT'],
'classConstant' => ['\phpDocumentor\Reflection\DocBlock::CONSTANT'],
'property' => ['\phpDocumentor\Reflection\DocBlock::$summary'],
'method' => ['\phpDocumentor\Reflection\DocBlock::getSummary()'],
];
}
}