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