diff --git a/src/DocBlock/Description.php b/src/DocBlock/Description.php index 087aa6a..ff27edb 100644 --- a/src/DocBlock/Description.php +++ b/src/DocBlock/Description.php @@ -14,29 +14,65 @@ namespace phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Description\Formatter; use phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter; +use Webmozart\Assert\Assert; +/** + * Object representing to description for a DocBlock. + * + * A Description object can consist of plain text but can also include tags. A Description Formatter can then combine + * a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete + * description text using the format that you would prefer. + * + * Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is + * thus recommended to use that to create a Description object, like this: + * + * $description = $descriptionFactory->create('This is a {@see Description}', $context); + * + * The description factory will interpret the given body and create a body template and list of tags from them, and pass + * that onto the constructor if this class. + * + * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace + * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial + * > type names and FQSENs. + * + * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this: + * + * $description = new Description( + * 'This is a %1$s', + * [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ] + * ); + * + * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object + * is mainly responsible for rendering. + * + * @see DescriptionFactory to create a new Description. + * @see Description\Formatter for the formatting of the body and tags. + */ class Description { /** @var string */ - private $body; + private $bodyTemplate; /** @var Tag[] */ private $tags; /** - * Initializes a this object with a series of tokens of which a description consists. + * Initializes a Description with its body (template) and a listing of the tags used in the body template. * - * @param string $body + * @param string $bodyTemplate * @param Tag[] $tags */ - public function __construct($body, array $tags = []) + public function __construct($bodyTemplate, array $tags = []) { - $this->body = $body; + Assert::string($bodyTemplate); + + $this->bodyTemplate = $bodyTemplate; $this->tags = $tags; } /** - * Renders this description as a string where the provided formatter will format tags for the expected output. + * Renders this description as a string where the provided formatter will format the tags in the expected string + * format. * * @param Formatter|null $formatter * @@ -48,6 +84,16 @@ class Description $formatter = new PassthroughFormatter(); } - return vsprintf($this->body, $formatter->format($this->tags)); + return vsprintf($this->bodyTemplate, $formatter->format($this->tags)); + } + + /** + * Returns a plain string representation of this description. + * + * @return string + */ + public function __toString() + { + return $this->render(); } } diff --git a/tests/unit/DocBlock/DescriptionTest.php b/tests/unit/DocBlock/DescriptionTest.php index 44794fb..82b0947 100644 --- a/tests/unit/DocBlock/DescriptionTest.php +++ b/tests/unit/DocBlock/DescriptionTest.php @@ -13,8 +13,8 @@ namespace phpDocumentor\Reflection\DocBlock; use Mockery as m; -use phpDocumentor\Reflection\DocBlock\Tags\Deprecated; -use phpDocumentor\Reflection\DocBlock\Tags\Link; +use phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter; +use phpDocumentor\Reflection\DocBlock\Tags\Other; /** * @coversDefaultClass \phpDocumentor\Reflection\DocBlock\Description @@ -22,88 +22,54 @@ use phpDocumentor\Reflection\DocBlock\Tags\Link; class DescriptionTest extends \PHPUnit_Framework_TestCase { /** - * @param array $examples - * @dataProvider provideExampleDescriptions * @covers ::__construct * @covers ::render - * @covers ::parse - * @uses phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter - * @uses phpDocumentor\Reflection\DocBlock\Tag - * @uses phpDocumentor\Reflection\DocBlock\Tags\Link + * @uses \phpDocumentor\Reflection\DocBlock\Tags\Other + * @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag + * @uses \phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter */ - public function testParsesDescription($example) + public function testDescriptionCanRenderUsingABodyWithPlaceholdersAndTags() { - $object = new Description($example); + $body = 'This is a %1$s body.'; + $expected = 'This is a {@internal significant } body.'; + $tags = [new Other('internal', new Description('significant '))]; - $this->assertSame($example, $object->render()); + $fixture = new Description($body, $tags); + + // without formatter (thus the PassthroughFormatter by default) + $this->assertSame($expected, $fixture->render()); + + // with a custom formatter + $formatter = m::mock(PassthroughFormatter::class); + $formatter->shouldReceive('format')->with($tags)->andReturn(['{@internal significant }']); + $this->assertSame($expected, $fixture->render($formatter)); } /** * @covers ::__construct * @covers ::render - * @covers ::parse - * @uses phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter + * @covers ::__toString + * @uses \phpDocumentor\Reflection\DocBlock\Tags\Other + * @uses \phpDocumentor\Reflection\DocBlock\Tags\BaseTag + * @uses \phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter */ - public function testInlineTagEscapingSequence() + public function testDescriptionCanBeCastToString() { - $fixture = 'This is text for a description with literal {{@}link}.'; - $expected = 'This is text for a description with literal {@link}.'; - $object = new Description($fixture); - $this->assertSame($expected, $object->render()); + $body = 'This is a %1$s body.'; + $expected = 'This is a {@internal significant } body.'; + $tags = [new Other('internal', new Description('significant '))]; + + $fixture = new Description($body, $tags); + + $this->assertSame($expected, (string)$fixture); } /** * @covers ::__construct - * @covers ::render - * @covers ::parse - * @uses phpDocumentor\Reflection\DocBlock\Tag - * @uses phpDocumentor\Reflection\DocBlock\Tags\Version - * @uses phpDocumentor\Reflection\DocBlock\Tags\Link + * @expectedException \InvalidArgumentException */ - public function testFormatterReceivesContentsAsTokens() + public function testBodyTemplateMustBeAString() { - $fixture = <<shouldReceive('format')->with($expected)->andReturn($fixture); - - $object = new Description($fixture); - $this->assertSame($fixture, $object->render($formatter)); - } - - /** - * Provides a series of example strings that the parser should correctly interpret and return. - * - * @return string[][] - */ - public function provideExampleDescriptions() - { - return [ - ['This is text for a description.'], - ['This is text for a {@link http://phpdoc.org/ description} that uses an inline tag.'], - ['{@link http://phpdoc.org/ This} is text for a description that starts with an inline tag.'], - [ - 'This is text for a description with {@internal inline tag with {@link http://phpdoc.org another ' - . 'inline tag} in it}.' - ], - ['This is text for a description containing { that is literal.'], - ['This is text for a description containing {@internal inline tag that has { that is literal}.'], - ['This is text for a description with {} that is not a tag.'], - ['This is text for a description with {@internal inline tag with {} that is not an inline tag}.'], - ['This is text for a description with an {@internal inline tag with literal {{@}link{} in it}.'] - ]; + new Description([]); } }