Compare commits

..
7 changed files with 403 additions and 32 deletions
+109 -4
View File
@@ -36,14 +36,36 @@ class DocBlock implements \Reflector
*/ */
protected $tags = array(); protected $tags = array();
/** @var string the current namespace */
protected $namespace = '\\';
/** @var string[] List of namespace aliases => Fully Qualified Namespace */
protected $namespace_aliases = array();
/** /**
* Parses the given docblock and populates the member fields. * Parses the given docblock and populates the member fields.
* *
* @param string|\Reflector $docblock A docblock comment (including asterisks) * The constructor may also receive namespace information such as the
* current namespace and aliases. This information is used in the
* {@link expandType()} method to transform a relative Type into a FQCN.
*
* For example the param and return tags use this to expand their type
* information.
*
* @param \Reflector|string $docblock A docblock comment (including asterisks)
* or reflector supporting the getDocComment method. * or reflector supporting the getDocComment method.
* @param string $namespace The namespace where this DocBlock resides in;
* defaults to `\`.
* @param string[] $namespace_aliases a list of namespace aliases as
* provided by the `use` keyword; the key of the array is the alias name
* or last part of the alias array if no alias name is provided.
*
* @throws \InvalidArgumentException if the given argument does not have the
* getDocComment method.
*/ */
public function __construct($docblock) public function __construct(
{ $docblock, $namespace = '\\', $namespace_aliases = array()
) {
if (is_object($docblock)) { if (is_object($docblock)) {
if (!method_exists($docblock, 'getDocComment')) { if (!method_exists($docblock, 'getDocComment')) {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
@@ -61,6 +83,9 @@ class DocBlock implements \Reflector
$this->short_description = $short; $this->short_description = $short;
$this->long_description = new DocBlock\LongDescription($long); $this->long_description = new DocBlock\LongDescription($long);
$this->parseTags($tags); $this->parseTags($tags);
$this->namespace = $namespace;
$this->namespace_aliases = $namespace_aliases;
} }
/** /**
@@ -192,7 +217,9 @@ class DocBlock implements \Reflector
// create proper Tag objects // create proper Tag objects
foreach ($result as $key => $tag_line) { foreach ($result as $key => $tag_line) {
$result[$key] = DocBlock\Tag::createInstance($tag_line); $tag = DocBlock\Tag::createInstance($tag_line);
$tag->setDocBlock($this);
$result[$key] = $tag;
} }
$this->tags = $result; $this->tags = $result;
@@ -271,6 +298,84 @@ class DocBlock implements \Reflector
return false; return false;
} }
/**
* Tries to expand a type to it's full namespaced equivalent (FQCN).
*
* This method will take the given type and examine the current namespace
* and namespace aliases to see whether it should expand it into a FQCN
* as defined by the rules in PHP.
*
* @param string $type Type to expand into full namespaced
* equivalent.
* @param string[] $ignore_keywords Whether to ignore given keywords, when
* null it will use the default keywords: 'string', 'int', 'integer',
* 'bool', 'boolean', 'float', 'double', 'object', 'mixed', 'array',
* 'resource', 'void', 'null', 'callback', 'false', 'true'.
* Default value for this parameter is null.
*
* @return string
*/
public function expandType($type, $ignore_keywords = null)
{
if ($type === null) {
return null;
}
if ($ignore_keywords === null) {
$ignore_keywords = array(
'string', 'int', 'integer', 'bool', 'boolean', 'float', 'double',
'object', 'mixed', 'array', 'resource', 'void', 'null',
'callback', 'false', 'true'
);
}
$namespace = '';
if ($this->namespace != 'default' && $this->namespace != 'global') {
$namespace = rtrim($this->namespace, '\\') . '\\';
}
$type = explode('|', $type);
foreach ($type as &$item) {
$item = trim($item);
// add support for array notation
$is_array = false;
if (substr($item, -2) == '[]') {
$item = substr($item, 0, -2);
$is_array = true;
}
if ((substr($item, 0, 1) != '\\')
&& (!in_array(strtolower($item), $ignore_keywords))
) {
$type_parts = explode('\\', $item);
// if the first segment is an alias; replace with full name
if (isset($this->namespace_aliases[$type_parts[0]])) {
$type_parts[0] = $this->namespace_aliases[$type_parts[0]];
$item = implode('\\', $type_parts);
} else {
// otherwise prepend the current namespace
$item = $namespace . $item;
}
}
// full paths always start with a slash
if (isset($item[0]) && ($item[0] !== '\\')
&& (!in_array(strtolower($item), $ignore_keywords))
) {
$item = '\\' . $item;
}
// re-add the array notation markers
if ($is_array) {
$item .= '[]';
}
}
return implode('|', $type);
}
/** /**
* Builds a string representation of this object. * Builds a string representation of this object.
* *
@@ -33,7 +33,7 @@ class Tag implements \Reflector
/** @var int line number of the tag */ /** @var int line number of the tag */
protected $line_number = 0; protected $line_number = 0;
/** @var object docblock class */ /** @var \phpDocumentor\Reflection\DocBlock docblock class */
protected $docblock; protected $docblock;
/** /**
@@ -63,7 +63,12 @@ class ParamTag extends Tag
public function getTypes() public function getTypes()
{ {
$types = explode('|', $this->type); $types = explode('|', $this->type);
array_walk($types, 'trim'); foreach ($types as &$type) {
$type = !empty($type) && $this->docblock
? $this->docblock->expandType($type)
: trim($type);
}
return $types; return $types;
} }
@@ -74,7 +79,7 @@ class ParamTag extends Tag
*/ */
public function getType() public function getType()
{ {
return $this->type; return $this->docblock->expandType($this->type);
} }
/** /**
@@ -42,25 +42,4 @@ class ReturnTag extends ParamTag
$this->description = implode(' ', $content); $this->description = implode(' ', $content);
} }
/**
* Returns the type of the variable.
*
* @return string
*/
public function getTypes()
{
$types = explode('|', $this->type);
array_walk($types, 'trim');
return $types;
}
/**
* Returns the type of the variable.
*
* @return string
*/
public function getType()
{
return $this->type;
}
} }
@@ -0,0 +1,118 @@
<?php
/**
* phpDocumentor Param tag test.
*
* @author Mike van Riel <[email protected]>
* @copyright Copyright (c) 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com)
*/
namespace phpDocumentor\Reflection\DocBlock\Tag;
require_once __DIR__ . '/../../../../../src/phpDocumentor/Reflection/DocBlock/Tag/ParamTag.php';
/**
* Test class for phpDocumentor_Reflection_DocBlock_Param.
*
* @author Mike van Riel <[email protected]>
* @copyright Copyright (c) 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com)
*/
class ParamTagTest extends \PHPUnit_Framework_TestCase
{
/**
* Test that the \phpDocumentor\Reflection\DocBlock\Tag\ParamTag can
* understand the param DocBlock.
*
* @param string $type
* @param string $content
* @param string $extracted_type
* @param string $extracted_variable_name
* @param string $extracted_description
*
* @covers \phpDocumentor\Reflection\DocBlock\Tag\ParamTag::__construct
*
* @dataProvider provideDataForConstructor
*
* @return void
*/
public function testConstructorParsesInputsIntoCorrectFields(
$type, $content, $extracted_type, $extracted_variable_name,
$extracted_description
) {
$tag = new ParamTag($type, $content);
$this->assertEquals($extracted_type, $tag->getTypes());
$this->assertEquals($extracted_variable_name, $tag->getVariableName());
$this->assertEquals($extracted_description, $tag->getDescription());
}
/**
* Tests whether the getTypes method correctly converts the given tags.
*
* @param string $type Type string to test
* @param string[] $expected Array of expected types
*
* @covers \phpDocumentor\Reflection\DocBlock\Tag\ParamTag::getTypes()
*
* @dataProvider provideTypesToExpand
*/
public function testExpandTypeIntoCorrectFcqn($type, $expected)
{
$docblock = new \phpDocumentor\Reflection\DocBlock(
'', '\My\Namespace', array('Alias' => '\My\Namespace\Aliasing')
);
$tag = new ParamTag('param', $type.' $my_type');
$tag->setDocBlock($docblock);
$this->assertEquals($expected, $tag->getTypes());
}
/**
* Data provider for testConstructorParsesInputsIntoCorrectFields()
*
* @return array
*/
public function provideDataForConstructor()
{
return array(
array('param', 'int', array(''), '', 'int'),
array('param', '$bob', array(''), '$bob', ''),
array(
'param', 'int Number of bobs', array('int'), '',
'Number of bobs'
),
array('param', 'int $bob', array('int'), '$bob', ''),
array(
'param', 'int $bob Number of bobs', array('int'), '$bob',
'Number of bobs'
),
);
}
/**
* Returns the types and their expected values to test the retrieval of
* types.
*
* @return string[]
*/
public function provideTypesToExpand()
{
return array(
array('', array('')),
array(' ', array('')),
array('int', array('int')),
array('int ', array('int')),
array('string', array('string')),
array('DocBlock', array('\My\Namespace\DocBlock')),
// array(' DocBlock ', array('\My\Namespace\DocBlock')), FIXME
array('Alias\DocBlock', array('\My\Namespace\Aliasing\DocBlock')),
array(
'DocBlock|Tag',
array('\My\Namespace\DocBlock', '\My\Namespace\Tag')
),
array(
'DocBlock|null',
array('\My\Namespace\DocBlock', 'null')
),
);
}
}
@@ -0,0 +1,81 @@
<?php
/**
* phpDocumentor Return tag test.
*
* @author Mike van Riel <[email protected]>
* @copyright Copyright (c) 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com)
*/
namespace phpDocumentor\Reflection\DocBlock\Tag;
require_once __DIR__
. '/../../../../../src/phpDocumentor/Reflection/DocBlock/Tag/ReturnTag.php';
/**
* Test class for phpDocumentor_Reflection_DocBlock_ReturnTag.
*
* @author Mike van Riel <[email protected]>
* @copyright Copyright (c) 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com)
*/
class ReturnTagTest extends ParamTagTest
{
/**
* Test that the \phpDocumentor\Reflection\DocBlock\Tag\ReturnTag can
* understand the Return DocBlock.
*
* @param string $content
* @param string $extracted_type
* @param string $extracted_description
*
* @covers \phpDocumentor\Reflection\DocBlock\Tag\ReturnTag::__construct
*
* @dataProvider provideDataForConstructor
*
* @return void
*/
public function testConstructorParsesInputsIntoCorrectFields(
$content, $extracted_type, $extracted_description
) {
$tag = new ReturnTag('return', $content);
$this->assertEquals($extracted_type, $tag->getTypes());
$this->assertEquals($extracted_description, $tag->getDescription());
}
/**
* Tests whether the getTypes method correctly converts the given tags.
*
* @param string $type Type string to test
* @param string[] $expected Array of expected types
*
* @covers \phpDocumentor\Reflection\DocBlock\Tag\ReturnTag::getTypes()
*
* @dataProvider provideTypesToExpand
*
* @return void
*/
public function testExpandTypeIntoCorrectFcqn($type, $expected)
{
$docblock = new \phpDocumentor\Reflection\DocBlock(
'', '\My\Namespace', array('Alias' => '\My\Namespace\Aliasing')
);
$tag = new ReturnTag('return', $type);
$tag->setDocBlock($docblock);
$this->assertEquals($expected, $tag->getTypes());
}
/**
* Data provider for testConstructorParsesInputsIntoCorrectFields()
*
* @return array
*/
public function provideDataForConstructor()
{
return array(
array('', array(''), ''),
array('int', array('int'), ''),
array('int Number of Bobs', array('int'), 'Number of Bobs'),
);
}
}
@@ -11,7 +11,8 @@
namespace phpDocumentor\Reflection; namespace phpDocumentor\Reflection;
require_once __DIR__.'/../../../src/phpDocumentor/Reflection/DocBlock.php'; require_once __DIR__.'/../../../src/phpDocumentor/Reflection/DocBlock.php';
require_once __DIR__.'/../../../src/phpDocumentor/Reflection/DocBlock/LongDescription.php'; require_once __DIR__
.'/../../../src/phpDocumentor/Reflection/DocBlock/LongDescription.php';
/** /**
* Test class for phpDocumentor_Reflection_DocBlock * Test class for phpDocumentor_Reflection_DocBlock
@@ -38,7 +39,8 @@ DOCBLOCK;
'This is a short description.', $object->getShortDescription() 'This is a short description.', $object->getShortDescription()
); );
$this->assertEquals( $this->assertEquals(
'This is a long description.', $object->getLongDescription()->getContents() 'This is a long description.',
$object->getLongDescription()->getContents()
); );
$this->assertEquals(2, count($object->getTags())); $this->assertEquals(2, count($object->getTags()));
$this->assertTrue($object->hasTag('see')); $this->assertTrue($object->hasTag('see'));
@@ -58,8 +60,89 @@ DOCBLOCK;
'This is a short description.', $object->getShortDescription() 'This is a short description.', $object->getShortDescription()
); );
$this->assertEquals( $this->assertEquals(
"This is a long description.\nThis is a continuation of the long description.", $object->getLongDescription()->getContents() "This is a long description.\nThis is a continuation of the long "
); ."description.", $object->getLongDescription()->getContents()
);
}
/**
* Tests whether a type is expanded with the given namespace and that a
* keyword is not expanded.
*
* @covers \phpDocumentor\Reflection\DocBlock::expandType()
*
* @return void
*/
public function testExpandTypeUsingNamespace()
{
$docblock = new DocBlock('', '\My\Namespace');
$this->assertEquals('\My\Namespace\Mine', $docblock->expandType('Mine'));
}
/**
* Tests whether a type is expanded when no namespace is given.
*
* @covers \phpDocumentor\Reflection\DocBlock::expandType()
*
* @return void
*/
public function testExpandTypeWithoutNamespace()
{
$docblock = new DocBlock('');
$this->assertEquals('\Mine', $docblock->expandType('Mine'));
}
/**
* Tests whether a type is expanded with the given namespace when an alias
* is provided.
*
* @covers \phpDocumentor\Reflection\DocBlock::expandType()
*
* @return void
*/
public function testExpandTypeUsingNamespaceAlias()
{
$docblock = new DocBlock(
'', '\My\Namespace', array('Alias' => '\My\Namespace\Alias')
);
// first try a normal resolution without alias
$this->assertEquals(
'\My\Namespace\Al', $docblock->expandType('Al')
);
// try to use the alias
$this->assertEquals(
'\My\Namespace\Alias\Al', $docblock->expandType('Alias\Al')
);
}
/**
* Tests whether the keywords that should not be converted are not converted.
*
* @param string $keyword The keyword that is to be tested; this is provided
* by the dataprovider.
*
* @covers \phpDocumentor\Reflection\DocBlock::expandType()
*
* @dataProvider getNonExpandableKeywordsForExpandType
*
* @return void
*/
public function testThatExpandTypeDoesNotExpandAllKeywords($keyword)
{
$docblock = new DocBlock('', '\My\Namespace');
$this->assertEquals($keyword, $docblock->expandType($keyword));
}
public function getNonExpandableKeywordsForExpandType()
{
return array(
array('string'), array('int'), array('integer'), array('bool'),
array('boolean'), array('float'), array('double'), array('object'),
array('mixed'), array('array'), array('resource'), array('void'),
array('null'), array('callback'), array('false'), array('true')
);
} }
} }