From 50b104f2c08a8e9787e1b4428eaad666868d4abd Mon Sep 17 00:00:00 2001 From: Mike van Riel Date: Fri, 26 Jun 2015 08:59:00 +0200 Subject: [PATCH] Add tests for DocBlockFactory and extract interface --- composer.json | 2 +- composer.lock | 78 +++++---- src/DocBlock/StandardTagFactory.php | 190 ++++++++++++++++++++++ src/DocBlock/TagFactory.php | 155 +----------------- src/DocBlock/Tags/BaseTag.php | 3 - src/DocBlock/Tags/Generic.php | 4 +- src/DocBlockFactory.php | 45 +++--- tests/unit/DocBlockFactoryTest.php | 237 ++++++++++++++++++++++++++++ 8 files changed, 496 insertions(+), 218 deletions(-) create mode 100644 src/DocBlock/StandardTagFactory.php create mode 100644 tests/unit/DocBlockFactoryTest.php diff --git a/composer.json b/composer.json index 3402ac1..d4f4b49 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "4.4" + "phpunit/phpunit": "^4.4" }, "extra": { "branch-alias": { diff --git a/composer.lock b/composer.lock index 1a93eb1..d74c17b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "ba3538f875cdcc53e70e55ff36b78c31", + "hash": "da8060231f14649de9e35951050aa3be", "packages": [ { "name": "phpdocumentor/reflection-common", @@ -154,16 +154,16 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.0.4", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119" + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119", - "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", "shasum": "" }, "require": { @@ -174,7 +174,7 @@ "ext-pdo": "*", "ext-phar": "*", "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "2.0.*@ALPHA" + "squizlabs/php_codesniffer": "~2.0" }, "type": "library", "extra": { @@ -183,8 +183,8 @@ } }, "autoload": { - "psr-0": { - "Doctrine\\Instantiator\\": "src" + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -204,7 +204,7 @@ "constructor", "instantiate" ], - "time": "2014-10-13 12:58:55" + "time": "2015-06-14 21:17:01" }, { "name": "hamcrest/hamcrest-php", @@ -318,16 +318,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "2.1.5", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "be2286cb8c7e1773eded49d9719219e6f74f9e3e" + "reference": "631e365cf26bb2c078683e8d9bcf8bc631ac4d44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/be2286cb8c7e1773eded49d9719219e6f74f9e3e", - "reference": "be2286cb8c7e1773eded49d9719219e6f74f9e3e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/631e365cf26bb2c078683e8d9bcf8bc631ac4d44", + "reference": "631e365cf26bb2c078683e8d9bcf8bc631ac4d44", "shasum": "" }, "require": { @@ -376,7 +376,7 @@ "testing", "xunit" ], - "time": "2015-06-09 13:05:42" + "time": "2015-06-19 07:11:55" }, { "name": "phpunit/php-file-iterator", @@ -425,16 +425,16 @@ }, { "name": "phpunit/php-text-template", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", "shasum": "" }, "require": { @@ -443,20 +443,17 @@ "type": "library", "autoload": { "classmap": [ - "Text/" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -465,7 +462,7 @@ "keywords": [ "template" ], - "time": "2014-01-30 17:20:04" + "time": "2015-06-21 13:50:34" }, { "name": "phpunit/php-timer", @@ -510,16 +507,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "db63be1159c81df649cd0260e30249a586d4129e" + "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/db63be1159c81df649cd0260e30249a586d4129e", - "reference": "db63be1159c81df649cd0260e30249a586d4129e", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/7a9b0969488c3c54fd62b4d504b3ec758fd005d9", + "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9", "shasum": "" }, "require": { @@ -555,20 +552,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-06-12 07:34:24" + "time": "2015-06-19 03:43:16" }, { "name": "phpunit/phpunit", - "version": "4.4.0", + "version": "4.4.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0" + "reference": "2e8580deebb7d1ac92ac878595e6bffe01069c2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0", - "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2e8580deebb7d1ac92ac878595e6bffe01069c2a", + "reference": "2e8580deebb7d1ac92ac878595e6bffe01069c2a", "shasum": "" }, "require": { @@ -586,8 +583,9 @@ "sebastian/comparator": "~1.0", "sebastian/diff": "~1.1", "sebastian/environment": "~1.1", - "sebastian/exporter": "~1.0", + "sebastian/exporter": "~1.1", "sebastian/global-state": "~1.0", + "sebastian/recursion-context": "~1.0", "sebastian/version": "~1.0", "symfony/yaml": "~2.0" }, @@ -626,7 +624,7 @@ "testing", "xunit" ], - "time": "2014-12-05 06:49:03" + "time": "2015-01-27 16:06:15" }, { "name": "phpunit/phpunit-mock-objects", @@ -1021,16 +1019,16 @@ }, { "name": "sebastian/version", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4" + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ab931d46cd0d3204a91e1b9a40c4bc13032b58e4", - "reference": "ab931d46cd0d3204a91e1b9a40c4bc13032b58e4", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", "shasum": "" }, "type": "library", @@ -1052,7 +1050,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-02-24 06:35:25" + "time": "2015-06-21 13:59:46" }, { "name": "symfony/yaml", diff --git a/src/DocBlock/StandardTagFactory.php b/src/DocBlock/StandardTagFactory.php new file mode 100644 index 0000000..cbbfec2 --- /dev/null +++ b/src/DocBlock/StandardTagFactory.php @@ -0,0 +1,190 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\DocBlock\Tags\Generic; +use phpDocumentor\Reflection\FqsenResolver; +use phpDocumentor\Reflection\Types\Context; + +final class StandardTagFactory implements TagFactory +{ + /** PCRE regular expression matching a tag name. */ + const REGEX_TAGNAME = '[\w\-\_\\\\]+'; + + /** + * @var array An array with a tag as a key, and an FQCN to a class that handles it as an array value. + */ + private $tagHandlerMappings = array( + 'author' => '\phpDocumentor\Reflection\DocBlock\Tags\Author', + 'covers' => '\phpDocumentor\Reflection\DocBlock\Tags\Covers', + 'deprecated' => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated', + 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', + 'link' => '\phpDocumentor\Reflection\DocBlock\Tags\Link', + 'method' => '\phpDocumentor\Reflection\DocBlock\Tags\Method', + 'param' => '\phpDocumentor\Reflection\DocBlock\Tags\Param', + 'property-read' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead', + 'property' => '\phpDocumentor\Reflection\DocBlock\Tags\Property', + 'property-write' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite', + 'return' => '\phpDocumentor\Reflection\DocBlock\Tags\Return', + 'see' => '\phpDocumentor\Reflection\DocBlock\Tags\See', + 'since' => '\phpDocumentor\Reflection\DocBlock\Tags\Since', + 'source' => '\phpDocumentor\Reflection\DocBlock\Tags\Source', + 'throw' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', + 'throws' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', + 'uses' => '\phpDocumentor\Reflection\DocBlock\Tags\Uses', + 'var' => '\phpDocumentor\Reflection\DocBlock\Tags\Var_', + 'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version' + ); + + /** @var FqsenResolver */ + private $fqsenResolver; + + /** @var mixed[] */ + private $serviceLocator = []; + + public function __construct(FqsenResolver $fqsenResolver) + { + $this->fqsenResolver = $fqsenResolver; + $this->addService($fqsenResolver); + } + + public function addParameter($name, $value) + { + $this->serviceLocator[$name] = $value; + } + + public function addService($service) + { + $this->serviceLocator[get_class($service)] = $service; + } + + /** + * Factory method responsible for instantiating the correct sub type. + * + * @param string $tagLine The text for this tag, including description. + * @param Context $context + * + * @throws \InvalidArgumentException if an invalid tag line was presented. + * + * @return static A new tag object. + */ + public function create($tagLine, Context $context = null) + { + if (! $context) { + $context = new Context(''); + } + list($tagName, $tagBody) = $this->extractTagParts($tagLine); + + $handler = Generic::class; + if (isset($this->tagHandlerMappings[$tagName])) { + $handler = $this->tagHandlerMappings[$tagName]; + } elseif ($this->isAnnotation($tagName)) { + $tagName = (string)$this->fqsenResolver->resolve($tagName, $context); + if (isset($this->tagHandlerMappings[$tagName])) { + $handler = $this->tagHandlerMappings[$tagName]; + } + } + + $parameters = (new \ReflectionMethod($handler, 'create'))->getParameters(); + + $wiring = array_merge( + $this->serviceLocator, + [ + 'name' => $tagName, + 'body' => $tagBody, + Context::class => $context + ] + ); + + $arguments = []; + foreach ($parameters as $index => $parameter) { + $typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null; + if (isset($wiring[$typeHint])) { + $arguments[] = $wiring[$typeHint]; + continue; + } + + $parameterName = $parameter->getName(); + if (isset($wiring[$parameterName])) { + $arguments[] = $wiring[$parameterName]; + continue; + } + + $arguments[] = null; + } + + return call_user_func_array([$handler, 'create'], $arguments); + } + + /** + * Registers a handler for tags. + * + * Registers a handler for tags. The class specified is autoloaded if it's not available. It must inherit from + * this class. + * + * @param string $tag Name of tag to register a handler for. When registering a namespaced tag, the full + * name, along with a prefixing slash MUST be provided. + * @param string|null $handler FQCN of handler. + * + * @return bool TRUE on success, FALSE on failure. + */ + public function registerTagHandler($tag, $handler) + { + $tag = trim((string)$tag); + + if ('' !== $tag + && class_exists($handler) + && is_subclass_of($handler, Tag::class) + && ! strpos($tag, '\\') //Accept no slash, and 1st slash at offset 0. + ) { + $this->tagHandlerMappings[$tag] = $handler; + + return true; + } + + return false; + } + + /** + * Extracts all components for a tag. + * + * @param string $tagLine + * + * @return string[] + */ + private function extractTagParts($tagLine) + { + $matches = array(); + if (! preg_match('/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)?/us', $tagLine, $matches)) { + throw new \InvalidArgumentException( + 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' + ); + } + + if (count($matches) < 3) { + $matches[] = ''; + } + + return array_slice($matches, 1); + } + + private function isAnnotation($tag) + { + // 1. Contains a namespace separator + // 2. Contains parenthesis + // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part + // of the annotation class name matches the found tag name + + return false; + } +} diff --git a/src/DocBlock/TagFactory.php b/src/DocBlock/TagFactory.php index 01b6829..807bf92 100644 --- a/src/DocBlock/TagFactory.php +++ b/src/DocBlock/TagFactory.php @@ -12,61 +12,13 @@ namespace phpDocumentor\Reflection\DocBlock; -use phpDocumentor\Reflection\DocBlock\Tags\Generic; -use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\Types\Context; -final class TagFactory +interface TagFactory { - /** PCRE regular expression matching a tag name. */ - const REGEX_TAGNAME = '[\w\-\_\\\\]+'; + public function addParameter($name, $value); - /** - * @var array An array with a tag as a key, and an FQCN to a class that handles it as an array value. - */ - private $tagHandlerMappings = array( - 'author' => '\phpDocumentor\Reflection\DocBlock\Tags\Author', - 'covers' => '\phpDocumentor\Reflection\DocBlock\Tags\Covers', - 'deprecated' => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated', - 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', - 'link' => '\phpDocumentor\Reflection\DocBlock\Tags\Link', - 'method' => '\phpDocumentor\Reflection\DocBlock\Tags\Method', - 'param' => '\phpDocumentor\Reflection\DocBlock\Tags\Param', - 'property-read' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead', - 'property' => '\phpDocumentor\Reflection\DocBlock\Tags\Property', - 'property-write' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite', - 'return' => '\phpDocumentor\Reflection\DocBlock\Tags\Return', - 'see' => '\phpDocumentor\Reflection\DocBlock\Tags\See', - 'since' => '\phpDocumentor\Reflection\DocBlock\Tags\Since', - 'source' => '\phpDocumentor\Reflection\DocBlock\Tags\Source', - 'throw' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', - 'throws' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', - 'uses' => '\phpDocumentor\Reflection\DocBlock\Tags\Uses', - 'var' => '\phpDocumentor\Reflection\DocBlock\Tags\Var_', - 'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version' - ); - - /** @var FqsenResolver */ - private $fqsenResolver; - - /** @var mixed[] */ - private $serviceLocator = []; - - public function __construct(FqsenResolver $fqsenResolver) - { - $this->fqsenResolver = $fqsenResolver; - $this->addService($fqsenResolver); - } - - public function addParameter($name, $value) - { - $this->serviceLocator[$name] = $value; - } - - public function addService($service) - { - $this->serviceLocator[get_class($service)] = $service; - } + public function addService($service); /** * Factory method responsible for instantiating the correct sub type. @@ -78,53 +30,7 @@ final class TagFactory * * @return static A new tag object. */ - public function create($tagLine, Context $context = null) - { - if (!$context) { - $context = new Context(''); - } - list($tagName, $tagBody) = $this->extractTagParts($tagLine); - - $handler = Generic::class; - if (isset($this->tagHandlerMappings[$tagName])) { - $handler = $this->tagHandlerMappings[$tagName]; - } elseif ($this->isAnnotation($tagName)) { - $tagName = (string)$this->fqsenResolver->resolve($tagName, $context); - if (isset($this->tagHandlerMappings[$tagName])) { - $handler = $this->tagHandlerMappings[$tagName]; - } - } - - $parameters = (new \ReflectionMethod($handler, 'create'))->getParameters(); - - $wiring = array_merge( - $this->serviceLocator, - [ - 'name' => $tagName, - 'body' => $tagBody, - Context::class => $context - ] - ); - - $arguments = []; - foreach ($parameters as $index => $parameter) { - $typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null; - if (isset($wiring[$typeHint])) { - $arguments[] = $wiring[$typeHint]; - continue; - } - - $parameterName = $parameter->getName(); - if (isset($wiring[$parameterName])) { - $arguments[] = $wiring[$parameterName]; - continue; - } - - $arguments[] = null; - } - - return call_user_func_array([$handler, 'create'], $arguments); - } + public function create($tagLine, Context $context = null); /** * Registers a handler for tags. @@ -132,58 +38,11 @@ final class TagFactory * Registers a handler for tags. The class specified is autoloaded if it's not available. It must inherit from * this class. * - * @param string $tag Name of tag to register a handler for. When registering a namespaced tag, the full - * name, along with a prefixing slash MUST be provided. + * @param string $tag Name of tag to register a handler for. When registering a namespaced tag, the full + * name, along with a prefixing slash MUST be provided. * @param string|null $handler FQCN of handler. * * @return bool TRUE on success, FALSE on failure. */ - public function registerTagHandler($tag, $handler) - { - $tag = trim((string)$tag); - - if ('' !== $tag - && class_exists($handler) - && is_subclass_of($handler, Tag::class) - && !strpos($tag, '\\') //Accept no slash, and 1st slash at offset 0. - ) { - $this->tagHandlerMappings[$tag] = $handler; - return true; - } - - return false; - } - - /** - * Extracts all components for a tag. - * - * @param string $tagLine - * - * @return string[] - */ - private function extractTagParts($tagLine) - { - $matches = array(); - if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)?/us', $tagLine, $matches)) { - throw new \InvalidArgumentException( - 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' - ); - } - - if (count($matches) < 3) { - $matches[] = ''; - } - - return array_slice($matches, 1); - } - - private function isAnnotation($tag) - { - // 1. Contains a namespace separator - // 2. Contains parenthesis - // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part - // of the annotation class name matches the found tag name - - return false; - } + public function registerTagHandler($tag, $handler); } diff --git a/src/DocBlock/Tags/BaseTag.php b/src/DocBlock/Tags/BaseTag.php index e4a909a..14bb717 100644 --- a/src/DocBlock/Tags/BaseTag.php +++ b/src/DocBlock/Tags/BaseTag.php @@ -14,9 +14,6 @@ namespace phpDocumentor\Reflection\DocBlock\Tags; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Description; -use phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use phpDocumentor\Reflection\DocBlock\TagFactory; -use phpDocumentor\Reflection\Types\Context; /** * Parses a tag definition for a DocBlock. diff --git a/src/DocBlock/Tags/Generic.php b/src/DocBlock/Tags/Generic.php index 7c62346..1fd27ee 100644 --- a/src/DocBlock/Tags/Generic.php +++ b/src/DocBlock/Tags/Generic.php @@ -14,7 +14,7 @@ namespace phpDocumentor\Reflection\DocBlock\Tags; use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\DescriptionFactory; -use phpDocumentor\Reflection\DocBlock\TagFactory; +use phpDocumentor\Reflection\DocBlock\StandardTagFactory; use phpDocumentor\Reflection\Types\Context; use Webmozart\Assert\Assert; @@ -80,7 +80,7 @@ class Generic extends BaseTag */ private function validateTagName($name) { - if (!preg_match('/^' . TagFactory::REGEX_TAGNAME . '$/u', $name)) { + if (! preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { throw new \InvalidArgumentException( 'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' . 'hyphens and backslashes.' diff --git a/src/DocBlockFactory.php b/src/DocBlockFactory.php index d1ca3d1..c402cab 100644 --- a/src/DocBlockFactory.php +++ b/src/DocBlockFactory.php @@ -13,8 +13,10 @@ namespace phpDocumentor\Reflection; use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\DocBlock\StandardTagFactory; use phpDocumentor\Reflection\DocBlock\TagFactory; use phpDocumentor\Reflection\Types\Context; +use Webmozart\Assert\Assert; final class DocBlockFactory implements DocBlockFactoryInterface { @@ -27,13 +29,10 @@ final class DocBlockFactory implements DocBlockFactoryInterface /** * Initializes this factory with the required subcontractors. * - * @param DocBlock\DescriptionFactory $descriptionFactory - * @param DocBlock\TagFactory $tagFactory + * @param DescriptionFactory $descriptionFactory + * @param TagFactory $tagFactory */ - public function __construct( - DocBlock\DescriptionFactory $descriptionFactory, - DocBlock\TagFactory $tagFactory - ) + public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) { $this->descriptionFactory = $descriptionFactory; $this->tagFactory = $tagFactory; @@ -49,7 +48,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface public static function createInstance(array $additionalTags = []) { $fqsenResolver = new FqsenResolver(); - $tagFactory = new TagFactory($fqsenResolver); + $tagFactory = new StandardTagFactory($fqsenResolver); $descriptionFactory = new DescriptionFactory($tagFactory); $tagFactory->addService($descriptionFactory); @@ -63,9 +62,10 @@ final class DocBlockFactory implements DocBlockFactoryInterface } /** - * @param $docblock + * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the + * getDocComment method (such as a ReflectionClass object). * @param Types\Context $context - * @param Location $location + * @param Location $location * * @return DocBlock */ @@ -73,14 +73,15 @@ final class DocBlockFactory implements DocBlockFactoryInterface { if (is_object($docblock)) { if (!method_exists($docblock, 'getDocComment')) { - throw new \InvalidArgumentException( - 'Invalid object passed; the given object must support the getDocComment method' - ); + $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; + throw new \InvalidArgumentException($exceptionMessage); } $docblock = $docblock->getDocComment(); } + Assert::stringNotEmpty($docblock); + if ($context === null) { $context = new Context(''); } @@ -90,7 +91,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface return new DocBlock( $summary, - $this->descriptionFactory->create($description, $context), + $description ? $this->descriptionFactory->create($description, $context) : null, $this->parseTagBlock($tags), $context, $location, @@ -108,23 +109,14 @@ final class DocBlockFactory implements DocBlockFactoryInterface */ private function stripDocComment($comment) { - $comment = trim( - preg_replace( - '#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', - '$1', - $comment - ) - ); + $comment = trim(preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', '$1', $comment)); // reg ex above is not able to remove */ from a single line docblock if (substr($comment, -2) == '*/') { $comment = trim(substr($comment, 0, -2)); } - // normalize strings - $comment = str_replace(array("\r\n", "\r"), "\n", $comment); - - return $comment; + return str_replace(array("\r\n", "\r"), "\n", $comment); } /** @@ -248,6 +240,7 @@ final class DocBlockFactory implements DocBlockFactoryInterface $result[count($result) - 1] .= "\n" . $tag_line; } } + return $result; } @@ -263,7 +256,11 @@ final class DocBlockFactory implements DocBlockFactoryInterface } if ('@' !== $tags[0]) { + // @codeCoverageIgnoreStart + // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that + // we didn't foresee. throw new \LogicException('A tag block started with text instead of an at-sign(@): ' . $tags); + // @codeCoverageIgnoreEnd } return $tags; diff --git a/tests/unit/DocBlockFactoryTest.php b/tests/unit/DocBlockFactoryTest.php new file mode 100644 index 0000000..011ca7a --- /dev/null +++ b/tests/unit/DocBlockFactoryTest.php @@ -0,0 +1,237 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +use Mockery as m; +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\DocBlock\Tag; +use phpDocumentor\Reflection\DocBlock\TagFactory; +use phpDocumentor\Reflection\Types\Context; + +/** + * @coversDefaultClass phpDocumentor\Reflection\DocBlockFactory + * @covers :: + * @uses \Webmozart\Assert\Assert + * @uses phpDocumentor\Reflection\DocBlock + */ +class DocBlockFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers ::__construct + * @covers ::createInstance + * @uses \phpDocumentor\Reflection\DocBlock\StandardTagFactory + * @uses \phpDocumentor\Reflection\DocBlock\DescriptionFactory + */ + public function testCreateFactoryUsingFactoryMethod() + { + $fixture = DocBlockFactory::createInstance(); + + $this->assertInstanceOf(DocBlockFactory::class, $fixture); + } + + /** + * @covers ::__construct + * @covers ::create + * @uses phpDocumentor\Reflection\DocBlock\Description + */ + public function testCreateDocBlockFromReflection() + { + $fixture = new DocBlockFactory(m::mock(DescriptionFactory::class), m::mock(TagFactory::class)); + + $docBlock = '/** This is a DocBlock */'; + $classReflector = m::mock(\ReflectionClass::class); + $classReflector->shouldReceive('getDocComment')->andReturn($docBlock); + $docblock = $fixture->create($classReflector); + + $this->assertInstanceOf(DocBlock::class, $docblock); + $this->assertSame('This is a DocBlock', $docblock->getSummary()); + $this->assertEquals(new Description(''), $docblock->getDescription()); + $this->assertSame([], $docblock->getTags()); + $this->assertEquals(new Context(''), $docblock->getContext()); + $this->assertNull($docblock->getLocation()); + } + + /** + * @covers ::__construct + * @covers ::create + * @uses phpDocumentor\Reflection\DocBlock\Description + */ + public function testCreateDocBlockFromStringWithDocComment() + { + $fixture = new DocBlockFactory(m::mock(DescriptionFactory::class), m::mock(TagFactory::class)); + + $docblock = $fixture->create('/** This is a DocBlock */'); + + $this->assertInstanceOf(DocBlock::class, $docblock); + $this->assertSame('This is a DocBlock', $docblock->getSummary()); + $this->assertEquals(new Description(''), $docblock->getDescription()); + $this->assertSame([], $docblock->getTags()); + $this->assertEquals(new Context(''), $docblock->getContext()); + $this->assertNull($docblock->getLocation()); + } + + /** + * @covers ::create + * @covers ::__construct + * @uses phpDocumentor\Reflection\DocBlock\Description + */ + public function testCreateDocBlockFromStringWithoutDocComment() + { + $fixture = new DocBlockFactory(m::mock(DescriptionFactory::class), m::mock(TagFactory::class)); + + $docblock = $fixture->create('This is a DocBlock'); + + $this->assertInstanceOf(DocBlock::class, $docblock); + $this->assertSame('This is a DocBlock', $docblock->getSummary()); + $this->assertEquals(new Description(''), $docblock->getDescription()); + $this->assertSame([], $docblock->getTags()); + $this->assertEquals(new Context(''), $docblock->getContext()); + $this->assertNull($docblock->getLocation()); + } + + /** + * @covers ::__construct + * @covers ::create + * @uses phpDocumentor\Reflection\DocBlock\DescriptionFactory + * @uses phpDocumentor\Reflection\DocBlock\Description + * @dataProvider provideSummaryAndDescriptions + */ + public function testSummaryAndDescriptionAreSeparated($given, $summary, $description) + { + $tagFactory = m::mock(TagFactory::class); + $fixture = new DocBlockFactory(new DescriptionFactory($tagFactory), $tagFactory); + + $docblock = $fixture->create($given); + + $this->assertSame($summary, $docblock->getSummary()); + $this->assertEquals(new Description($description), $docblock->getDescription()); + } + + /** + * @covers ::__construct + * @covers ::create + * @uses phpDocumentor\Reflection\DocBlock\DescriptionFactory + * @uses phpDocumentor\Reflection\DocBlock\Description + */ + public function testDescriptionsRetainFormatting() + { + $tagFactory = m::mock(TagFactory::class); + $fixture = new DocBlockFactory(new DescriptionFactory($tagFactory), $tagFactory); + + $given = <<create($given); + + $this->assertEquals(new Description($description), $docblock->getDescription()); + } + + /** + * @covers ::__construct + * @covers ::create + * @uses phpDocumentor\Reflection\DocBlock\DescriptionFactory + * @uses phpDocumentor\Reflection\DocBlock\Description + */ + public function testTagsAreInterpretedUsingFactory() + { + $tagString = << This is with + multiline description. +TAG; + + $tag = m::mock(Tag::class); + $tagFactory = m::mock(TagFactory::class); + $tagFactory->shouldReceive('create')->with($tagString)->andReturn($tag); + + $fixture = new DocBlockFactory(new DescriptionFactory($tagFactory), $tagFactory); + + $given = << This is with + * multiline description. + */ +DOCBLOCK; + + $docblock = $fixture->create($given, new Context('')); + + $this->assertEquals([$tag], $docblock->getTags()); + } + + public function provideSummaryAndDescriptions() + { + return [ + ['This is a DocBlock', 'This is a DocBlock', ''], + [ + 'This is a DocBlock. This should still be summary.', + 'This is a DocBlock. This should still be summary.', + '' + ], + [ + <<