[ Index ]

PHP Cross Reference of phpBB-3.3.7-deutsch

title

Body

[close]

/vendor/twig/twig/src/Node/Expression/ -> CallExpression.php (source)

   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  
  12  namespace Twig\Node\Expression;
  13  
  14  use Twig\Compiler;
  15  use Twig\Error\SyntaxError;
  16  use Twig\Extension\ExtensionInterface;
  17  use Twig\Node\Node;
  18  
  19  abstract class CallExpression extends AbstractExpression
  20  {
  21      private $reflector;
  22  
  23      protected function compileCallable(Compiler $compiler)
  24      {
  25          $callable = $this->getAttribute('callable');
  26  
  27          $closingParenthesis = false;
  28          $isArray = false;
  29          if (\is_string($callable) && false === strpos($callable, '::')) {
  30              $compiler->raw($callable);
  31          } else {
  32              list($r, $callable) = $this->reflectCallable($callable);
  33              if ($r instanceof \ReflectionMethod && \is_string($callable[0])) {
  34                  if ($r->isStatic()) {
  35                      $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
  36                  } else {
  37                      $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
  38                  }
  39              } elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) {
  40                  // For BC/FC with namespaced aliases
  41                  $class = (new \ReflectionClass(\get_class($callable[0])))->name;
  42                  if (!$compiler->getEnvironment()->hasExtension($class)) {
  43                      // Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error
  44                      $compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class));
  45                  } else {
  46                      $compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\')));
  47                  }
  48  
  49                  $compiler->raw(sprintf('->%s', $callable[1]));
  50              } else {
  51                  $closingParenthesis = true;
  52                  $isArray = true;
  53                  $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
  54              }
  55          }
  56  
  57          $this->compileArguments($compiler, $isArray);
  58  
  59          if ($closingParenthesis) {
  60              $compiler->raw(')');
  61          }
  62      }
  63  
  64      protected function compileArguments(Compiler $compiler, $isArray = false)
  65      {
  66          $compiler->raw($isArray ? '[' : '(');
  67  
  68          $first = true;
  69  
  70          if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
  71              $compiler->raw('$this->env');
  72              $first = false;
  73          }
  74  
  75          if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
  76              if (!$first) {
  77                  $compiler->raw(', ');
  78              }
  79              $compiler->raw('$context');
  80              $first = false;
  81          }
  82  
  83          if ($this->hasAttribute('arguments')) {
  84              foreach ($this->getAttribute('arguments') as $argument) {
  85                  if (!$first) {
  86                      $compiler->raw(', ');
  87                  }
  88                  $compiler->string($argument);
  89                  $first = false;
  90              }
  91          }
  92  
  93          if ($this->hasNode('node')) {
  94              if (!$first) {
  95                  $compiler->raw(', ');
  96              }
  97              $compiler->subcompile($this->getNode('node'));
  98              $first = false;
  99          }
 100  
 101          if ($this->hasNode('arguments')) {
 102              $callable = $this->getAttribute('callable');
 103              $arguments = $this->getArguments($callable, $this->getNode('arguments'));
 104              foreach ($arguments as $node) {
 105                  if (!$first) {
 106                      $compiler->raw(', ');
 107                  }
 108                  $compiler->subcompile($node);
 109                  $first = false;
 110              }
 111          }
 112  
 113          $compiler->raw($isArray ? ']' : ')');
 114      }
 115  
 116      protected function getArguments($callable, $arguments)
 117      {
 118          $callType = $this->getAttribute('type');
 119          $callName = $this->getAttribute('name');
 120  
 121          $parameters = [];
 122          $named = false;
 123          foreach ($arguments as $name => $node) {
 124              if (!\is_int($name)) {
 125                  $named = true;
 126                  $name = $this->normalizeName($name);
 127              } elseif ($named) {
 128                  throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
 129              }
 130  
 131              $parameters[$name] = $node;
 132          }
 133  
 134          $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic');
 135          if (!$named && !$isVariadic) {
 136              return $parameters;
 137          }
 138  
 139          if (!$callable) {
 140              if ($named) {
 141                  $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName);
 142              } else {
 143                  $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName);
 144              }
 145  
 146              throw new \LogicException($message);
 147          }
 148  
 149          list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic);
 150          $arguments = [];
 151          $names = [];
 152          $missingArguments = [];
 153          $optionalArguments = [];
 154          $pos = 0;
 155          foreach ($callableParameters as $callableParameter) {
 156              $name = $this->normalizeName($callableParameter->name);
 157              if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
 158                  if ('start' === $name) {
 159                      $name = 'low';
 160                  } elseif ('end' === $name) {
 161                      $name = 'high';
 162                  }
 163              }
 164  
 165              $names[] = $name;
 166  
 167              if (\array_key_exists($name, $parameters)) {
 168                  if (\array_key_exists($pos, $parameters)) {
 169                      throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
 170                  }
 171  
 172                  if (\count($missingArguments)) {
 173                      throw new SyntaxError(sprintf(
 174                          'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".',
 175                          $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments)
 176                      ), $this->getTemplateLine(), $this->getSourceContext());
 177                  }
 178  
 179                  $arguments = array_merge($arguments, $optionalArguments);
 180                  $arguments[] = $parameters[$name];
 181                  unset($parameters[$name]);
 182                  $optionalArguments = [];
 183              } elseif (\array_key_exists($pos, $parameters)) {
 184                  $arguments = array_merge($arguments, $optionalArguments);
 185                  $arguments[] = $parameters[$pos];
 186                  unset($parameters[$pos]);
 187                  $optionalArguments = [];
 188                  ++$pos;
 189              } elseif ($callableParameter->isDefaultValueAvailable()) {
 190                  $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1);
 191              } elseif ($callableParameter->isOptional()) {
 192                  if (empty($parameters)) {
 193                      break;
 194                  } else {
 195                      $missingArguments[] = $name;
 196                  }
 197              } else {
 198                  throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
 199              }
 200          }
 201  
 202          if ($isVariadic) {
 203              $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);
 204              foreach ($parameters as $key => $value) {
 205                  if (\is_int($key)) {
 206                      $arbitraryArguments->addElement($value);
 207                  } else {
 208                      $arbitraryArguments->addElement($value, new ConstantExpression($key, -1));
 209                  }
 210                  unset($parameters[$key]);
 211              }
 212  
 213              if ($arbitraryArguments->count()) {
 214                  $arguments = array_merge($arguments, $optionalArguments);
 215                  $arguments[] = $arbitraryArguments;
 216              }
 217          }
 218  
 219          if (!empty($parameters)) {
 220              $unknownParameter = null;
 221              foreach ($parameters as $parameter) {
 222                  if ($parameter instanceof Node) {
 223                      $unknownParameter = $parameter;
 224                      break;
 225                  }
 226              }
 227  
 228              throw new SyntaxError(
 229                  sprintf(
 230                      'Unknown argument%s "%s" for %s "%s(%s)".',
 231                      \count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names)
 232                  ),
 233                  $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(),
 234                  $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext()
 235              );
 236          }
 237  
 238          return $arguments;
 239      }
 240  
 241      protected function normalizeName($name)
 242      {
 243          return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name));
 244      }
 245  
 246      private function getCallableParameters($callable, bool $isVariadic): array
 247      {
 248          list($r) = $this->reflectCallable($callable);
 249          if (null === $r) {
 250              return [[], false];
 251          }
 252  
 253          $parameters = $r->getParameters();
 254          if ($this->hasNode('node')) {
 255              array_shift($parameters);
 256          }
 257          if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
 258              array_shift($parameters);
 259          }
 260          if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
 261              array_shift($parameters);
 262          }
 263          if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
 264              foreach ($this->getAttribute('arguments') as $argument) {
 265                  array_shift($parameters);
 266              }
 267          }
 268          $isPhpVariadic = false;
 269          if ($isVariadic) {
 270              $argument = end($parameters);
 271              $isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName();
 272              if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
 273                  array_pop($parameters);
 274              } elseif ($argument && $argument->isVariadic()) {
 275                  array_pop($parameters);
 276                  $isPhpVariadic = true;
 277              } else {
 278                  $callableName = $r->name;
 279                  if ($r instanceof \ReflectionMethod) {
 280                      $callableName = $r->getDeclaringClass()->name.'::'.$callableName;
 281                  }
 282  
 283                  throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name')));
 284              }
 285          }
 286  
 287          return [$parameters, $isPhpVariadic];
 288      }
 289  
 290      private function reflectCallable($callable)
 291      {
 292          if (null !== $this->reflector) {
 293              return $this->reflector;
 294          }
 295  
 296          if (\is_array($callable)) {
 297              if (!method_exists($callable[0], $callable[1])) {
 298                  // __call()
 299                  return [null, []];
 300              }
 301              $r = new \ReflectionMethod($callable[0], $callable[1]);
 302          } elseif (\is_object($callable) && !$callable instanceof \Closure) {
 303              $r = new \ReflectionObject($callable);
 304              $r = $r->getMethod('__invoke');
 305              $callable = [$callable, '__invoke'];
 306          } elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
 307              $class = substr($callable, 0, $pos);
 308              $method = substr($callable, $pos + 2);
 309              if (!method_exists($class, $method)) {
 310                  // __staticCall()
 311                  return [null, []];
 312              }
 313              $r = new \ReflectionMethod($callable);
 314              $callable = [$class, $method];
 315          } else {
 316              $r = new \ReflectionFunction($callable);
 317          }
 318  
 319          return $this->reflector = [$r, $callable];
 320      }
 321  }
 322  
 323  class_alias('Twig\Node\Expression\CallExpression', 'Twig_Node_Expression_Call');


Generated: Thu Mar 24 21:31:15 2022 Cross-referenced by PHPXref 0.7.1