1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Twig\Util; 13 14use Twig\TwigCallableInterface; 15 16/** 17 * @author Fabien Potencier <fabien@symfony.com> 18 * 19 * @internal 20 */ 21final class ReflectionCallable 22{ 23 private $reflector; 24 private $callable; 25 private $name; 26 27 public function __construct( 28 TwigCallableInterface $twigCallable, 29 ) { 30 $callable = $twigCallable->getCallable(); 31 if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { 32 $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; 33 } 34 35 if (\is_array($callable) && method_exists($callable[0], $callable[1])) { 36 $this->reflector = $r = new \ReflectionMethod($callable[0], $callable[1]); 37 $this->callable = $callable; 38 $this->name = $r->class.'::'.$r->name; 39 40 return; 41 } 42 43 $checkVisibility = $callable instanceof \Closure; 44 try { 45 $closure = \Closure::fromCallable($callable); 46 } catch (\TypeError $e) { 47 throw new \LogicException(\sprintf('Callback for %s "%s" is not callable in the current scope.', $twigCallable->getType(), $twigCallable->getName()), 0, $e); 48 } 49 $this->reflector = $r = new \ReflectionFunction($closure); 50 51 if (str_contains($r->name, '{closure')) { 52 $this->callable = $callable; 53 $this->name = 'Closure'; 54 55 return; 56 } 57 58 if ($object = $r->getClosureThis()) { 59 $callable = [$object, $r->name]; 60 $this->name = get_debug_type($object).'::'.$r->name; 61 } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { 62 $callable = [$class->name, $r->name]; 63 $this->name = $class->name.'::'.$r->name; 64 } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { 65 $callable = [\is_array($callable) ? $callable[0] : $class->name, $r->name]; 66 $this->name = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; 67 } else { 68 $callable = $this->name = $r->name; 69 } 70 71 if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { 72 $callable = $r->getClosure(); 73 } 74 75 $this->callable = $callable; 76 } 77 78 public function getReflector(): \ReflectionFunctionAbstract 79 { 80 return $this->reflector; 81 } 82 83 /** 84 * @return callable 85 */ 86 public function getCallable() 87 { 88 return $this->callable; 89 } 90 91 public function getName(): string 92 { 93 return $this->name; 94 } 95} 96