| [ Index ] |
PHP Cross Reference of phpBB-3.3.14-deutsch |
[Summary view] [Print] [Text view]
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 if (\is_string($callable) && false === strpos($callable, '::')) { 28 $compiler->raw($callable); 29 } else { 30 [$r, $callable] = $this->reflectCallable($callable); 31 32 if (\is_string($callable)) { 33 $compiler->raw($callable); 34 } elseif (\is_array($callable) && \is_string($callable[0])) { 35 if (!$r instanceof \ReflectionMethod || $r->isStatic()) { 36 $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); 37 } else { 38 $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); 39 } 40 } elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) { 41 $class = \get_class($callable[0]); 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 $compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name'))); 52 } 53 } 54 55 $this->compileArguments($compiler); 56 } 57 58 protected function compileArguments(Compiler $compiler, $isArray = false) 59 { 60 $compiler->raw($isArray ? '[' : '('); 61 62 $first = true; 63 64 if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { 65 $compiler->raw('$this->env'); 66 $first = false; 67 } 68 69 if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { 70 if (!$first) { 71 $compiler->raw(', '); 72 } 73 $compiler->raw('$context'); 74 $first = false; 75 } 76 77 if ($this->hasAttribute('arguments')) { 78 foreach ($this->getAttribute('arguments') as $argument) { 79 if (!$first) { 80 $compiler->raw(', '); 81 } 82 $compiler->string($argument); 83 $first = false; 84 } 85 } 86 87 if ($this->hasNode('node')) { 88 if (!$first) { 89 $compiler->raw(', '); 90 } 91 $compiler->subcompile($this->getNode('node')); 92 $first = false; 93 } 94 95 if ($this->hasNode('arguments')) { 96 $callable = $this->getAttribute('callable'); 97 $arguments = $this->getArguments($callable, $this->getNode('arguments')); 98 foreach ($arguments as $node) { 99 if (!$first) { 100 $compiler->raw(', '); 101 } 102 $compiler->subcompile($node); 103 $first = false; 104 } 105 } 106 107 $compiler->raw($isArray ? ']' : ')'); 108 } 109 110 protected function getArguments($callable, $arguments) 111 { 112 $callType = $this->getAttribute('type'); 113 $callName = $this->getAttribute('name'); 114 115 $parameters = []; 116 $named = false; 117 foreach ($arguments as $name => $node) { 118 if (!\is_int($name)) { 119 $named = true; 120 $name = $this->normalizeName($name); 121 } elseif ($named) { 122 throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); 123 } 124 125 $parameters[$name] = $node; 126 } 127 128 $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic'); 129 if (!$named && !$isVariadic) { 130 return $parameters; 131 } 132 133 if (!$callable) { 134 if ($named) { 135 $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); 136 } else { 137 $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); 138 } 139 140 throw new \LogicException($message); 141 } 142 143 list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic); 144 $arguments = []; 145 $names = []; 146 $missingArguments = []; 147 $optionalArguments = []; 148 $pos = 0; 149 foreach ($callableParameters as $callableParameter) { 150 $name = $this->normalizeName($callableParameter->name); 151 if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { 152 if ('start' === $name) { 153 $name = 'low'; 154 } elseif ('end' === $name) { 155 $name = 'high'; 156 } 157 } 158 159 $names[] = $name; 160 161 if (\array_key_exists($name, $parameters)) { 162 if (\array_key_exists($pos, $parameters)) { 163 throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); 164 } 165 166 if (\count($missingArguments)) { 167 throw new SyntaxError(sprintf( 168 '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".', 169 $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) 170 ), $this->getTemplateLine(), $this->getSourceContext()); 171 } 172 173 $arguments = array_merge($arguments, $optionalArguments); 174 $arguments[] = $parameters[$name]; 175 unset($parameters[$name]); 176 $optionalArguments = []; 177 } elseif (\array_key_exists($pos, $parameters)) { 178 $arguments = array_merge($arguments, $optionalArguments); 179 $arguments[] = $parameters[$pos]; 180 unset($parameters[$pos]); 181 $optionalArguments = []; 182 ++$pos; 183 } elseif ($callableParameter->isDefaultValueAvailable()) { 184 $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1); 185 } elseif ($callableParameter->isOptional()) { 186 if (empty($parameters)) { 187 break; 188 } else { 189 $missingArguments[] = $name; 190 } 191 } else { 192 throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); 193 } 194 } 195 196 if ($isVariadic) { 197 $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1); 198 foreach ($parameters as $key => $value) { 199 if (\is_int($key)) { 200 $arbitraryArguments->addElement($value); 201 } else { 202 $arbitraryArguments->addElement($value, new ConstantExpression($key, -1)); 203 } 204 unset($parameters[$key]); 205 } 206 207 if ($arbitraryArguments->count()) { 208 $arguments = array_merge($arguments, $optionalArguments); 209 $arguments[] = $arbitraryArguments; 210 } 211 } 212 213 if (!empty($parameters)) { 214 $unknownParameter = null; 215 foreach ($parameters as $parameter) { 216 if ($parameter instanceof Node) { 217 $unknownParameter = $parameter; 218 break; 219 } 220 } 221 222 throw new SyntaxError( 223 sprintf( 224 'Unknown argument%s "%s" for %s "%s(%s)".', 225 \count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) 226 ), 227 $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(), 228 $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext() 229 ); 230 } 231 232 return $arguments; 233 } 234 235 protected function normalizeName($name) 236 { 237 return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); 238 } 239 240 private function getCallableParameters($callable, bool $isVariadic): array 241 { 242 [$r, , $callableName] = $this->reflectCallable($callable); 243 244 $parameters = $r->getParameters(); 245 if ($this->hasNode('node')) { 246 array_shift($parameters); 247 } 248 if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { 249 array_shift($parameters); 250 } 251 if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { 252 array_shift($parameters); 253 } 254 if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { 255 foreach ($this->getAttribute('arguments') as $argument) { 256 array_shift($parameters); 257 } 258 } 259 $isPhpVariadic = false; 260 if ($isVariadic) { 261 $argument = end($parameters); 262 $isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName(); 263 if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { 264 array_pop($parameters); 265 } elseif ($argument && $argument->isVariadic()) { 266 array_pop($parameters); 267 $isPhpVariadic = true; 268 } else { 269 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'))); 270 } 271 } 272 273 return [$parameters, $isPhpVariadic]; 274 } 275 276 private function reflectCallable($callable) 277 { 278 if (null !== $this->reflector) { 279 return $this->reflector; 280 } 281 282 if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { 283 $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; 284 } 285 286 if (\is_array($callable) && method_exists($callable[0], $callable[1])) { 287 $r = new \ReflectionMethod($callable[0], $callable[1]); 288 289 return $this->reflector = [$r, $callable, $r->class.'::'.$r->name]; 290 } 291 292 $checkVisibility = $callable instanceof \Closure; 293 try { 294 $closure = \Closure::fromCallable($callable); 295 } catch (\TypeError $e) { 296 throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e); 297 } 298 $r = new \ReflectionFunction($closure); 299 300 if (false !== strpos($r->name, '{closure}')) { 301 return $this->reflector = [$r, $callable, 'Closure']; 302 } 303 304 if ($object = $r->getClosureThis()) { 305 $callable = [$object, $r->name]; 306 $callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name; 307 } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { 308 $callableName = $class->name.'::'.$r->name; 309 } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { 310 $callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; 311 } else { 312 $callable = $callableName = $r->name; 313 } 314 315 if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { 316 $callable = $r->getClosure(); 317 } 318 319 return $this->reflector = [$r, $callable, $callableName]; 320 } 321 } 322 323 class_alias('Twig\Node\Expression\CallExpression', 'Twig_Node_Expression_Call');
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Nov 25 19:05:08 2024 | Cross-referenced by PHPXref 0.7.1 |