ProxyCallVisitor.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Di\Aop;
  12. use Hyperf\Support\Composer;
  13. use PhpParser\Node;
  14. use PhpParser\Node\Arg;
  15. use PhpParser\Node\Expr\Array_;
  16. use PhpParser\Node\Expr\ArrayItem;
  17. use PhpParser\Node\Expr\Assign;
  18. use PhpParser\Node\Expr\Closure;
  19. use PhpParser\Node\Expr\FuncCall;
  20. use PhpParser\Node\Expr\StaticCall;
  21. use PhpParser\Node\Expr\Variable;
  22. use PhpParser\Node\Identifier;
  23. use PhpParser\Node\Name;
  24. use PhpParser\Node\Scalar\MagicConst\Class_ as MagicConstClass;
  25. use PhpParser\Node\Scalar\MagicConst\Dir as MagicConstDir;
  26. use PhpParser\Node\Scalar\MagicConst\File as MagicConstFile;
  27. use PhpParser\Node\Scalar\MagicConst\Function_ as MagicConstFunction;
  28. use PhpParser\Node\Scalar\MagicConst\Method as MagicConstMethod;
  29. use PhpParser\Node\Scalar\MagicConst\Trait_ as MagicConstTrait;
  30. use PhpParser\Node\Scalar\String_;
  31. use PhpParser\Node\Stmt\Class_;
  32. use PhpParser\Node\Stmt\ClassMethod;
  33. use PhpParser\Node\Stmt\Enum_;
  34. use PhpParser\Node\Stmt\Expression;
  35. use PhpParser\Node\Stmt\Return_;
  36. use PhpParser\Node\Stmt\Trait_;
  37. use PhpParser\Node\Stmt\TraitUse;
  38. use PhpParser\NodeVisitorAbstract;
  39. class ProxyCallVisitor extends NodeVisitorAbstract
  40. {
  41. /**
  42. * Define the proxy handler trait here.
  43. */
  44. private array $proxyTraits = [
  45. ProxyTrait::class,
  46. ];
  47. private bool $shouldRewrite = false;
  48. public function __construct(protected VisitorMetadata $visitorMetadata)
  49. {
  50. }
  51. public function beforeTraverse(array $nodes)
  52. {
  53. foreach ($nodes as $namespace) {
  54. if ($namespace instanceof Node\Stmt\Declare_) {
  55. continue;
  56. }
  57. if (! $namespace instanceof Node\Stmt\Namespace_) {
  58. break;
  59. }
  60. foreach ($namespace->stmts as $class) {
  61. if ($class instanceof Node\Stmt\ClassLike) {
  62. $this->visitorMetadata->classLike = get_class($class);
  63. }
  64. }
  65. }
  66. return null;
  67. }
  68. public function enterNode(Node $node)
  69. {
  70. switch ($node) {
  71. case $node instanceof ClassMethod:
  72. $this->shouldRewrite = $this->shouldRewrite($node);
  73. break;
  74. }
  75. return null;
  76. }
  77. public function leaveNode(Node $node)
  78. {
  79. switch ($node) {
  80. case $node instanceof ClassMethod:
  81. if (! $this->shouldRewrite($node)) {
  82. return $node;
  83. }
  84. // Rewrite the method to proxy call method.
  85. return $this->rewriteMethod($node);
  86. case $node instanceof Trait_:
  87. // If the node is trait and php version >= 7.3, it can `use ProxyTrait` like class.
  88. case $node instanceof Enum_:
  89. // If the node is enum and php version >= 8.1, it can `use ProxyTrait` like class.
  90. case $node instanceof Class_ && ! $node->isAnonymous():
  91. // Add use proxy traits.
  92. $stmts = $node->stmts;
  93. if ($stmt = $this->buildProxyCallTraitUseStatement()) {
  94. array_unshift($stmts, $stmt);
  95. }
  96. $node->stmts = $stmts;
  97. unset($stmts);
  98. return $node;
  99. case $node instanceof MagicConstFunction:
  100. // Rewrite __FUNCTION__ to $__function__ variable.
  101. if ($this->shouldRewrite) {
  102. return new Variable('__function__');
  103. }
  104. break;
  105. case $node instanceof MagicConstMethod:
  106. // Rewrite __METHOD__ to $__method__ variable.
  107. if ($this->shouldRewrite) {
  108. return new Variable('__method__');
  109. }
  110. break;
  111. case $node instanceof MagicConstDir:
  112. // Rewrite __DIR__ as the real directory path
  113. if ($file = Composer::getLoader()->findFile($this->visitorMetadata->className)) {
  114. return new String_(dirname(realpath($file)));
  115. }
  116. break;
  117. case $node instanceof MagicConstFile:
  118. // Rewrite __FILE__ to the real file path
  119. if ($file = Composer::getLoader()->findFile($this->visitorMetadata->className)) {
  120. return new String_(realpath($file));
  121. }
  122. break;
  123. }
  124. return null;
  125. }
  126. /**
  127. * Build `use ProxyTrait;`.
  128. */
  129. private function buildProxyCallTraitUseStatement(): ?TraitUse
  130. {
  131. $traits = [];
  132. foreach ($this->proxyTraits as $proxyTrait) {
  133. if (! is_string($proxyTrait) || ! trait_exists($proxyTrait)) {
  134. continue;
  135. }
  136. // Add backslash prefix if the proxy trait does not start with backslash.
  137. $proxyTrait[0] !== '\\' && $proxyTrait = '\\' . $proxyTrait;
  138. $traits[] = new Name($proxyTrait);
  139. }
  140. if (empty($traits)) {
  141. return null;
  142. }
  143. return new TraitUse($traits);
  144. }
  145. /**
  146. * Rewrite a normal class method to a proxy call method,
  147. * include normal class method and static method.
  148. */
  149. private function rewriteMethod(ClassMethod $node): ClassMethod
  150. {
  151. // Build the static proxy call method base on the original method.
  152. $shouldReturn = true;
  153. $returnType = $node->getReturnType();
  154. if ($returnType instanceof Identifier && $returnType->name === 'void') {
  155. $shouldReturn = false;
  156. }
  157. $staticCall = new StaticCall(new Name('self'), '__proxyCall', [
  158. // __CLASS__
  159. new Arg($this->getMagicConst()),
  160. // __FUNCTION__
  161. new Arg(new MagicConstFunction()),
  162. // ['order' => ['param1', 'param2'], 'keys' => compact('param1', 'param2'), 'variadic' => 'param2']
  163. new Arg($this->getArguments($node->getParams())),
  164. // A closure that wrapped original method code.
  165. new Arg(new Closure([
  166. 'params' => $this->filterModifier($node->getParams()),
  167. 'uses' => [
  168. new Variable('__function__'),
  169. new Variable('__method__'),
  170. ],
  171. 'stmts' => $node->stmts,
  172. ])),
  173. ]);
  174. $stmts = $this->unshiftMagicMethods([]);
  175. if ($shouldReturn) {
  176. $stmts[] = new Return_($staticCall);
  177. } else {
  178. $stmts[] = new Expression($staticCall);
  179. }
  180. $node->stmts = $stmts;
  181. return $node;
  182. }
  183. /**
  184. * @param Node\Param[] $params
  185. * @return Node\Param[]
  186. */
  187. private function filterModifier(array $params): array
  188. {
  189. return array_map(function (Node\Param $param) {
  190. $tempParam = clone $param;
  191. $tempParam->flags &= ~Class_::VISIBILITY_MODIFIER_MASK & ~Class_::MODIFIER_READONLY;
  192. return $tempParam;
  193. }, $params);
  194. }
  195. /**
  196. * @param Node\Param[] $params
  197. */
  198. private function getArguments(array $params): Array_
  199. {
  200. if (empty($params)) {
  201. return new Array_([
  202. new ArrayItem(
  203. key: new String_('keys'),
  204. value: new Array_([], ['kind' => Array_::KIND_SHORT]),
  205. ),
  206. ], ['kind' => Array_::KIND_SHORT]);
  207. }
  208. // ['param1', 'param2', ...]
  209. $methodParamsList = new Array_(
  210. array_map(fn (Node\Param $param) => new ArrayItem(new String_($param->var->name)), $params),
  211. ['kind' => Array_::KIND_SHORT]
  212. );
  213. return new Array_([
  214. new ArrayItem(
  215. key: new String_('order'),
  216. value: $methodParamsList,
  217. ),
  218. new ArrayItem(
  219. key: new String_('keys'),
  220. value: new FuncCall(new Name('compact'), [new Arg($methodParamsList)])
  221. ),
  222. new ArrayItem(
  223. key: new String_('variadic'),
  224. value: $this->getVariadicParamName($params),
  225. )], ['kind' => Array_::KIND_SHORT]);
  226. }
  227. /**
  228. * @param Node\Param[] $params
  229. */
  230. private function getVariadicParamName(array $params): String_
  231. {
  232. foreach ($params as $param) {
  233. if ($param->variadic) {
  234. return new String_($param->var->name);
  235. }
  236. }
  237. return new String_('');
  238. }
  239. private function unshiftMagicMethods(array $stmts = []): array
  240. {
  241. $magicConstFunction = new Expression(new Assign(new Variable('__function__'), new MagicConstFunction()));
  242. $magicConstMethod = new Expression(new Assign(new Variable('__method__'), new MagicConstMethod()));
  243. array_unshift($stmts, $magicConstFunction, $magicConstMethod);
  244. return $stmts;
  245. }
  246. private function getMagicConst(): Node\Scalar\MagicConst
  247. {
  248. return match ($this->visitorMetadata->classLike) {
  249. Trait_::class => new MagicConstTrait(),
  250. default => new MagicConstClass(),
  251. };
  252. }
  253. private function shouldRewrite(ClassMethod $node): bool
  254. {
  255. if ($this->visitorMetadata->classLike == Node\Stmt\Interface_::class || $node->isAbstract()) {
  256. return false;
  257. }
  258. $rewriteCollection = Aspect::parse($this->visitorMetadata->className);
  259. return $rewriteCollection->shouldRewrite($node->name->toString());
  260. }
  261. }