From cd252c1f0965a87db753cd5ab5e814b2c9eb05ce Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Thu, 21 Jun 2012 22:17:30 +0200 Subject: [PATCH] Added support for namespace expansion of a DocBlock and via the DocBlock to its tags --- src/phpDocumentor/Reflection/DocBlock.php | 118 +++++++++++++++++- .../phpDocumentor/Reflection/DocBlockTest.php | 91 +++++++++++++- 2 files changed, 202 insertions(+), 7 deletions(-) diff --git a/src/phpDocumentor/Reflection/DocBlock.php b/src/phpDocumentor/Reflection/DocBlock.php index 9d5cfb6..782bfab 100644 --- a/src/phpDocumentor/Reflection/DocBlock.php +++ b/src/phpDocumentor/Reflection/DocBlock.php @@ -36,14 +36,36 @@ class DocBlock implements \Reflector */ 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. * - * @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. + * @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 (!method_exists($docblock, 'getDocComment')) { throw new \InvalidArgumentException( @@ -61,6 +83,9 @@ class DocBlock implements \Reflector $this->short_description = $short; $this->long_description = new DocBlock\LongDescription($long); $this->parseTags($tags); + + $this->namespace = $namespace; + $this->namespace_aliases = $namespace_aliases; } /** @@ -271,6 +296,93 @@ class DocBlock implements \Reflector 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') { + $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 part is the keyword 'namespace', replace it + // with the current namespace + if ($type_parts[0] == 'namespace') { + $type_parts[0] = $this->getNamespace(); + $item = implode('\\', $type_parts); + } + + // 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); + } elseif (count($type_parts) == 1) { + // prefix the item with the namespace if there is only one + // part and no alias + $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. * diff --git a/tests/phpDocumentor/Reflection/DocBlockTest.php b/tests/phpDocumentor/Reflection/DocBlockTest.php index b2a07ee..32d248f 100644 --- a/tests/phpDocumentor/Reflection/DocBlockTest.php +++ b/tests/phpDocumentor/Reflection/DocBlockTest.php @@ -11,7 +11,8 @@ namespace phpDocumentor\Reflection; 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 @@ -38,7 +39,8 @@ DOCBLOCK; 'This is a short description.', $object->getShortDescription() ); $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->assertTrue($object->hasTag('see')); @@ -58,8 +60,89 @@ DOCBLOCK; 'This is a short description.', $object->getShortDescription() ); $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') + ); } }