Work towards stabilizing the API.

- Changed Tag to an interface and flattened hierarchy
- Changed Description to accept a list of tags and a template
- Allow auto-wiring when constructing tags
This commit is contained in:
Mike van Riel
2015-06-13 14:30:43 +02:00
committed by Mike van Riel
parent 2054338d51
commit 2c18f0e883
17 changed files with 1463 additions and 230 deletions
+11 -8
View File
@@ -15,20 +15,24 @@ namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description\Formatter;
use phpDocumentor\Reflection\DocBlock\Description\PassthroughFormatter;
class Description
{
/** @var Tag[]|string[] The contents, as an array of strings and Tag objects */
private $tokens;
/** @var string */
private $body;
/** @var Tag[] */
private $tags;
/**
* Initializes a this object with a series of tokens of which a description consists.
*
* @param Tag[]|string[] $tokens
* @param string $body
* @param Tag[] $tags
*/
public function __construct(array $tokens)
public function __construct($body, array $tags = [])
{
$this->tokens = $tokens;
$this->body = $body;
$this->tags = $tags;
}
/**
@@ -44,7 +48,6 @@ class Description
$formatter = new PassthroughFormatter();
}
return $formatter->format($this->tokens);
return vsprintf($this->body, $formatter->format($this->tags));
}
}
+11 -4
View File
@@ -12,6 +12,8 @@
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Types\Context;
final class DescriptionFactory
{
/** @var TagFactory */
@@ -33,11 +35,13 @@ final class DescriptionFactory
* @param string $contents
* @param Context $context
*
* @return array An array of strings and tag objects, in the order they occur within the description.
* @return Description
*/
public function create($contents, Context $context = null)
{
return new Description($this->parse($this->lex($contents), $context));
list($text, $tags) = $this->parse($this->lex($contents), $context);
return new Description($text, $tags);
}
/**
@@ -93,8 +97,11 @@ final class DescriptionFactory
private function parse($tokens, Context $context)
{
$count = count($tokens);
$tagCount = 0;
$tags = [];
for ($i = 1; $i < $count; $i += 2) {
$tokens[$i] = $this->tagFactory->create($tokens[$i], $context);
$tokens[$i] = ++$tagCount;
$tags[] = $this->tagFactory->create($tokens[$i], $context);
}
//In order to allow "literal" inline tags, the otherwise invalid
@@ -104,7 +111,7 @@ final class DescriptionFactory
$tokens[$i] = str_replace(['{@}', '{}'], ['@', '}'], $tokens[$i]);
}
return $tokens;
return [implode('', $tokens), $tags];
}
}
+5 -74
View File
@@ -12,82 +12,13 @@
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description\Formatter;
/**
* Parses a tag definition for a DocBlock.
*/
class Tag
interface Tag
{
/** @var string Name of the tag */
protected $name = '';
public static function create($body);
/** @var Description|null Description of the tag. */
protected $description;
public function render(Formatter $formatter = null);
/**
* Parses a tag and populates the member variables.
*
* We explicitly do not type-hint the $description so that classes inheriting this class can override the
* constructor without running into PHP notices.
*
* @param string $name Name of the tag.
* @param Description $description The contents of the given tag.
*/
public function __construct($name, $description)
{
$this->validateTagName($name);
if (!$description instanceof Description) {
throw new \InvalidArgumentException('The description should be an object of type Description');
}
$this->name = $name;
$this->description = $description;
}
/**
* Gets the name of this tag.
*
* @return string The name of this tag.
*/
public function getName()
{
return $this->name;
}
public function render(DocBlock\Description\Formatter $formatter = null)
{
if (!$formatter) {
$formatter = new DocBlock\Description\PassthroughFormatter();
}
return $formatter->format([$this]);
}
/**
* Returns the tag as a serialized string
*
* @return string
*/
public function __toString()
{
return "@{$this->getName()} {$this->description->render()}";
}
/**
* Validates if the tag name matches the expected format, otherwise throws an exception.
*
* @param string $name
*
* @return void
*/
private function validateTagName($name)
{
if (!preg_match('/^' . TagFactory::REGEX_TAGNAME . '$/u', $name)) {
throw new \InvalidArgumentException(
'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, '
. 'hyphens and backslashes.'
);
}
}
public function __toString();
}
+53 -10
View File
@@ -12,7 +12,8 @@
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\FqsenFactory;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context;
final class TagFactory
{
@@ -44,12 +45,26 @@ final class TagFactory
'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version'
);
/** @var FqsenFactory */
private $fqsenFactory;
/** @var FqsenResolver */
private $fqsenResolver;
public function __construct(FqsenFactory $fqsenFactory)
/** @var mixed[] */
private $serviceLocator = [];
public function __construct(FqsenResolver $fqsenResolver)
{
$this->fqsenFactory = $fqsenFactory;
$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;
}
/**
@@ -67,19 +82,47 @@ final class TagFactory
if (!$context) {
$context = new Context('');
}
list($tagName, $tagDescription) = $this->extractTagParts($tagLine);
list($tagName, $tagBody) = $this->extractTagParts($tagLine);
$handler = Tag::class;
if (isset($this->tagHandlerMappings[$tagName])) {
$handler = $this->tagHandlerMappings[$tagName];
} elseif ($this->isAnnotation($tagName)) {
$tagName = (string)$this->fqsenFactory->create($tagName, $context);
$tagName = (string)$this->fqsenResolver->resolve($tagName, $context);
if (isset($this->tagHandlerMappings[$tagName])) {
$handler = $this->tagHandlerMappings[$tagName];
}
}
return $handler::create($tagName, $tagDescription);
$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);
}
/**
@@ -126,11 +169,11 @@ final class TagFactory
);
}
if (count($matches) == 1) {
if (count($matches) < 3) {
$matches[] = '';
}
return $matches;
return array_slice($matches, 1);
}
private function isAnnotation($tag)
+4 -5
View File
@@ -13,12 +13,11 @@
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tag;
/**
* Reflection class for an {@}author tag in a Docblock.
*/
final class Author extends Tag
final class Author extends BaseTag
{
/** @var string register that this is the author tag. */
protected $name = 'author';
@@ -44,7 +43,7 @@ final class Author extends Tag
throw new \InvalidArgumentException('The author tag does not have a valid e-mail address');
}
$this->authorName = $authorName;
$this->authorName = $authorName;
$this->authorEmail = $authorEmail;
}
@@ -81,9 +80,9 @@ final class Author extends Tag
/**
* {@inheritdoc}
*/
public static function create($content)
public static function create($body)
{
$splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $content, $matches);
$splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches);
if (!$splitTagContent) {
return null;
}
+55
View File
@@ -0,0 +1,55 @@
<?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\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.
*/
abstract class BaseTag implements DocBlock\Tag
{
/** @var string Name of the tag */
protected $name = '';
/** @var Description|null Description of the tag. */
protected $description;
/**
* Gets the name of this tag.
*
* @return string The name of this tag.
*/
public function getName()
{
return $this->name;
}
public function getDescription()
{
return $this->description;
}
public function render(DocBlock\Description\Formatter $formatter = null)
{
if (!$formatter) {
$formatter = new DocBlock\Description\PassthroughFormatter();
}
return $formatter->format([$this]);
}
}
+12 -11
View File
@@ -12,20 +12,17 @@
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Context;
use DocBlock\Types\Resolver;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\TypeResolver;
/**
* Reflection class for a @covers tag in a Docblock.
*
* @author Mike van Riel <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
class Covers extends Tag
class Covers extends BaseTag
{
/** @var Fqsen */
protected $refers = null;
@@ -45,14 +42,18 @@ class Covers extends Tag
/**
* {@inheritdoc}
*/
public static function create($content, Context $context)
public static function create(
$body,
DescriptionFactory $descriptionFactory = null,
FqsenResolver $resolver = null,
Context $context = null
)
{
$parts = preg_split('/\s+/Su', $content, 2);
$resolver = new Resolver();
$parts = preg_split('/\s+/Su', $body, 2);
return new static(
$resolver->resolve($parts[0], $context),
new Description(isset($parts[1]) ? $parts[1] : '', $context)
$descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context)
);
}
+5 -4
View File
@@ -14,12 +14,13 @@ namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Context;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
/**
* Reflection class for a {@}deprecated tag in a Docblock.
*/
final class Deprecated extends Tag
final class Deprecated extends BaseTag
{
/**
* PCRE regular expression matching a version vector.
@@ -49,16 +50,16 @@ final class Deprecated extends Tag
/**
* {@inheritdoc}
*/
public static function create($content, Context $context = null)
public static function create($body, DescriptionFactory $descriptionFactory = null, Context $context = null)
{
$matches = [];
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $content, $matches)) {
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return null;
}
return new static(
$matches[1],
new Description(isset($matches[2]) ? $matches[2] : '', $context)
$descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
);
}
+78
View File
@@ -0,0 +1,78 @@
<?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\DocBlock\Tags;
use Doctrine\Instantiator\Exception\InvalidArgumentException;
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.
*/
class Other extends BaseTag
{
/**
* Parses a tag and populates the member variables.
*
* @param string $name Name of the tag.
* @param Description $description The contents of the given tag.
*/
public function __construct($name, Description $description = null)
{
$this->validateTagName($name);
$this->name = $name;
$this->description = $description;
}
public static function create(
$body,
$name = '',
DescriptionFactory $descriptionFactory = null,
Context $context = null
)
{
$description = $descriptionFactory ? $descriptionFactory->create($body, $context) : null;
return new static($name, $description);
}
/**
* Returns the tag as a serialized string
*
* @return string
*/
public function __toString()
{
return "@{$this->getName()} {$this->description->render()}";
}
/**
* Validates if the tag name matches the expected format, otherwise throws an exception.
*
* @param string $name
*
* @return void
*/
private function validateTagName($name)
{
if (!preg_match('/^' . TagFactory::REGEX_TAGNAME . '$/u', $name)) {
throw new \InvalidArgumentException(
'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, '
. 'hyphens and backslashes.'
);
}
}
}
+54 -55
View File
@@ -1,28 +1,34 @@
<?php
/**
* phpDocumentor
* This file is part of phpDocumentor.
*
* PHP Version 5.3
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @author Mike van Riel <mike[email protected]>
* @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
* @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context;
/**
* Reflection class for a @param tag in a Docblock.
*
* @author Mike van Riel <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link http://phpdoc.org
*/
class Param extends Return_
class Param extends BaseTag
{
protected $name = 'param';
/** @var Type */
private $type;
/** @var string */
protected $variableName = '';
@@ -30,56 +36,58 @@ class Param extends Return_
protected $isVariadic = false;
/**
* {@inheritdoc}
* @param string $variableName
* @param Type $type
* @param bool $isVariadic
* @param Description $description
*/
public function getContent()
public function __construct($variableName, Type $type = null, $isVariadic = false, Description $description = null)
{
if (null === $this->description) {
$this->description
= "{$this->type} {$this->variableName} {$this->description}";
}
return $this->description;
$this->variableName = $variableName;
$this->type = $type;
$this->isVariadic = $isVariadic;
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public function setContent($content)
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
Context $context = null
)
{
Tag::setContent($content);
$parts = preg_split(
'/(\s+)/Su',
$this->description,
3,
PREG_SPLIT_DELIM_CAPTURE
);
$parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
$type = null;
$variableName = '';
$isVariadic = false;
// if the first item that is encountered is not a variable; it is a type
if (isset($parts[0])
&& (strlen($parts[0]) > 0)
&& ($parts[0][0] !== '$')
) {
$this->type = array_shift($parts);
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
$type = $typeResolver->resolve(array_shift($parts), $context);
array_shift($parts);
}
// if the next item starts with a $ or ...$ it must be the variable name
if (isset($parts[0])
&& (strlen($parts[0]) > 0)
&& ($parts[0][0] == '$' || substr($parts[0], 0, 4) === '...$')
) {
$this->variableName = array_shift($parts);
if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] == '$' || substr($parts[0], 0, 4) === '...$')) {
$variableName = array_shift($parts);
array_shift($parts);
if (substr($this->variableName, 0, 3) === '...') {
$this->isVariadic = true;
$this->variableName = substr($this->variableName, 3);
if (substr($variableName, 0, 3) === '...') {
$isVariadic = true;
$variableName = substr($variableName, 3);
}
if (substr($variableName, 0, 1) === '$') {
$variableName = substr($variableName, 1);
}
}
$this->setDescription(implode('', $parts));
$description = $descriptionFactory->create(implode('', $parts), $context);
$this->description = $content;
return $this;
return new static($variableName, $type, $isVariadic, $description);
}
/**
@@ -92,21 +100,6 @@ class Param extends Return_
return $this->variableName;
}
/**
* Sets the variable's name.
*
* @param string $name The new name for this variable.
*
* @return $this
*/
public function setVariableName($name)
{
$this->variableName = $name;
$this->description = null;
return $this;
}
/**
* Returns whether this tag is variadic.
*
@@ -116,4 +109,10 @@ class Param extends Return_
{
return $this->isVariadic;
}
public function __toString()
{
return $this->type . ' ' . ($this->isVariadic() ? '...' : '') . '$' . $this->variableName . ' '
. $this->description;
}
}
+26 -51
View File
@@ -12,84 +12,59 @@
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Type\Collection;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context;
/**
* Reflection class for a @return tag in a Docblock.
*/
class Return_ extends Tag
final class Return_ extends BaseTag
{
/** @var string The raw type component. */
protected $type = '';
protected $name = 'return';
/** @var Collection The parsed type component. */
protected $types = null;
/** @var Type */
private $type;
/**
* {@inheritdoc}
*/
public function getContent()
public function __construct(Type $type, Description $description = null)
{
if (null === $this->description) {
$this->description = "{$this->type} {$this->description}";
}
return $this->description;
$this->description = $description;
$this->type = $type;
}
/**
* {@inheritdoc}
*/
public function setContent($content)
public static function create(
$body,
TypeResolver $typeResolver = null,
DescriptionFactory $descriptionFactory = null,
Context $context = null
)
{
parent::setContent($content);
$parts = preg_split('/\s+/Su', $body, 2);
$parts = preg_split('/\s+/Su', $this->description, 2);
$type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
$description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '');
// any output is considered a type
$this->type = $parts[0];
$this->types = null;
$this->setDescription(isset($parts[1]) ? $parts[1] : '');
$this->description = $content;
return $this;
return new static($type, $description);
}
/**
* Returns the unique types of the variable.
*
* @return string[]
*/
public function getTypes()
public function __toString()
{
return $this->getTypesCollection()->getArrayCopy();
return $this->type . ' ' . $this->description;
}
/**
* Returns the type section of the variable.
*
* @return string
* @return Type
*/
public function getType()
{
return (string) $this->getTypesCollection();
}
/**
* Returns the type collection.
*
* @return void
*/
protected function getTypesCollection()
{
if (null === $this->types) {
$this->types = new Collection(
array($this->type),
$this->docblock ? $this->docblock->getContext() : null
);
}
return $this->types;
return $this->type;
}
}
+2 -2
View File
@@ -41,9 +41,9 @@ class See extends Tag
/**
* {@inheritdoc}
*/
public static function create($content, Context $context)
public static function create($body, Context $context)
{
$parts = preg_split('/\s+/Su', $content, 2);
$parts = preg_split('/\s+/Su', $body, 2);
$resolver = new Resolver();
return new static(
+2 -2
View File
@@ -47,10 +47,10 @@ class Version extends Tag
/**
* {@inheritdoc}
*/
public static function create($content, Context $context = null)
public static function create($body, Context $context = null)
{
$matches = [];
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $content, $matches)) {
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return null;
}