mirror of
https://github.com/barryvdh/ReflectionDocBlock.git
synced 2026-08-18 18:13:13 +00:00
Add tests for StandardTagFactory and adjust for found bugs
This commit is contained in:
@@ -15,20 +15,38 @@ namespace phpDocumentor\Reflection\DocBlock;
|
|||||||
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
|
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
|
||||||
use phpDocumentor\Reflection\FqsenResolver;
|
use phpDocumentor\Reflection\FqsenResolver;
|
||||||
use phpDocumentor\Reflection\Types\Context;
|
use phpDocumentor\Reflection\Types\Context;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Tag object given the contents of a tag.
|
||||||
|
*
|
||||||
|
* This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create`
|
||||||
|
* factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can
|
||||||
|
* pass the dependencies that you need to construct a tag object.
|
||||||
|
*
|
||||||
|
* > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise
|
||||||
|
* > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to
|
||||||
|
* > verify that a dependency is actually passed.
|
||||||
|
*
|
||||||
|
* This Factory also features a Service Locator component that is used to pass the right dependencies to the
|
||||||
|
* `create` method of a tag; each dependency should be registered as a service or as a parameter.
|
||||||
|
*
|
||||||
|
* When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass
|
||||||
|
* the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface.
|
||||||
|
*/
|
||||||
final class StandardTagFactory implements TagFactory
|
final class StandardTagFactory implements TagFactory
|
||||||
{
|
{
|
||||||
/** PCRE regular expression matching a tag name. */
|
/** PCRE regular expression matching a tag name. */
|
||||||
const REGEX_TAGNAME = '[\w\-\_\\\\]+';
|
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.
|
* @var string[] An array with a tag as a key, and an FQCN to a class that handles it as an array value.
|
||||||
*/
|
*/
|
||||||
private $tagHandlerMappings = array(
|
private $tagHandlerMappings = [
|
||||||
'author' => '\phpDocumentor\Reflection\DocBlock\Tags\Author',
|
'author' => '\phpDocumentor\Reflection\DocBlock\Tags\Author',
|
||||||
'covers' => '\phpDocumentor\Reflection\DocBlock\Tags\Covers',
|
'covers' => '\phpDocumentor\Reflection\DocBlock\Tags\Covers',
|
||||||
'deprecated' => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated',
|
'deprecated' => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated',
|
||||||
'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example',
|
// 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example',
|
||||||
'link' => '\phpDocumentor\Reflection\DocBlock\Tags\Link',
|
'link' => '\phpDocumentor\Reflection\DocBlock\Tags\Link',
|
||||||
'method' => '\phpDocumentor\Reflection\DocBlock\Tags\Method',
|
'method' => '\phpDocumentor\Reflection\DocBlock\Tags\Method',
|
||||||
'param' => '\phpDocumentor\Reflection\DocBlock\Tags\Param',
|
'param' => '\phpDocumentor\Reflection\DocBlock\Tags\Param',
|
||||||
@@ -44,115 +62,92 @@ final class StandardTagFactory implements TagFactory
|
|||||||
'uses' => '\phpDocumentor\Reflection\DocBlock\Tags\Uses',
|
'uses' => '\phpDocumentor\Reflection\DocBlock\Tags\Uses',
|
||||||
'var' => '\phpDocumentor\Reflection\DocBlock\Tags\Var_',
|
'var' => '\phpDocumentor\Reflection\DocBlock\Tags\Var_',
|
||||||
'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version'
|
'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version'
|
||||||
);
|
];
|
||||||
|
|
||||||
/** @var FqsenResolver */
|
/**
|
||||||
|
* @var \ReflectionParameter[][] a lazy-loading cache containing parameters for each tagHandler that has been used.
|
||||||
|
*/
|
||||||
|
private $tagHandlerParameterCache = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var FqsenResolver
|
||||||
|
*/
|
||||||
private $fqsenResolver;
|
private $fqsenResolver;
|
||||||
|
|
||||||
/** @var mixed[] */
|
/**
|
||||||
|
* @var mixed[] an array representing a simple Service Locator where we can store parameters and
|
||||||
|
* services that can be inserted into the Factory Methods of Tag Handlers.
|
||||||
|
*/
|
||||||
private $serviceLocator = [];
|
private $serviceLocator = [];
|
||||||
|
|
||||||
public function __construct(FqsenResolver $fqsenResolver)
|
/**
|
||||||
|
* Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers.
|
||||||
|
*
|
||||||
|
* If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property
|
||||||
|
* is used.
|
||||||
|
*
|
||||||
|
* @param FqsenResolver $fqsenResolver
|
||||||
|
* @param string[] $tagHandlers
|
||||||
|
*
|
||||||
|
* @see self::registerTagHandler() to add a new tag handler to the existing default list.
|
||||||
|
*/
|
||||||
|
public function __construct(FqsenResolver $fqsenResolver, array $tagHandlers = null)
|
||||||
{
|
{
|
||||||
$this->fqsenResolver = $fqsenResolver;
|
$this->fqsenResolver = $fqsenResolver;
|
||||||
$this->addService($fqsenResolver);
|
if ($tagHandlers !== null) {
|
||||||
|
$this->tagHandlerMappings = $tagHandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addParameter($name, $value)
|
$this->addService($fqsenResolver, FqsenResolver::class);
|
||||||
{
|
|
||||||
$this->serviceLocator[$name] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addService($service)
|
|
||||||
{
|
|
||||||
$this->serviceLocator[get_class($service)] = $service;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method responsible for instantiating the correct sub type.
|
* {@inheritDoc}
|
||||||
*
|
|
||||||
* @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)
|
public function create($tagLine, Context $context = null)
|
||||||
{
|
{
|
||||||
if (! $context) {
|
if (! $context) {
|
||||||
$context = new Context('');
|
$context = new Context('');
|
||||||
}
|
}
|
||||||
|
|
||||||
list($tagName, $tagBody) = $this->extractTagParts($tagLine);
|
list($tagName, $tagBody) = $this->extractTagParts($tagLine);
|
||||||
|
|
||||||
$handler = Generic::class;
|
return $this->createTag($tagBody, $tagName, $context);
|
||||||
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.
|
* {@inheritDoc}
|
||||||
*
|
|
||||||
* 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)
|
public function addParameter($name, $value)
|
||||||
{
|
{
|
||||||
$tag = trim((string)$tag);
|
$this->serviceLocator[$name] = $value;
|
||||||
|
|
||||||
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;
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function addService($service, $alias = null)
|
||||||
|
{
|
||||||
|
$this->serviceLocator[$alias ?: get_class($service)] = $service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function registerTagHandler($tagName, $handler)
|
||||||
|
{
|
||||||
|
Assert::stringNotEmpty($tagName);
|
||||||
|
Assert::stringNotEmpty($handler);
|
||||||
|
Assert::classExists($handler);
|
||||||
|
Assert::implementsInterface($handler, Tag::class);
|
||||||
|
|
||||||
|
if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'A namespaced tag must have a leading backslash as it must be fully qualified'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tagHandlerMappings[$tagName] = $handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,7 +173,135 @@ final class StandardTagFactory implements TagFactory
|
|||||||
return array_slice($matches, 1);
|
return array_slice($matches, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isAnnotation($tag)
|
/**
|
||||||
|
* Creates a new tag object with the given name and body or returns null if the tag name was recognized but the
|
||||||
|
* body was invalid.
|
||||||
|
*
|
||||||
|
* @param string $body
|
||||||
|
* @param string $name
|
||||||
|
* @param Context $context
|
||||||
|
*
|
||||||
|
* @return Tag|null
|
||||||
|
*/
|
||||||
|
private function createTag($body, $name, Context $context)
|
||||||
|
{
|
||||||
|
$handlerClassName = $this->findHandlerClassName($name, $context);
|
||||||
|
$arguments = $this->getArgumentsForParametersFromWiring(
|
||||||
|
$this->fetchParametersForHandlerFactoryMethod($handlerClassName),
|
||||||
|
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
return call_user_func_array([$handlerClassName, 'create'], $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
|
||||||
|
*
|
||||||
|
* @param string $tagName
|
||||||
|
* @param Context $context
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function findHandlerClassName($tagName, Context $context)
|
||||||
|
{
|
||||||
|
$handlerClassName = Generic::class;
|
||||||
|
if (isset($this->tagHandlerMappings[$tagName])) {
|
||||||
|
$handlerClassName = $this->tagHandlerMappings[$tagName];
|
||||||
|
} elseif ($this->isAnnotation($tagName)) {
|
||||||
|
// TODO: Annotation support is planned for a later stage and as such is disabled for now
|
||||||
|
// $tagName = (string)$this->fqsenResolver->resolve($tagName, $context);
|
||||||
|
// if (isset($this->annotationMappings[$tagName])) {
|
||||||
|
// $handlerClassName = $this->annotationMappings[$tagName];
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handlerClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the arguments that need to be passed to the Factory Method with the given Parameters.
|
||||||
|
*
|
||||||
|
* @param \ReflectionParameter[] $parameters
|
||||||
|
* @param mixed[] $locator
|
||||||
|
*
|
||||||
|
* @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters
|
||||||
|
* is provided with this method.
|
||||||
|
*/
|
||||||
|
private function getArgumentsForParametersFromWiring($parameters, $locator)
|
||||||
|
{
|
||||||
|
$arguments = [];
|
||||||
|
foreach ($parameters as $index => $parameter) {
|
||||||
|
$typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null;
|
||||||
|
if (isset($locator[$typeHint])) {
|
||||||
|
$arguments[] = $locator[$typeHint];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameterName = $parameter->getName();
|
||||||
|
if (isset($locator[$parameterName])) {
|
||||||
|
$arguments[] = $locator[$parameterName];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$arguments[] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
|
||||||
|
* tag handler class name.
|
||||||
|
*
|
||||||
|
* @param string $handlerClassName
|
||||||
|
*
|
||||||
|
* @return \ReflectionParameter[]
|
||||||
|
*/
|
||||||
|
private function fetchParametersForHandlerFactoryMethod($handlerClassName)
|
||||||
|
{
|
||||||
|
if (! isset($this->tagHandlerParameterCache[$handlerClassName])) {
|
||||||
|
$methodReflection = new \ReflectionMethod($handlerClassName, 'create');
|
||||||
|
$this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->tagHandlerParameterCache[$handlerClassName];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this class' Service Locator with added dynamic parameters, such as the tag's name, body and
|
||||||
|
* Context.
|
||||||
|
*
|
||||||
|
* @param Context $context The Context (namespace and aliasses) that may be passed and is used to resolve FQSENs.
|
||||||
|
* @param string $tagName The name of the tag that may be passed onto the factory method of the Tag class.
|
||||||
|
* @param string $tagBody The body of the tag that may be passed onto the factory method of the Tag class.
|
||||||
|
*
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
private function getServiceLocatorWithDynamicParameters(Context $context, $tagName, $tagBody)
|
||||||
|
{
|
||||||
|
$locator = array_merge(
|
||||||
|
$this->serviceLocator,
|
||||||
|
[
|
||||||
|
'name' => $tagName,
|
||||||
|
'body' => $tagBody,
|
||||||
|
Context::class => $context
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $locator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given tag belongs to an annotation.
|
||||||
|
*
|
||||||
|
* @param string $tagContent
|
||||||
|
*
|
||||||
|
* @todo this method should be populated once we implement Annotation notation support.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isAnnotation($tagContent)
|
||||||
{
|
{
|
||||||
// 1. Contains a namespace separator
|
// 1. Contains a namespace separator
|
||||||
// 2. Contains parenthesis
|
// 2. Contains parenthesis
|
||||||
|
|||||||
@@ -16,8 +16,45 @@ use phpDocumentor\Reflection\Types\Context;
|
|||||||
|
|
||||||
interface TagFactory
|
interface TagFactory
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Adds a parameter to the service locator that can be injected in a tag's factory method.
|
||||||
|
*
|
||||||
|
* When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to
|
||||||
|
* typehint a parameter in the signature so that we can use that interface or class name to inject a dependency
|
||||||
|
* (see {@see addService()} for more information on that).
|
||||||
|
*
|
||||||
|
* Another way is to check the name of the argument against the names in the Service Locator. With this method
|
||||||
|
* you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching
|
||||||
|
* name.
|
||||||
|
*
|
||||||
|
* Be aware that there are two reserved names:
|
||||||
|
*
|
||||||
|
* - name, representing the name of the tag.
|
||||||
|
* - body, representing the complete body of the tag.
|
||||||
|
*
|
||||||
|
* These parameters are injected at the last moment and will override any existing parameter with those names.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function addParameter($name, $value);
|
public function addParameter($name, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.
|
||||||
|
*
|
||||||
|
* When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter
|
||||||
|
* has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint.
|
||||||
|
*
|
||||||
|
* Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the
|
||||||
|
* interface is passed as alias then every time that interface is requested the provided service will be returned.
|
||||||
|
*
|
||||||
|
* @param object $service
|
||||||
|
* @param string $alias
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public function addService($service);
|
public function addService($service);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,21 +65,29 @@ interface TagFactory
|
|||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException if an invalid tag line was presented.
|
* @throws \InvalidArgumentException if an invalid tag line was presented.
|
||||||
*
|
*
|
||||||
* @return static A new tag object.
|
* @return Tag A new tag object.
|
||||||
*/
|
*/
|
||||||
public function create($tagLine, Context $context = null);
|
public function create($tagLine, Context $context = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a handler for tags.
|
* Registers a handler for tags.
|
||||||
*
|
*
|
||||||
* Registers a handler for tags. The class specified is autoloaded if it's not available. It must inherit from
|
* If you want to use your own tags then you can use this method to instruct the TagFactory to register the name
|
||||||
* this class.
|
* of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement the {@see Tag} interface (and thus
|
||||||
|
* the create method).
|
||||||
*
|
*
|
||||||
* @param string $tag Name of tag to register a handler for. When registering a namespaced tag, the full
|
* @param string $tagName Name of tag to register a handler for. When registering a namespaced tag, the full
|
||||||
* name, along with a prefixing slash MUST be provided.
|
* name, along with a prefixing slash MUST be provided.
|
||||||
* @param string|null $handler FQCN of handler.
|
* @param string $handler FQCN of handler.
|
||||||
*
|
*
|
||||||
* @return bool TRUE on success, FALSE on failure.
|
* @throws \InvalidArgumentException if the tag name is not a string
|
||||||
|
* @throws \InvalidArgumentException if the tag name is namespaced (contains backslashes) but does not start with
|
||||||
|
* a backslash
|
||||||
|
* @throws \InvalidArgumentException if the handler is not a string
|
||||||
|
* @throws \InvalidArgumentException if the handler is not an existing class
|
||||||
|
* @throws \InvalidArgumentException if the handler does not implement the {@see Tag} interface
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function registerTagHandler($tag, $handler);
|
public function registerTagHandler($tagName, $handler);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class Generic extends BaseTag
|
|||||||
) {
|
) {
|
||||||
Assert::string($body);
|
Assert::string($body);
|
||||||
Assert::stringNotEmpty($name);
|
Assert::stringNotEmpty($name);
|
||||||
|
Assert::notNull($descriptionFactory);
|
||||||
|
|
||||||
$description = $descriptionFactory && $body ? $descriptionFactory->create($body, $context) : null;
|
$description = $descriptionFactory && $body ? $descriptionFactory->create($body, $context) : null;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,325 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Mockery as m;
|
||||||
|
use phpDocumentor\Reflection\DocBlock\Tags\Author;
|
||||||
|
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
|
||||||
|
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
|
||||||
|
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
|
||||||
|
use phpDocumentor\Reflection\DocBlock\Tags\See;
|
||||||
|
use phpDocumentor\Reflection\Fqsen;
|
||||||
|
use phpDocumentor\Reflection\FqsenResolver;
|
||||||
|
use phpDocumentor\Reflection\Types\Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @coversDefaultClass phpDocumentor\Reflection\DocBlock\StandardTagFactory
|
||||||
|
* @covers ::<private>
|
||||||
|
*/
|
||||||
|
class StandardTagFactoryTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::create
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\Generic
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\BaseTag
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Description
|
||||||
|
*/
|
||||||
|
public function testCreatingAGenericTag()
|
||||||
|
{
|
||||||
|
$expectedTagName = 'unknown-tag';
|
||||||
|
$expectedDescriptionText = 'This is a description';
|
||||||
|
$expectedDescription = new Description($expectedDescriptionText);
|
||||||
|
$context = new Context('');
|
||||||
|
|
||||||
|
$descriptionFactory = m::mock(DescriptionFactory::class);
|
||||||
|
$descriptionFactory
|
||||||
|
->shouldReceive('create')
|
||||||
|
->once()
|
||||||
|
->with($expectedDescriptionText, $context)
|
||||||
|
->andReturn($expectedDescription)
|
||||||
|
;
|
||||||
|
|
||||||
|
$tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
|
||||||
|
$tagFactory->addService($descriptionFactory, DescriptionFactory::class);
|
||||||
|
|
||||||
|
/** @var Generic $tag */
|
||||||
|
$tag = $tagFactory->create('@' . $expectedTagName . ' This is a description', $context);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Generic::class, $tag);
|
||||||
|
$this->assertSame($expectedTagName, $tag->getName());
|
||||||
|
$this->assertSame($expectedDescription, $tag->getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::create
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\Author
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\BaseTag
|
||||||
|
*/
|
||||||
|
public function testCreatingASpecificTag()
|
||||||
|
{
|
||||||
|
$context = new Context('');
|
||||||
|
$tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
|
||||||
|
|
||||||
|
/** @var Author $tag */
|
||||||
|
$tag = $tagFactory->create('@author Mike van Riel <[email protected]>', $context);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Author::class, $tag);
|
||||||
|
$this->assertSame('author', $tag->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::create
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\See
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\BaseTag
|
||||||
|
*/
|
||||||
|
public function testAnEmptyContextIsCreatedIfNoneIsProvided()
|
||||||
|
{
|
||||||
|
$fqsen = '\Tag';
|
||||||
|
$resolver = m::mock(FqsenResolver::class)
|
||||||
|
->shouldReceive('resolve')
|
||||||
|
->with('Tag', m::type(Context::class))
|
||||||
|
->andReturn(new Fqsen($fqsen))
|
||||||
|
->getMock()
|
||||||
|
;
|
||||||
|
$descriptionFactory = m::mock(DescriptionFactory::class);
|
||||||
|
$descriptionFactory->shouldIgnoreMissing();
|
||||||
|
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
$tagFactory->addService($descriptionFactory, DescriptionFactory::class);
|
||||||
|
|
||||||
|
/** @var See $tag */
|
||||||
|
$tag = $tagFactory->create('@see Tag');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(See::class, $tag);
|
||||||
|
$this->assertSame($fqsen, (string)$tag->getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::create
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\Author
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\BaseTag
|
||||||
|
*/
|
||||||
|
public function testPassingYourOwnSetOfTagHandlers()
|
||||||
|
{
|
||||||
|
$context = new Context('');
|
||||||
|
$tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class), ['user' => Author::class]);
|
||||||
|
|
||||||
|
/** @var Author $tag */
|
||||||
|
$tag = $tagFactory->create('@user Mike van Riel <[email protected]>', $context);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Author::class, $tag);
|
||||||
|
$this->assertSame('author', $tag->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::create
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage The tag "@user/myuser" does not seem to be wellformed, please check it for errors
|
||||||
|
*/
|
||||||
|
public function testExceptionIsThrownIfProvidedTagIsNotWellformed()
|
||||||
|
{
|
||||||
|
$this->markTestIncomplete(
|
||||||
|
'For some reason this test fails; once I have access to a RegEx analyzer I will have to test the regex'
|
||||||
|
)
|
||||||
|
;
|
||||||
|
$tagFactory = new StandardTagFactory(m::mock(FqsenResolver::class));
|
||||||
|
$tagFactory->create('@user[myuser');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::__construct
|
||||||
|
* @covers ::addParameter
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
*/
|
||||||
|
public function testAddParameterToServiceLocator()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
$tagFactory->addParameter('myParam', 'myValue');
|
||||||
|
|
||||||
|
$this->assertAttributeSame(
|
||||||
|
[FqsenResolver::class => $resolver, 'myParam' => 'myValue'],
|
||||||
|
'serviceLocator',
|
||||||
|
$tagFactory
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::addService
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
*/
|
||||||
|
public function testAddServiceToServiceLocator()
|
||||||
|
{
|
||||||
|
$service = new PassthroughFormatter();
|
||||||
|
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
$tagFactory->addService($service);
|
||||||
|
|
||||||
|
$this->assertAttributeSame(
|
||||||
|
[FqsenResolver::class => $resolver, PassthroughFormatter::class => $service],
|
||||||
|
'serviceLocator',
|
||||||
|
$tagFactory
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::addService
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
*/
|
||||||
|
public function testInjectConcreteServiceForInterfaceToServiceLocator()
|
||||||
|
{
|
||||||
|
$interfaceName = Formatter::class;
|
||||||
|
$service = new PassthroughFormatter();
|
||||||
|
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
$tagFactory->addService($service, $interfaceName);
|
||||||
|
|
||||||
|
$this->assertAttributeSame(
|
||||||
|
[FqsenResolver::class => $resolver, $interfaceName => $service],
|
||||||
|
'serviceLocator',
|
||||||
|
$tagFactory
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::create
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\Tags\Author
|
||||||
|
*/
|
||||||
|
public function testRegisteringAHandlerForANewTag()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler('my-tag', Author::class);
|
||||||
|
|
||||||
|
// Assert by trying to create one
|
||||||
|
$tag = $tagFactory->create('@my-tag Mike van Riel <[email protected]>');
|
||||||
|
$this->assertInstanceOf(Author::class, $tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testHandlerRegistrationFailsIfProvidedTagNameIsNotAString()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler([], Author::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testHandlerRegistrationFailsIfProvidedTagNameIsEmpty()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler('', Author::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testHandlerRegistrationFailsIfProvidedTagNameIsNamespaceButNotFullyQualified()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler('Name\Spaced\Tag', Author::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testHandlerRegistrationFailsIfProvidedHandlerIsNotAString()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler('my-tag', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testHandlerRegistrationFailsIfProvidedHandlerIsEmpty()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler('my-tag', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testHandlerRegistrationFailsIfProvidedHandlerIsNotAnExistingClassName()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler('my-tag', 'IDoNotExist');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::registerTagHandler
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::__construct
|
||||||
|
* @uses phpDocumentor\Reflection\DocBlock\StandardTagFactory::addService
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testHandlerRegistrationFailsIfProvidedHandlerDoesNotImplementTheTagInterface()
|
||||||
|
{
|
||||||
|
$resolver = m::mock(FqsenResolver::class);
|
||||||
|
$tagFactory = new StandardTagFactory($resolver);
|
||||||
|
|
||||||
|
$tagFactory->registerTagHandler('my-tag', 'stdClass');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user