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

CodeHandler   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 240
rs 8.4864
wmc 48
lcom 1
cbo 3

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 3
A innerCode() 0 6 1
B parse() 0 38 5
A isQuotedString() 0 7 2
A getVarname() 0 9 4
A parseArrayString() 0 10 4
A parseArrayAssign() 0 17 4
B parseArrayElement() 0 17 7
A parseArray() 0 19 4
A parseEqual() 0 13 2
D parseSeparator() 0 45 9
B parseBetweenSeparators() 0 27 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Jade\Compiler;
4
5
/**
6
 * Class Jade\Compiler\CodeHandler.
7
 */
8
class CodeHandler extends CompilerUtils
9
{
10
    protected $input;
11
    protected $name;
12
    protected $separators;
13
14
    public function __construct($input, $name)
15
    {
16
        if (!is_string($input)) {
17
            throw new \InvalidArgumentException('Expecting a string of PHP, got: ' . gettype($input), 11);
18
        }
19
20
        if (strlen($input) === 0) {
21
            throw new \InvalidArgumentException('Expecting a string of PHP, empty string received.', 12);
22
        }
23
24
        $this->input = trim(preg_replace('/\bvar\b/', '', $input));
25
        $this->name = $name;
26
        $this->separators = array();
27
    }
28
29
    public function innerCode($input, $name)
30
    {
31
        $handler = new static($input, $name);
32
33
        return $handler->parse();
34
    }
35
36
    public function parse()
37
    {
38
        if ($this->isQuotedString()) {
39
            return array($this->input);
40
        }
41
42
        if (strpos('=,;?', substr($this->input, 0, 1)) !== false) {
43
            throw new \ErrorException('Expecting a variable name or an expression, got: ' . $this->input, 13);
44
        }
45
46
        preg_match_all(
47
            '/(?<![<>=!])=(?!>|=)|[\[\]\{\}\(\),;\.]|(?!:):|->/', // punctuation
48
            preg_replace_callback('/[a-zA-Z0-9\\\\_\\x7f-\\xff]*\((?:[0-9\/%\.\s*+-]++|(?R))*+\)/', function ($match) {
49
                // no need to keep separators in simple PHP expressions (functions calls, parentheses, calculs)
50
                return str_repeat(' ', strlen($match[0]));
51
            }, preg_replace_callback('/([\'"]).*?(?<!\\\\)(?:\\\\{2})*\\1/', function ($match) {
52
                // do not take separators in strings
53
                return str_repeat(' ', strlen($match[0]));
54
            }, $this->input)),
55
            $separators,
56
            PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE
57
        );
58
59
        $this->separators = $separators[0];
60
61
        if (count($this->separators) === 0) {
62
            if (strstr('0123456789-+("\'$', substr($this->input, 0, 1)) === false) {
63
                $this->input = static::addDollarIfNeeded($this->input);
64
            }
65
66
            return array($this->input);
67
        }
68
69
        // add a pseudo separator for the end of the input
70
        array_push($this->separators, array(null, strlen($this->input)));
71
72
        return $this->parseBetweenSeparators();
73
    }
74
75
    protected function isQuotedString()
76
    {
77
        $firstChar = substr($this->input, 0, 1);
78
        $lastChar = substr($this->input, -1);
79
80
        return false !== strpos('"\'', $firstChar) && $lastChar === $firstChar;
81
    }
82
83
    protected function getVarname($separator)
84
    {
85
        // do not add $ if it is not like a variable
86
        $varname = static::convertVarPath(substr($this->input, 0, $separator[1]), '/^%s/');
87
88
        return $separator[0] !== '(' && $varname !== '' && strstr('0123456789-+("\'$', substr($varname, 0, 1)) === false
89
            ? static::addDollarIfNeeded($varname)
90
            : $varname;
91
    }
92
93
    protected function parseArrayString(&$argument, $match, $consume, &$quote, &$key, &$value)
94
    {
95
        $quote = $quote
96
            ? CommonUtils::escapedEnd($match[1])
97
                ? $quote
98
                : null
99
            : $match[2];
100
        ${is_null($value) ? 'key' : 'value'} .= $match[0];
101
        $consume($argument, $match[0]);
102
    }
103
104
    protected function parseArrayAssign(&$argument, $match, $consume, &$quote, &$key, &$value)
105
    {
106
        if ($quote) {
107
            ${is_null($value) ? 'key' : 'value'} .= $match[0];
108
            $consume($argument, $match[0]);
109
110
            return;
111
        }
112
113
        if (!is_null($value)) {
114
            throw new \ErrorException('Parse error on ' . substr($argument, strlen($match[1])), 15);
115
        }
116
117
        $key .= $match[1];
118
        $value = '';
119
        $consume($argument, $match[0]);
120
    }
121
122
    protected function parseArrayElement(&$argument, $match, $consume, &$quote, &$key, &$value)
123
    {
124
        switch ($match[2]) {
125
            case '"':
126
            case "'":
127
                $this->parseArrayString($argument, $match, $consume, $quote, $key, $value);
128
                break;
129
            case ':':
130
            case '=>':
131
                $this->parseArrayAssign($argument, $match, $consume, $quote, $key, $value);
132
                break;
133
            case ',':
134
                ${is_null($value) ? 'key' : 'value'} .= $match[0];
135
                $consume($argument, $match[0]);
136
                break;
137
        }
138
    }
139
140
    protected function parseArray($input, $subCodeHandler)
141
    {
142
        $output = array();
143
        $key = '';
144
        $value = null;
145
        $addToOutput = $subCodeHandler->addToOutput($output, $key, $value);
146
        $consume = $subCodeHandler->consume();
147
        foreach ($input as $argument) {
148
            $argument = ltrim($argument, '$');
149
            $quote = null;
150
            while (preg_match('/^(.*?)(=>|[\'",:])/', $argument, $match)) {
151
                $this->parseArrayElement($argument, $match, $consume, $quote, $key, $value);
152
            }
153
            ${is_null($value) ? 'key' : 'value'} .= $argument;
154
            $addToOutput();
155
        }
156
157
        return 'array(' . implode(', ', $output) . ')';
158
    }
159
160
    protected function parseEqual($sep, &$separators, &$result, $innerName, $subCodeHandler)
161
    {
162
        if (preg_match('/^[[:space:]]*$/', $innerName)) {
163
            next($separators);
164
            $handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result);
165
166
            return implode($handleCodeInbetween());
167
        }
168
169
        $handleRecursion = $subCodeHandler->handleRecursion($result);
170
171
        return $handleRecursion(array($sep, end($separators)));
172
    }
