PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/laravel_tintuc/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php

https://gitlab.com/nmhieucoder/laravel_tintuc
PHP | 347 lines | 197 code | 38 blank | 112 comment | 13 complexity | a3aacd8b18f11246a7962a523f85a3b0 MD5 | raw file
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of phpDocumentor.
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. *
  9. * @link http://phpdoc.org
  10. */
  11. namespace phpDocumentor\Reflection\DocBlock;
  12. use InvalidArgumentException;
  13. use phpDocumentor\Reflection\DocBlock\Tags\Author;
  14. use phpDocumentor\Reflection\DocBlock\Tags\Covers;
  15. use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
  16. use phpDocumentor\Reflection\DocBlock\Tags\Generic;
  17. use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
  18. use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag;
  19. use phpDocumentor\Reflection\DocBlock\Tags\Method;
  20. use phpDocumentor\Reflection\DocBlock\Tags\Param;
  21. use phpDocumentor\Reflection\DocBlock\Tags\Property;
  22. use phpDocumentor\Reflection\DocBlock\Tags\PropertyRead;
  23. use phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite;
  24. use phpDocumentor\Reflection\DocBlock\Tags\Return_;
  25. use phpDocumentor\Reflection\DocBlock\Tags\See as SeeTag;
  26. use phpDocumentor\Reflection\DocBlock\Tags\Since;
  27. use phpDocumentor\Reflection\DocBlock\Tags\Source;
  28. use phpDocumentor\Reflection\DocBlock\Tags\Throws;
  29. use phpDocumentor\Reflection\DocBlock\Tags\Uses;
  30. use phpDocumentor\Reflection\DocBlock\Tags\Var_;
  31. use phpDocumentor\Reflection\DocBlock\Tags\Version;
  32. use phpDocumentor\Reflection\FqsenResolver;
  33. use phpDocumentor\Reflection\Types\Context as TypeContext;
  34. use ReflectionMethod;
  35. use ReflectionNamedType;
  36. use ReflectionParameter;
  37. use Webmozart\Assert\Assert;
  38. use function array_merge;
  39. use function array_slice;
  40. use function call_user_func_array;
  41. use function count;
  42. use function get_class;
  43. use function preg_match;
  44. use function strpos;
  45. use function trim;
  46. /**
  47. * Creates a Tag object given the contents of a tag.
  48. *
  49. * This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create`
  50. * factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can
  51. * pass the dependencies that you need to construct a tag object.
  52. *
  53. * > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise
  54. * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to
  55. * > verify that a dependency is actually passed.
  56. *
  57. * This Factory also features a Service Locator component that is used to pass the right dependencies to the
  58. * `create` method of a tag; each dependency should be registered as a service or as a parameter.
  59. *
  60. * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass
  61. * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface.
  62. */
  63. final class StandardTagFactory implements TagFactory
  64. {
  65. /** PCRE regular expression matching a tag name. */
  66. public const REGEX_TAGNAME = '[\w\-\_\\\\:]+';
  67. /**
  68. * @var array<class-string<Tag>> An array with a tag as a key, and an
  69. * FQCN to a class that handles it as an array value.
  70. */
  71. private $tagHandlerMappings = [
  72. 'author' => Author::class,
  73. 'covers' => Covers::class,
  74. 'deprecated' => Deprecated::class,
  75. // 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example',
  76. 'link' => LinkTag::class,
  77. 'method' => Method::class,
  78. 'param' => Param::class,
  79. 'property-read' => PropertyRead::class,
  80. 'property' => Property::class,
  81. 'property-write' => PropertyWrite::class,
  82. 'return' => Return_::class,
  83. 'see' => SeeTag::class,
  84. 'since' => Since::class,
  85. 'source' => Source::class,
  86. 'throw' => Throws::class,
  87. 'throws' => Throws::class,
  88. 'uses' => Uses::class,
  89. 'var' => Var_::class,
  90. 'version' => Version::class,
  91. ];
  92. /**
  93. * @var array<class-string<Tag>> An array with a anotation s a key, and an
  94. * FQCN to a class that handles it as an array value.
  95. */
  96. private $annotationMappings = [];
  97. /**
  98. * @var ReflectionParameter[][] a lazy-loading cache containing parameters
  99. * for each tagHandler that has been used.
  100. */
  101. private $tagHandlerParameterCache = [];
  102. /** @var FqsenResolver */
  103. private $fqsenResolver;
  104. /**
  105. * @var mixed[] an array representing a simple Service Locator where we can store parameters and
  106. * services that can be inserted into the Factory Methods of Tag Handlers.
  107. */
  108. private $serviceLocator = [];
  109. /**
  110. * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers.
  111. *
  112. * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property
  113. * is used.
  114. *
  115. * @see self::registerTagHandler() to add a new tag handler to the existing default list.
  116. *
  117. * @param array<class-string<Tag>> $tagHandlers
  118. */
  119. public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers = null)
  120. {
  121. $this->fqsenResolver = $fqsenResolver;
  122. if ($tagHandlers !== null) {
  123. $this->tagHandlerMappings = $tagHandlers;
  124. }
  125. $this->addService($fqsenResolver, FqsenResolver::class);
  126. }
  127. public function create(string $tagLine, ?TypeContext $context = null) : Tag
  128. {
  129. if (!$context) {
  130. $context = new TypeContext('');
  131. }
  132. [$tagName, $tagBody] = $this->extractTagParts($tagLine);
  133. return $this->createTag(trim($tagBody), $tagName, $context);
  134. }
  135. /**
  136. * @param mixed $value
  137. */
  138. public function addParameter(string $name, $value) : void
  139. {
  140. $this->serviceLocator[$name] = $value;
  141. }
  142. public function addService(object $service, ?string $alias = null) : void
  143. {
  144. $this->serviceLocator[$alias ?: get_class($service)] = $service;
  145. }
  146. public function registerTagHandler(string $tagName, string $handler) : void
  147. {
  148. Assert::stringNotEmpty($tagName);
  149. Assert::classExists($handler);
  150. Assert::implementsInterface($handler, Tag::class);
  151. if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
  152. throw new InvalidArgumentException(
  153. 'A namespaced tag must have a leading backslash as it must be fully qualified'
  154. );
  155. }
  156. $this->tagHandlerMappings[$tagName] = $handler;
  157. }
  158. /**
  159. * Extracts all components for a tag.
  160. *
  161. * @return string[]
  162. */
  163. private function extractTagParts(string $tagLine) : array
  164. {
  165. $matches = [];
  166. if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\s\(\{])\s*([^\s].*)|$)/us', $tagLine, $matches)) {
  167. throw new InvalidArgumentException(
  168. 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'
  169. );
  170. }
  171. if (count($matches) < 3) {
  172. $matches[] = '';
  173. }
  174. return array_slice($matches, 1);
  175. }
  176. /**
  177. * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the
  178. * body was invalid.
  179. */
  180. private function createTag(string $body, string $name, TypeContext $context) : Tag
  181. {
  182. $handlerClassName = $this->findHandlerClassName($name, $context);
  183. $arguments = $this->getArgumentsForParametersFromWiring(
  184. $this->fetchParametersForHandlerFactoryMethod($handlerClassName),
  185. $this->getServiceLocatorWithDynamicParameters($context, $name, $body)
  186. );
  187. try {
  188. $callable = [$handlerClassName, 'create'];
  189. Assert::isCallable($callable);
  190. /** @phpstan-var callable(string): ?Tag $callable */
  191. $tag = call_user_func_array($callable, $arguments);
  192. return $tag ?? InvalidTag::create($body, $name);
  193. } catch (InvalidArgumentException $e) {
  194. return InvalidTag::create($body, $name)->withError($e);
  195. }
  196. }
  197. /**
  198. * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
  199. *
  200. * @return class-string<Tag>
  201. */
  202. private function findHandlerClassName(string $tagName, TypeContext $context) : string
  203. {
  204. $handlerClassName = Generic::class;
  205. if (isset($this->tagHandlerMappings[$tagName])) {
  206. $handlerClassName = $this->tagHandlerMappings[$tagName];
  207. } elseif ($this->isAnnotation($tagName)) {
  208. // TODO: Annotation support is planned for a later stage and as such is disabled for now
  209. $tagName = (string) $this->fqsenResolver->resolve($tagName, $context);
  210. if (isset($this->annotationMappings[$tagName])) {
  211. $handlerClassName = $this->annotationMappings[$tagName];
  212. }
  213. }
  214. return $handlerClassName;
  215. }
  216. /**
  217. * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters.
  218. *
  219. * @param ReflectionParameter[] $parameters
  220. * @param mixed[] $locator
  221. *
  222. * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters
  223. * is provided with this method.
  224. */
  225. private function getArgumentsForParametersFromWiring(array $parameters, array $locator) : array
  226. {
  227. $arguments = [];
  228. foreach ($parameters as $parameter) {
  229. $type = $parameter->getType();
  230. $typeHint = null;
  231. if ($type instanceof ReflectionNamedType) {
  232. $typeHint = $type->getName();
  233. if ($typeHint === 'self') {
  234. $declaringClass = $parameter->getDeclaringClass();
  235. if ($declaringClass !== null) {
  236. $typeHint = $declaringClass->getName();
  237. }
  238. }
  239. }
  240. if (isset($locator[$typeHint])) {
  241. $arguments[] = $locator[$typeHint];
  242. continue;
  243. }
  244. $parameterName = $parameter->getName();
  245. if (isset($locator[$parameterName])) {
  246. $arguments[] = $locator[$parameterName];
  247. continue;
  248. }
  249. $arguments[] = null;
  250. }
  251. return $arguments;
  252. }
  253. /**
  254. * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
  255. * tag handler class name.
  256. *
  257. * @param class-string $handlerClassName
  258. *
  259. * @return ReflectionParameter[]
  260. */
  261. private function fetchParametersForHandlerFactoryMethod(string $handlerClassName) : array
  262. {
  263. if (!isset($this->tagHandlerParameterCache[$handlerClassName])) {
  264. $methodReflection = new ReflectionMethod($handlerClassName, 'create');
  265. $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
  266. }
  267. return $this->tagHandlerParameterCache[$handlerClassName];
  268. }
  269. /**
  270. * Returns a copy of this class' Service Locator with added dynamic parameters,
  271. * such as the tag's name, body and Context.
  272. *
  273. * @param TypeContext $context The Context (namespace and aliasses) that may be
  274. * passed and is used to resolve FQSENs.
  275. * @param string $tagName The name of the tag that may be
  276. * passed onto the factory method of the Tag class.
  277. * @param string $tagBody The body of the tag that may be
  278. * passed onto the factory method of the Tag class.
  279. *
  280. * @return mixed[]
  281. */
  282. private function getServiceLocatorWithDynamicParameters(
  283. TypeContext $context,
  284. string $tagName,
  285. string $tagBody
  286. ) : array {
  287. return array_merge(
  288. $this->serviceLocator,
  289. [
  290. 'name' => $tagName,
  291. 'body' => $tagBody,
  292. TypeContext::class => $context,
  293. ]
  294. );
  295. }
  296. /**
  297. * Returns whether the given tag belongs to an annotation.
  298. *
  299. * @todo this method should be populated once we implement Annotation notation support.
  300. */
  301. private function isAnnotation(string $tagContent) : bool
  302. {
  303. // 1. Contains a namespace separator
  304. // 2. Contains parenthesis
  305. // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part
  306. // of the annotation class name matches the found tag name
  307. return false;
  308. }
  309. }