You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
8.1 KiB
279 lines
8.1 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @link http://phpdoc.org
|
|
*/
|
|
|
|
namespace phpDocumentor\Reflection\DocBlock\Tags;
|
|
|
|
use InvalidArgumentException;
|
|
use phpDocumentor\Reflection\DocBlock\Description;
|
|
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
|
|
use phpDocumentor\Reflection\Type;
|
|
use phpDocumentor\Reflection\TypeResolver;
|
|
use phpDocumentor\Reflection\Types\Context as TypeContext;
|
|
use phpDocumentor\Reflection\Types\Mixed_;
|
|
use phpDocumentor\Reflection\Types\Void_;
|
|
use Webmozart\Assert\Assert;
|
|
use function array_keys;
|
|
use function explode;
|
|
use function implode;
|
|
use function is_string;
|
|
use function preg_match;
|
|
use function sort;
|
|
use function strpos;
|
|
use function substr;
|
|
use function trim;
|
|
use function var_export;
|
|
|
|
/**
|
|
* Reflection class for an {@}method in a Docblock.
|
|
*/
|
|
final class Method extends BaseTag implements Factory\StaticMethod
|
|
{
|
|
/** @var string */
|
|
protected $name = 'method';
|
|
|
|
/** @var string */
|
|
private $methodName;
|
|
|
|
/**
|
|
* @phpstan-var array<int, array{name: string, type: Type}>
|
|
* @var array<int, array<string, Type|string>>
|
|
*/
|
|
private $arguments;
|
|
|
|
/** @var bool */
|
|
private $isStatic;
|
|
|
|
/** @var Type */
|
|
private $returnType;
|
|
|
|
/**
|
|
* @param array<int, array<string, Type|string>> $arguments
|
|
*
|
|
* @phpstan-param array<int, array{name: string, type: Type}|string> $arguments
|
|
*/
|
|
public function __construct(
|
|
string $methodName,
|
|
array $arguments = [],
|
|
?Type $returnType = null,
|
|
bool $static = false,
|
|
?Description $description = null
|
|
) {
|
|
Assert::stringNotEmpty($methodName);
|
|
|
|
if ($returnType === null) {
|
|
$returnType = new Void_();
|
|
}
|
|
|
|
$this->methodName = $methodName;
|
|
$this->arguments = $this->filterArguments($arguments);
|
|
$this->returnType = $returnType;
|
|
$this->isStatic = $static;
|
|
$this->description = $description;
|
|
}
|
|
|
|
public static function create(
|
|
string $body,
|
|
?TypeResolver $typeResolver = null,
|
|
?DescriptionFactory $descriptionFactory = null,
|
|
?TypeContext $context = null
|
|
) : ?self {
|
|
Assert::stringNotEmpty($body);
|
|
Assert::notNull($typeResolver);
|
|
Assert::notNull($descriptionFactory);
|
|
|
|
// 1. none or more whitespace
|
|
// 2. optionally the keyword "static" followed by whitespace
|
|
// 3. optionally a word with underscores followed by whitespace : as
|
|
// type for the return value
|
|
// 4. then optionally a word with underscores followed by () and
|
|
// whitespace : as method name as used by phpDocumentor
|
|
// 5. then a word with underscores, followed by ( and any character
|
|
// until a ) and whitespace : as method name with signature
|
|
// 6. any remaining text : as description
|
|
if (!preg_match(
|
|
'/^
|
|
# Static keyword
|
|
# Declares a static method ONLY if type is also present
|
|
(?:
|
|
(static)
|
|
\s+
|
|
)?
|
|
# Return type
|
|
(?:
|
|
(
|
|
(?:[\w\|_\\\\]*\$this[\w\|_\\\\]*)
|
|
|
|
|
(?:
|
|
(?:[\w\|_\\\\]+)
|
|
# array notation
|
|
(?:\[\])*
|
|
)*+
|
|
)
|
|
\s+
|
|
)?
|
|
# Method name
|
|
([\w_]+)
|
|
# Arguments
|
|
(?:
|
|
\(([^\)]*)\)
|
|
)?
|
|
\s*
|
|
# Description
|
|
(.*)
|
|
$/sux',
|
|
$body,
|
|
$matches
|
|
)) {
|
|
return null;
|
|
}
|
|
|
|
[, $static, $returnType, $methodName, $argumentLines, $description] = $matches;
|
|
|
|
$static = $static === 'static';
|
|
|
|
if ($returnType === '') {
|
|
$returnType = 'void';
|
|
}
|
|
|
|
$returnType = $typeResolver->resolve($returnType, $context);
|
|
$description = $descriptionFactory->create($description, $context);
|
|
|
|
/** @phpstan-var array<int, array{name: string, type: Type}> $arguments */
|
|
$arguments = [];
|
|
if ($argumentLines !== '') {
|
|
$argumentsExploded = explode(',', $argumentLines);
|
|
foreach ($argumentsExploded as $argument) {
|
|
$argument = explode(' ', self::stripRestArg(trim($argument)), 2);
|
|
if (strpos($argument[0], '$') === 0) {
|
|
$argumentName = substr($argument[0], 1);
|
|
$argumentType = new Mixed_();
|
|
} else {
|
|
$argumentType = $typeResolver->resolve($argument[0], $context);
|
|
$argumentName = '';
|
|
if (isset($argument[1])) {
|
|
$argument[1] = self::stripRestArg($argument[1]);
|
|
$argumentName = substr($argument[1], 1);
|
|
}
|
|
}
|
|
|
|
$arguments[] = ['name' => $argumentName, 'type' => $argumentType];
|
|
}
|
|
}
|
|
|
|
return new static($methodName, $arguments, $returnType, $static, $description);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the method name.
|
|
*/
|
|
public function getMethodName() : string
|
|
{
|
|
return $this->methodName;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, Type|string>>
|
|
*
|
|
* @phpstan-return array<int, array{name: string, type: Type}>
|
|
*/
|
|
public function getArguments() : array
|
|
{
|
|
return $this->arguments;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the method tag describes a static method or not.
|
|
*
|
|
* @return bool TRUE if the method declaration is for a static method, FALSE otherwise.
|
|
*/
|
|
public function isStatic() : bool
|
|
{
|
|
return $this->isStatic;
|
|
}
|
|
|
|
public function getReturnType() : Type
|
|
{
|
|
return $this->returnType;
|
|
}
|
|
|
|
public function __toString() : string
|
|
{
|
|
$arguments = [];
|
|
foreach ($this->arguments as $argument) {
|
|
$arguments[] = $argument['type'] . ' $' . $argument['name'];
|
|
}
|
|
|
|
$argumentStr = '(' . implode(', ', $arguments) . ')';
|
|
|
|
if ($this->description) {
|
|
$description = $this->description->render();
|
|
} else {
|
|
$description = '';
|
|
}
|
|
|
|
$static = $this->isStatic ? 'static' : '';
|
|
|
|
$returnType = (string) $this->returnType;
|
|
|
|
$methodName = (string) $this->methodName;
|
|
|
|
return $static
|
|
. ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '')
|
|
. ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $methodName : '')
|
|
. $argumentStr
|
|
. ($description !== '' ? ' ' . $description : '');
|
|
}
|
|
|
|
/**
|
|
* @param mixed[][]|string[] $arguments
|
|
*
|
|
* @return mixed[][]
|
|
*
|
|
* @phpstan-param array<int, array{name: string, type: Type}|string> $arguments
|
|
* @phpstan-return array<int, array{name: string, type: Type}>
|
|
*/
|
|
private function filterArguments(array $arguments = []) : array
|
|
{
|
|
$result = [];
|
|
foreach ($arguments as $argument) {
|
|
if (is_string($argument)) {
|
|
$argument = ['name' => $argument];
|
|
}
|
|
|
|
if (!isset($argument['type'])) {
|
|
$argument['type'] = new Mixed_();
|
|
}
|
|
|
|
$keys = array_keys($argument);
|
|
sort($keys);
|
|
if ($keys !== ['name', 'type']) {
|
|
throw new InvalidArgumentException(
|
|
'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true)
|
|
);
|
|
}
|
|
|
|
$result[] = $argument;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private static function stripRestArg(string $argument) : string
|
|
{
|
|
if (strpos($argument, '...') === 0) {
|
|
$argument = trim(substr($argument, 3));
|
|
}
|
|
|
|
return $argument;
|
|
}
|
|
}
|
|
|