173
174
    protected function parseSeparator($sep, &$separators, &$result, &$varname, $subCodeHandler, $innerName)
175
    {
176
        $handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result);
177
        $var = '$__' . $this->name;
178
179
        switch ($sep[0]) {
180
            // translate the javascript's obj.attr into php's obj->attr or obj['attr']
181
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
182
            case '.':
183
                $result[] = sprintf("%s=is_array(%s)?%s['%s']:%s->%s",
184
                    $var, $varname, $varname, $innerName, $varname, $innerName
185
                );
186
                $varname = $var;
187
                break;
188
            //*/
189
190
            // funcall
191
            case '(':
192
                $arguments = $handleCodeInbetween();
193
                $call = $varname . '(' . implode(', ', $arguments) . ')';
194
                $call = static::addDollarIfNeeded($call);
195
                $varname = $var;
196
                array_push($result, "{$var}={$call}");
197
                break;
198
199
            case '[':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
200
                if (preg_match('/[a-zA-Z0-9\\\\_\\x7f-\\xff]$/', $varname)) {
201
                    $varname .= $sep[0] . $innerName;
202
                    break;
203
                }
204
            case '{':
205
                $varname .= $this->parseArray($handleCodeInbetween(), $subCodeHandler);
206
                break;
207
208
            case '=':
209
                $varname .= '=' . $this->parseEqual($sep, $separators, $result, $innerName, $subCodeHandler);
210
                break;
211
212
            default:
213
                if (($innerName !== false && $innerName !== '') || $sep[0] !== ')') {
214
                    $varname .= $sep[0] . $innerName;
215
                }
216
                break;
217
        }
218
    }
219
220
    protected function parseBetweenSeparators()
221
    {
222
        $separators = $this->separators;
223
224
        $result = array();
225
226
        $varname = $this->getVarname($separators[0]);
227
228
        $subCodeHandler = new SubCodeHandler($this, $this->input, $this->name);
229
        $getMiddleString = $subCodeHandler->getMiddleString();
230
        $getNext = $subCodeHandler->getNext($separators);
231
232
        // using next() ourselves so that we can advance the array pointer inside inner loops
233
        while (($sep = current($separators)) && $sep[0] !== null) {
234
            // $sep[0] - the separator string due to PREG_SPLIT_OFFSET_CAPTURE flag or null if end of string
235
            // $sep[1] - the offset due to PREG_SPLIT_OFFSET_CAPTURE
236
237
            $innerName = $getMiddleString($sep, $getNext(key($separators)));
238
239
            $this->parseSeparator($sep, $separators, $result, $varname, $subCodeHandler, $innerName);
240
241
            next($separators);
242
        }
243
        array_push($result, $varname);
244
245
        return $result;
246
    }
247
}
248