class DocBlockFactory
Hierarchy
- class \phpDocumentor\Reflection\DocBlockFactory implements \phpDocumentor\Reflection\DocBlockFactoryInterface
Expanded class hierarchy of DocBlockFactory
1 file declares its use of DocBlockFactory
- ClassTagRetriever.php in vendor/
phpspec/ prophecy/ src/ Prophecy/ PhpDocumentor/ ClassTagRetriever.php
File
-
vendor/
phpdocumentor/ reflection-docblock/ src/ DocBlockFactory.php, line 50
Namespace
phpDocumentor\ReflectionView source
final class DocBlockFactory implements DocBlockFactoryInterface {
private DocBlock\DescriptionFactory $descriptionFactory;
private TagFactory $tagFactory;
/**
* Initializes this factory with the required subcontractors.
*/
public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) {
$this->descriptionFactory = $descriptionFactory;
$this->tagFactory = $tagFactory;
}
/**
* Factory method for easy instantiation.
*
* @param array<string, class-string<Tag>|Factory> $additionalTags
*/
public static function createInstance(array $additionalTags = []) : DocBlockFactoryInterface {
$fqsenResolver = new FqsenResolver();
$tagFactory = new StandardTagFactory($fqsenResolver);
$descriptionFactory = new DescriptionFactory($tagFactory);
$typeResolver = new TypeResolver($fqsenResolver);
$phpstanTagFactory = new AbstractPHPStanFactory(new ParamFactory($typeResolver, $descriptionFactory), new VarFactory($typeResolver, $descriptionFactory), new ReturnFactory($typeResolver, $descriptionFactory), new PropertyFactory($typeResolver, $descriptionFactory), new PropertyReadFactory($typeResolver, $descriptionFactory), new PropertyWriteFactory($typeResolver, $descriptionFactory), new MethodFactory($typeResolver, $descriptionFactory), new ImplementsFactory($typeResolver, $descriptionFactory), new ExtendsFactory($typeResolver, $descriptionFactory), new TemplateFactory($typeResolver, $descriptionFactory), new TemplateImplementsFactory($typeResolver, $descriptionFactory), new TemplateExtendsFactory($typeResolver, $descriptionFactory));
$tagFactory->addService($descriptionFactory);
$tagFactory->addService($typeResolver);
$tagFactory->registerTagHandler('param', $phpstanTagFactory);
$tagFactory->registerTagHandler('var', $phpstanTagFactory);
$tagFactory->registerTagHandler('return', $phpstanTagFactory);
$tagFactory->registerTagHandler('property', $phpstanTagFactory);
$tagFactory->registerTagHandler('property-read', $phpstanTagFactory);
$tagFactory->registerTagHandler('property-write', $phpstanTagFactory);
$tagFactory->registerTagHandler('method', $phpstanTagFactory);
$tagFactory->registerTagHandler('extends', $phpstanTagFactory);
$tagFactory->registerTagHandler('implements', $phpstanTagFactory);
$tagFactory->registerTagHandler('template', $phpstanTagFactory);
$tagFactory->registerTagHandler('template-extends', $phpstanTagFactory);
$tagFactory->registerTagHandler('template-implements', $phpstanTagFactory);
$docBlockFactory = new self($descriptionFactory, $tagFactory);
foreach ($additionalTags as $tagName => $tagHandler) {
$docBlockFactory->registerTagHandler($tagName, $tagHandler);
}
return $docBlockFactory;
}
/**
* @param object|string $docblock A string containing the DocBlock to parse or an object supporting the
* getDocComment method (such as a ReflectionClass object).
*/
public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock {
if (is_object($docblock)) {
if (!method_exists($docblock, 'getDocComment')) {
$exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
throw new InvalidArgumentException($exceptionMessage);
}
$docblock = $docblock->getDocComment();
Assert::string($docblock);
}
Assert::stringNotEmpty($docblock);
if ($context === null) {
$context = new Types\Context('');
}
$parts = $this->splitDocBlock($this->stripDocComment($docblock));
[
$templateMarker,
$summary,
$description,
$tags,
] = $parts;
return new DocBlock($summary, $description ? $this->descriptionFactory
->create($description, $context) : null, $this->parseTagBlock($tags, $context), $context, $location, $templateMarker === '#@+', $templateMarker === '#@-');
}
/**
* @param class-string<Tag>|Factory $handler
*/
public function registerTagHandler(string $tagName, $handler) : void {
$this->tagFactory
->registerTagHandler($tagName, $handler);
}
/**
* Strips the asterisks from the DocBlock comment.
*
* @param string $comment String containing the comment text.
*/
private function stripDocComment(string $comment) : string {
$comment = preg_replace('#[ \\t]*(?:\\/\\*\\*|\\*\\/|\\*)?[ \\t]?(.*)?#u', '$1', $comment);
Assert::string($comment);
$comment = trim($comment);
// reg ex above is not able to remove */ from a single line docblock
if (substr($comment, -2) === '*/') {
$comment = trim(substr($comment, 0, -2));
}
return str_replace([
"\r\n",
"\r",
], "\n", $comment);
}
// phpcs:disable
/**
* Splits the DocBlock into a template marker, summary, description and block of tags.
*
* @param string $comment Comment to split into the sub-parts.
*
* @return string[] containing the template marker (if any), summary, description and a string containing the tags.
*
* @author Mike van Riel <me@mikevanriel.com> for extending the regex with template marker support.
*
* @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
*/
private function splitDocBlock(string $comment) : array {
// phpcs:enable
// Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
// method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
// performance impact of running a regular expression
if (strpos($comment, '@') === 0) {
return [
'',
'',
'',
$comment,
];
}
// clears all extra horizontal whitespace from the line endings to prevent parsing issues
$comment = preg_replace('/\\h*$/Sum', '', $comment);
Assert::string($comment);
/*
* Splits the docblock into a template marker, summary, description and tags section.
*
* - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
* occur after it and will be stripped).
* - The short description is started from the first character until a dot is encountered followed by a
* newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
* errors). This is optional.
* - The long description, any character until a new line is encountered followed by an @ and word
* characters (a tag). This is optional.
* - Tags; the remaining characters
*
* Big thanks to RichardJ for contributing this Regular Expression
*/
preg_match('/
\\A
# 1. Extract the template marker
(?:(\\#\\@\\+|\\#\\@\\-)\\n?)?
# 2. Extract the summary
(?:
(?! @\\pL ) # The summary may not start with an @
(
[^\\n.]+
(?:
(?! \\. \\n | \\n{2} ) # End summary upon a dot followed by newline or two newlines
[\\n.]* (?! [ \\t]* @\\pL ) # End summary when an @ is found as first character on a new line
[^\\n.]+ # Include anything else
)*
\\.?
)?
)
# 3. Extract the description
(?:
\\s* # Some form of whitespace _must_ precede a description because a summary must be there
(?! @\\pL ) # The description may not start with an @
(
[^\\n]+
(?: \\n+
(?! [ \\t]* @\\pL ) # End description when an @ is found as first character on a new line
[^\\n]+ # Include anything else
)*
)
)?
# 4. Extract the tags (anything that follows)
(\\s+ [\\s\\S]*)? # everything that follows
/ux', $comment, $matches);
array_shift($matches);
while (count($matches) < 4) {
$matches[] = '';
}
return $matches;
}
/**
* Creates the tag objects.
*
* @param string $tags Tag block to parse.
* @param Types\Context $context Context of the parsed Tag
*
* @return DocBlock\Tag[]
*/
private function parseTagBlock(string $tags, Types\Context $context) : array {
$tags = $this->filterTagBlock($tags);
if ($tags === null) {
return [];
}
$result = [];
$lines = $this->splitTagBlockIntoTagLines($tags);
foreach ($lines as $key => $tagLine) {
$result[$key] = $this->tagFactory
->create(trim($tagLine), $context);
}
return $result;
}
/**
* @return string[]
*/
private function splitTagBlockIntoTagLines(string $tags) : array {
$result = [];
foreach (explode("\n", $tags) as $tagLine) {
if ($tagLine !== '' && strpos($tagLine, '@') === 0) {
$result[] = $tagLine;
}
else {
$result[count($result) - 1] .= "\n" . $tagLine;
}
}
return $result;
}
private function filterTagBlock(string $tags) : ?string {
$tags = trim($tags);
if (!$tags) {
return null;
}
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;
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overriden Title |
---|---|---|---|---|
DocBlockFactory::$descriptionFactory | private | property | ||
DocBlockFactory::$tagFactory | private | property | ||
DocBlockFactory::create | public | function | Overrides DocBlockFactoryInterface::create | |
DocBlockFactory::createInstance | public static | function | Factory method for easy instantiation. | Overrides DocBlockFactoryInterface::createInstance |
DocBlockFactory::filterTagBlock | private | function | ||
DocBlockFactory::parseTagBlock | private | function | Creates the tag objects. | |
DocBlockFactory::registerTagHandler | public | function | ||
DocBlockFactory::splitDocBlock | private | function | Splits the DocBlock into a template marker, summary, description and block of tags. | |
DocBlockFactory::splitTagBlockIntoTagLines | private | function | ||
DocBlockFactory::stripDocComment | private | function | Strips the asterisks from the DocBlock comment. | |
DocBlockFactory::__construct | public | function | Initializes this factory with the required subcontractors. |