Twig_Node_Expression_Call   C
last analyzed

Complexity

Total Complexity 55

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5
Metric Value
wmc 55
lcom 1
cbo 5
dl 0
loc 198
rs 6.8

4 Methods

Rating   Name   Duplication   Size   Complexity  
F getArguments() 0 112 31
A normalizeName() 0 4 1
C compileCallable() 0 23 7
C compileArguments() 0 53 16

How to fix   Complexity   

Complex Class

Complex classes like Twig_Node_Expression_Call often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Twig_Node_Expression_Call, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of Twig.
5
 *
6
 * (c) 2012 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
    protected function compileCallable(Twig_Compiler $compiler)
14
    {
15
        $closingParenthesis = false;
16
        if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) {
17
            if (is_string($callable)) {
18
                $compiler->raw($callable);
19
            } elseif (is_array($callable) && $callable[0] instanceof Twig_ExtensionInterface) {
20
                $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', $callable[0]->getName(), $callable[1]));
21
            } else {
22
                $type = ucfirst($this->getAttribute('type'));
23
                $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name')));
24
                $closingParenthesis = true;
25
            }
26
        } else {
27
            $compiler->raw($this->getAttribute('thing')->compile());
28
        }
29
30
        $this->compileArguments($compiler);
31
32
        if ($closingParenthesis) {
33
            $compiler->raw(')');
34
        }
35
    }
36
37
    protected function compileArguments(Twig_Compiler $compiler)
38
    {
39
        $compiler->raw('(');
40
41
        $first = true;
42
43
        if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
44
            $compiler->raw('$this->env');
45
            $first = false;
46
        }
47
48
        if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
49
            if (!$first) {
50
                $compiler->raw(', ');
51
            }
52
            $compiler->raw('$context');
53
            $first = false;
54
        }
55
56
        if ($this->hasAttribute('arguments')) {
57
            foreach ($this->getAttribute('arguments') as $argument) {
58
                if (!$first) {
59
                    $compiler->raw(', ');
60
                }
61
                $compiler->string($argument);
62
                $first = false;
63
            }
64
        }
65
66
        if ($this->hasNode('node')) {
67
            if (!$first) {
68
                $compiler->raw(', ');
69
            }
70
            $compiler->subcompile($this->getNode('node'));
71
            $first = false;
72
        }
73
74
        if ($this->hasNode('arguments') && null !== $this->getNode('arguments')) {
75
            $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null;
76
77
            $arguments = $this->getArguments($callable, $this->getNode('arguments'));
78
79
            foreach ($arguments as $node) {
80
                if (!$first) {
81
                    $compiler->raw(', ');
82
                }
83
                $compiler->subcompile($node);
84
                $first = false;
85
            }
86
        }
87
88
        $compiler->raw(')');
89
    }
90
91
    protected function getArguments($callable, $arguments)
92
    {
93
        $callType = $this->getAttribute('type');
94
        $callName = $this->getAttribute('name');
95
96
        $parameters = array();
97
        $named = false;
98
        foreach ($arguments as $name => $node) {
99
            if (!is_int($name)) {
100
                $named = true;
101
                $name = $this->normalizeName($name);
102
            } elseif ($named) {
103
                throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName));
104
            }
105
106
            $parameters[$name] = $node;
107
        }
108
109
        if (!$named) {
110
            return $parameters;
111
        }
112
113
        if (!$callable) {
114
            throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $callType, $callName));
115
        }
116
117
        // manage named arguments
118
        if (is_array($callable)) {
119
            $r = new ReflectionMethod($callable[0], $callable[1]);
120
        } elseif (is_object($callable) && !$callable instanceof Closure) {
121
            $r = new ReflectionObject($callable);
122
            $r = $r->getMethod('__invoke');
123
        } else {
124
            $r = new ReflectionFunction($callable);
125
        }
126
127
        $definition = $r->getParameters();
128
        if ($this->hasNode('node')) {
129
            array_shift($definition);
130
        }
131
        if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
132
            array_shift($definition);
133
        }
134
        if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
135
            array_shift($definition);
136
        }
137
        if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
138
            foreach ($this->getAttribute('arguments') as $argument) {
139
                array_shift($definition);
140
            }
141
        }
142
143
        $arguments = array();
144
        $names = array();
145
        $missingArguments = array();
146
        $optionalArguments = array();
147
        $pos = 0;
148
        foreach ($definition as $param) {
149
            $names[] = $name = $this->normalizeName($param->name);
150
151
            if (array_key_exists($name, $parameters)) {
152
                if (array_key_exists($pos, $parameters)) {
153
                    throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName));
154
                }
155
156
                if (!empty($missingArguments)) {
157
                    throw new Twig_Error_Syntax(sprintf(
158
                        '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".',
159
                        $name, $callType, $callName, implode(', ', $names), count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments))
160
                    );
161
                }
162
163
                $arguments = array_merge($arguments, $optionalArguments);
164
                $arguments[] = $parameters[$name];
165
                unset($parameters[$name]);
166
                $optionalArguments = array();
167
            } elseif (array_key_exists($pos, $parameters)) {
168
                $arguments = array_merge($arguments, $optionalArguments);
169
                $arguments[] = $parameters[$pos];
170
                unset($parameters[$pos]);
171
                $optionalArguments = array();
172
                ++$pos;
173
            } elseif ($param->isDefaultValueAvailable()) {
174
                $optionalArguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1);
175
            } elseif ($param->isOptional()) {
176
                if (empty($parameters)) {
177
                    break;
178
                } else {
179
                    $missingArguments[] = $name;
180
                }
181
            } else {
182
                throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName));
183
            }
184
        }
185
186
        if (!empty($parameters)) {
187
            $unknownParameter = null;
188
            foreach ($parameters as $parameter) {
189
                if ($parameter instanceof Twig_Node) {
190
                    $unknownParameter = $parameter;
191
                    break;
192
                }
193
            }
194
195
            throw new Twig_Error_Syntax(sprintf(
196
                'Unknown argument%s "%s" for %s "%s(%s)".',
197
                count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names)
198
            ), $unknownParameter ? $unknownParameter->getLine() : -1);
199
        }
200
201
        return $arguments;
202
    }
203
204
    protected function normalizeName($name)
205
    {
206
        return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name));
207
    }
208
}
209