Completed
Push — master ( 9ad54b...895d73 )
by Federico
03:21
created

MixinVisitor   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 280
rs 8.439
wmc 47
lcom 1
cbo 2

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getMixinArgumentAssign() 0 8 2
B parseMixinArguments() 0 30 6
A parseMixinStringAttribute() 0 12 4
B parseMixinAttribute() 0 12 5
A parseMixinAttributes() 0 16 3
B renderClosureOpenning() 0 22 4
B renderClosureClosing() 0 25 4
C visitMixinCall() 0 77 7
A visitMixinCodeAndBlock() 0 13 3
B visitMixinDeclaration() 0 26 6
A visitMixin() 0 17 3

How to fix   Complexity   

Complex Class

Complex classes like MixinVisitor 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 MixinVisitor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Jade\Compiler;
4
5
use Jade\Nodes\Mixin;
6
7
abstract class MixinVisitor extends CodeVisitor
8
{
9
    protected function getMixinArgumentAssign($argument)
10
    {
11
        $argument = trim($argument);
12
13
        if (preg_match('`^[a-zA-Z][a-zA-Z0-9:_-]*\s*=`', $argument)) {
14
            return explode('=', $argument, 2);
15
        }
16
    }
17
18
    protected function parseMixinArguments(&$arguments, &$containsOnlyArrays, &$defaultAttributes)
19
    {
20
        $newArrayKey = null;
21
        $arguments = is_null($arguments) ? array() : explode(',', $arguments);
22
        foreach ($arguments as $key => &$argument) {
23
            if ($tab = $this->getMixinArgumentAssign($argument)) {
24
                if (is_null($newArrayKey)) {
25
                    $newArrayKey = $key;
26
                    $argument = array();
27
                } else {
28
                    unset($arguments[$key]);
29
                }
30
31
                $defaultAttributes[] = var_export($tab[0], true) . ' => ' . $tab[1];
32
                $arguments[$newArrayKey][$tab[0]] = static::decodeValue($tab[1]);
33
                continue;
34
            }
35
36
            $containsOnlyArrays = false;
37
            $newArrayKey = null;
38
        }
39
40
        return array_map(function ($argument) {
41
            if (is_array($argument)) {
42
                $argument = var_export($argument, true);
43
            }
44
45
            return $argument;
46
        }, $arguments);
47
    }
48
49
    protected function parseMixinStringAttribute($data)
50
    {
51
        $value = is_array($data['value'])
52
            ? preg_split('`\s+`', trim(implode(' ', $data['value'])))
53
            : trim($data['value']);
54
55
        return $data['escaped'] === true
56
            ? is_array($value)
57
                ? array_map('htmlspecialchars', $value)
58
                : htmlspecialchars($value)
59
            : $value;
60
    }
61
62
    protected function parseMixinAttribute($data)
63
    {
64
        if ($data['value'] === 'null' || $data['value'] === 'undefined' || is_null($data['value'])) {
65
            return;
66
        }
67
68
        if (is_bool($data['value'])) {
69
            return $data['value'];
70
        }
71
72
        return $this->parseMixinStringAttribute($data);
73
    }
74
75
    protected function parseMixinAttributes($attributes, $defaultAttributes, $mixinAttributes)
76
    {
77
        if (!count($attributes)) {
78
            return "(isset(\$attributes)) ? \$attributes : array($defaultAttributes)";
79
        }
80
81
        $parsedAttributes = array();
82
        foreach ($attributes as $data) {
83
            $parsedAttributes[$data['name']] = $this->parseMixinAttribute($data);
84
        }
85
86
        $attributes = var_export($parsedAttributes, true);
87
        $mixinAttributes = var_export(static::decodeAttributes($mixinAttributes), true);
88
89
        return "array_merge(\\Jade\\Compiler::withMixinAttributes($attributes, $mixinAttributes), (isset(\$attributes)) ? \$attributes : array($defaultAttributes))";
90
    }
91
92
    protected function renderClosureOpenning()
93
    {
94
        $arguments = func_get_args();
95
        $begin = array_shift($arguments);
96
        $begin = is_array($begin)
97
            ? $begin[0] . 'function ' . $begin[1]
98
            : $begin . 'function ';
99
        $params = implode(', ', array_map(function ($name) {
100
            return (substr($name, 0, 1) === '$' ? '' : '$') . $name;
101
        }, $arguments));
102
103
        if ($this->restrictedScope) {
0 ignored issues
show
Bug introduced by
The property restrictedScope does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
104
            return $this->buffer($this->createCode($begin . '(' . $params . ') {'));
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
105
        }
106
107
        $params = '&$__varHandler, ' . $params;
108
109
        $this->buffer(
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
110
            $this->createCode($begin . '(' . $params . ') {') .
111
            $this->createCode($this->indent() . 'extract($__varHandler, EXTR_SKIP);')
112
        );
113
    }
114
115
    protected function renderClosureClosing($code, $arguments = array())
116
    {
117
        if (!$this->restrictedScope) {
118
            $arguments = array_filter(array_map(function ($argument) {
119
                $argument = explode('=', $argument);
120
                $argument = trim($argument[0]);
121
122
                return substr($argument, 0, 1) === '$'
123
                    ? substr($argument, 1)
124
                    : false;
125
            }, array_slice($arguments, 1)));
126
            $exception = count($arguments)
127
                ? ' && !in_array($key, ' . var_export($arguments, true) . ')'
128
                : '';
129
            $this->buffer($this->createCode(
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
130
                'foreach ($__varHandler as $key => &$val) {' .
131
                'if ($key !== \'__varHandler\'' . $exception . ') {' .
132
                '$val = ${$key};' .
133
                '}' .
134
                '}'
135
            ));
136
        }
137
138
        $this->buffer($this->createCode($code));
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
139
    }
140
141
    /**
142
     * @param Nodes\Mixin $mixin
143
     */
144
    protected function visitMixinCall(Mixin $mixin, $name, $blockName, $attributes)
145
    {
146
        $arguments = $mixin->arguments;
0 ignored issues
show
Unused Code introduced by
$arguments is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
147
        $block = $mixin->block;
148
        $defaultAttributes = array();
149
        $containsOnlyArrays = true;
150
        $arguments = $this->parseMixinArguments($mixin->arguments, $containsOnlyArrays, $defaultAttributes);
151
152
        $defaultAttributes = implode(', ', $defaultAttributes);
153
        $attributes = $this->parseMixinAttributes($attributes, $defaultAttributes, $mixin->attributes);
154
155
        if ($block) {
156
            $this->renderClosureOpenning("\\Jade\\Compiler::recordMixinBlock($blockName, ", 'attributes');
157
            $this->visit($block);
158
            $this->renderClosureClosing('});');
159
        }
160
161
        $strings = array();
162
        $arguments = preg_replace_callback(
163
            '#([\'"])(.*(?!<\\\\)(?:\\\\{2})*)\\1#U',
164
            function ($match) use (&$strings) {
165
                $nextIndex = count($strings);
166
                $strings[] = $match[0];
167
168
                return 'stringToReplaceBy' . $nextIndex . 'ThCapture';
169
            },
170
            $arguments
171
        );
172
        $arguments = array_map(
173
            function ($arg) use ($strings) {
174
                return preg_replace_callback(
175
                    '#stringToReplaceBy([0-9]+)ThCapture#',
176
                    function ($match) use ($strings) {
177
                        return $strings[intval($match[1])];
178
                    },
179
                    $arg
180
                );
181
            },
182
            $arguments
183
        );
184
185
        array_unshift($arguments, $attributes);
186
        $arguments = array_filter($arguments, 'strlen');
187
        $statements = $this->apply('createStatements', $arguments);
188
189
        $variables = array_pop($statements);
190
        if ($mixin->call && $containsOnlyArrays) {
191
            array_splice($variables, 1, 0, array('null'));
192
        }
193
        $variables = implode(', ', $variables);
194
        array_push($statements, $variables);
195
196
        $arguments = $statements;
197
198
        $paramsPrefix = '';
199
        if (!$this->restrictedScope) {
200
            $this->buffer($this->createCode('$__varHandler = get_defined_vars();'));
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
201
            $paramsPrefix = '$__varHandler, ';
202
        }
203
        $codeFormat = str_repeat('%s;', count($arguments) - 1) . "{$name}({$paramsPrefix}%s)";
204
205
        array_unshift($arguments, $codeFormat);
206
207
        $this->buffer($this->apply('createCode', $arguments));
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
208
        if (!$this->restrictedScope) {
209
            $this->buffer(
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
210
                $this->createCode(
211
                    'extract(array_diff_key($__varHandler, array(\'__varHandler\' => 1, \'attributes\' => 1)));'
212
                )
213
            );
214
        }
215
216
        if ($block) {
217
            $code = $this->createCode("\\Jade\\Compiler::terminateMixinBlock($blockName);");
218
            $this->buffer($code);
0 ignored issues
show
Bug introduced by
The method buffer() does not exist on Jade\Compiler\MixinVisitor. Did you maybe mean bufferCustomKeyword()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
219
        }
220
    }
221
222
    protected function visitMixinCodeAndBlock($name, $block, $arguments)
223
    {
224
        $this->renderClosureOpenning(
225
            $this->allowMixinOverride
0 ignored issues
show
Bug introduced by
The property allowMixinOverride does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
226
                ? "{$name} = "
227
                : array("if(!function_exists('{$name}')) { ", $name),
228
            implode(',', $arguments)
229
        );
230
        $this->indents++;
231
        $this->visit($block);
232
        $this->indents--;
233
        $this->renderClosureClosing($this->allowMixinOverride ? '};' : '} }', $arguments);
234
    }
235
236
    /**
237
     * @param Nodes\Mixin $mixin
238
     */
239
    protected function visitMixinDeclaration(Mixin $mixin, $name)
240
    {
241
        $arguments = $mixin->arguments;
242
        $block = $mixin->block;
243
        $previousVisitedMixin = isset($this->visitedMixin) ? $this->visitedMixin : null;
0 ignored issues
show
Bug introduced by
The property visitedMixin does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
244
        $this->visitedMixin = $mixin;
245
        if ($arguments === null || empty($arguments)) {
246
            $arguments = array();
247
        } elseif (!is_array($arguments)) {
248
            $arguments = array($arguments);
249
        }
250
251
        array_unshift($arguments, 'attributes');
252
        $arguments = implode(',', $arguments);
253
        $arguments = explode(',', $arguments);
254
        array_walk($arguments, array(get_class(), 'initArgToNull'));
255
        $this->visitMixinCodeAndBlock($name, $block, $arguments);
256
257
        if (is_null($previousVisitedMixin)) {
258
            unset($this->visitedMixin);
259
260
            return;
261
        }
262
263
        $this->visitedMixin = $previousVisitedMixin;
264
    }
265
266
    /**
267
     * @param Nodes\Mixin $mixin
268
     */
269
    protected function visitMixin(Mixin $mixin)
270
    {
271
        $name = strtr($mixin->name, '-', '_') . '_mixin';
272
        $blockName = var_export($mixin->name, true);
273
        if ($this->allowMixinOverride) {
274
            $name = '$GLOBALS[\'' . $name . '\']';
275
        }
276
        $attributes = static::decodeAttributes($mixin->attributes);
277
278
        if ($mixin->call) {
279
            $this->visitMixinCall($mixin, $name, $blockName, $attributes);
280
281
            return;
282
        }
283
284
        $this->visitMixinDeclaration($mixin, $name);
285
    }
286
}
287