Completed
Push — master ( 76160a...cc51e4 )
by Todd
13s
created

CompilerBuffer::select()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.3949

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 14
cts 18
cp 0.7778
rs 8.439
c 0
b 0
f 0
cc 6
eloc 20
nc 6
nop 2
crap 6.3949
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2017 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Ebi;
9
10
11
use Symfony\Component\ExpressionLanguage\SyntaxError;
12
13
class CompilerBuffer {
14
    const STYLE_JOIN = 'join';
15
    const STYLE_ARRAY = 'array';
16
17
    /**
18
     * @var ComponentBuffer[]
19
     */
20
    private $buffers;
21
22
    /**
23
     * @var ComponentBuffer
24
     */
25
    private $current;
26
27
    private $currentName;
28
29
    private $basename;
30
31
    private $style = self::STYLE_JOIN;
32
33
    private $source;
34
35
    private $path;
36
37
    /**
38
     * @var array
39
     */
40
    private $defaults;
41
42
    /**
43
     * @var \SplObjectStorage
44
     */
45
    private $nodeProps;
46
47 65
    public function __construct($style = self::STYLE_JOIN, array $defaults = []) {
48
        $defaults += [
49 65
            'baseIndent' => 0
50
        ];
51
52 65
        $this->buffers = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
53 65
        $this->nodeProps = isset($defaults['nodeProps']) ? $defaults['nodeProps'] : new \SplObjectStorage();
54 65
        $this->style = $style;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
55 65
        $this->defaults = $defaults;
56 65
        $this->select('');
57 65
    }
58
59
    /**
60
     * Select a specific component buffer.
61
     * @param string $component The name of the component to select.
62
     * @param bool $add Whether to add a new component if there is already a compile buffer with the same name.
63
     */
64 65
    public function select($component, $add = false) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
65 65
        $previous = $this->currentName;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
66 65
        $this->currentName = $component;
67
68 65
        if (!array_key_exists($component, $this->buffers)) {
69 65
            $this->buffers[$component] = $buffer = new ComponentBuffer($this->defaults);
70 10
        } elseif ($add) {
71 1
            if (is_array($this->buffers[$component])) {
72
                $this->buffers[$component][] = $buffer = new ComponentBuffer($this->defaults);
73
            } else {
74 1
                $this->buffers[$component] = [
75 1
                    $this->buffers[$component],
76 1
                    $buffer = new ComponentBuffer($this->defaults)
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 16 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
77
                ];
78
            }
79
        } else {
80 10
            $buffer = $this->buffers[$component];
81 10
            if (is_array($buffer)) {
82
                $buffer = end($buffer);
83
                if ($previous === $component) {
84
                    $buffer = prev($buffer);
85
                }
86
            }
87
        }
88
89 65
        $this->current = $buffer;
90
91 65
        return $previous;
92
    }
93
94 59
    public function echoLiteral($value) {
95 59
        $this->current->echoLiteral($value);
96 59
    }
97
98 44
    public function echoCode($php) {
99 44
        $this->current->echoCode($php);
100 44
    }
101
102 65
    public function appendCode($php) {
103 65
        $this->current->appendCode($php);
104 65
    }
105
106 65
    public function indent($add) {
107 65
        $this->current->indent($add);
108 65
    }
109
110 20
    public function depth($add = 1) {
111 20
        $this->current->depth($add);
112 20
    }
113
114 21
    public function depthName($name, $add = 0) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
115 21
        return $this->current->depthName($name, $add);
116
    }
117
118 65
    public function pushScope(array $vars) {
119 65
        $this->current->pushScope($vars);
120 65
    }
121
122 58
    public function popScope() {
123 58
        $this->current->popScope();
124 58
    }
125
126 57
    public function getScopeVariables() {
127 57
        return $this->current->getScopeVariables();
128
    }
129
130 58
    public function flush() {
131 58
        switch ($this->getStyle()) {
132 58
            case self::STYLE_ARRAY:
133 12
                return $this->flushArray();
134
            default:
135 58
                return $this->flushJoin();
136
        }
137
    }
138
139
    private function flushJoin() {
140 58
        return implode("\n\n", array_map(function ($buffer) {
141
            /* @var ComponentBuffer $buffer */
142 58
            return $buffer->flush();
143 58
        }, $this->buffers));
144
    }
145
146 12
    private function flushArray() {
147 12
        $result = [];
148
149 12
        foreach ($this->buffers as $name => $buffers) {
150 12
            $flushed = [];
151 12
            if (is_array($buffers)) {
152 1
                foreach ($buffers as $buffer) {
153 1
                    $flushed[] = $buffer->flush();
154
                }
155
            } else {
156 12
                $flushed = [$buffers->flush()];
157
            }
158
159 12
            $flushed = array_filter($flushed);
160 12
            if (empty($flushed)) {
161 9
                continue;
162
            }
163
164 4
            if (count($flushed) === 1) {
165 4
                $children = reset($flushed);
166
            } else {
167 1
                $children = "[\n".implode(",\n", $flushed)."\n".$this->px(+1).']';
168
            }
169
170 4
            if ($name === '') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $name (integer) and '' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
171 4
                $result[] = $children;
172
            } else {
173 4
                $result[] = var_export($name, true).' => '.ltrim($children);
174
            }
175
        }
176
177 12
        if (empty($result)) {
178 9
            return '[]';
179
        } else {
180 4
            return "[\n".implode(",\n\n".$this->px(+1), $result)."\n".$this->px().']';
181
        }
182
    }
183
184 4
    protected function px($add = 0) {
185 4
        return str_repeat(' ', ($this->defaults['baseIndent'] + $add) * 4);
186
    }
187
188
    /**
189
     * Get the style.
190
     *
191
     * @return string Returns the style.
192
     */
193 58
    public function getStyle() {
194 58
        return $this->style;
195
    }
196
197
    /**
198
     * Set the style.
199
     *
200
     * @param string $style One of the **STYLE_*** constants.
201
     * @return $this
202
     */
203
    public function setStyle($style) {
204
        $this->style = $style;
205
        return $this;
206
    }
207
208
    /**
209
     * Get the basename.
210
     *
211
     * @return string Returns the basename.
212
     */
213
    public function getBasename() {
214
        return $this->basename;
215
    }
216
217
    /**
218
     * Set the basename.
219
     *
220
     * @param string $basename
221
     * @return $this
222
     */
223 65
    public function setBasename($basename) {
224 65
        $this->basename = $basename;
225 65
        return $this;
226
    }
227
228 65
    public function getNodeProp(\DOMNode $node, $name, $default = null) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
229 65
        if (!$this->nodeProps->contains($node) || !array_key_exists($name, $this->nodeProps[$node])) {
230 65
            return $default;
231
        }
232 6
        return $this->nodeProps[$node][$name];
233
    }
234
235 33
    public function setNodeProp(\DOMNode $node = null, $name, $value) {
236 33
        if ($node === null) {
237 19
            return $this;
238
        }
239
240 16
        if (!$this->nodeProps->contains($node)) {
241 16
            $this->nodeProps->attach($node, [$name => $value]);
242
        }
243
244 16
        $this->nodeProps[$node] = [$name => $value] + $this->nodeProps[$node];
245 16
        return $this;
246
    }
247
248 12
    public function getIndent() {
249 12
        return $this->current->getIndent();
250
    }
251
252 12
    public function getDepth() {
253 12
        return $this->current->getDepth();
254
    }
255
256
    public function getScope() {
257
        return $this->current->getScope();
258
    }
259
260 12
    public function getAllScopes() {
261 12
        return $this->current->getAllScopes();
262
    }
263
264
    /**
265
     * Create a new **CompileException** with proper context.
266
     *
267
     * @param \DOMNode $node The node that has the error.
268
     * @param \Exception $ex The exception that represents the low-level error.
269
     * @param array $context Custom context information for the exception.
270
     * @return CompileException Returns a new exception that can be thrown.
271
     */
272 7
    public function createCompilerException(\DOMNode $node, \Exception $ex, array $context = []) {
273
        $result = $context + [
274 7
            'path' => $this->getPath(),
275 7
            'source' => '',
276
            'sourcePosition' => null,
277 7
            'line' => $node->getLineNo(),
278
            'lines' => []
279
        ];
280 7
        $message = $ex->getMessage();
281
282 7
        if ($ex instanceof SyntaxError) {
283 3
            list($error, $position) = $this->splitSyntaxError($ex);
0 ignored issues
show
Unused Code introduced by
The assignment to $error is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
284 3
            $result['source'] = $result['source'] ?: ($node instanceof \DOMAttr ? $node->value : $node->nodeValue);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
285 3
            if (!isset($context['sourcePosition'])) {
286 3
                $result['sourcePosition'] = $position;
287
            }
288 4
        } elseif (empty($result['source'])) {
289 4
            if ($node instanceof \DOMAttr) {
290 3
                $result['source'] = $node->name.'="'.$node->value.'"';
291
            }
292
        }
293
294 7
        if (!empty($this->source)) {
295 7
            $allLines = explode("\n", $this->source);
296
297 7
            $lines = [];
298 7
            $line = $result['line'];
299 7
            for ($i = max(0, $line - 4); $i < $line + 3; $i++) {
300 7
                if (isset($allLines[$i])) {
301 7
                    $lines[$i + 1] = $allLines[$i];
302
                }
303
            }
304
305 7
            $result['lines'] = $lines;
306
        }
307
308 7
        return new CompileException($message, $result, $ex);
309
    }
310
311 3
    private function splitSyntaxError(SyntaxError $ex) {
312 3
        if (preg_match('`^(.*) around position (.*)\.$`', $ex->getMessage(), $m)) {
313 3
            return [$m[1], $m[2]];
314
        } else {
315
            return [$ex->getMessage(), 0];
316
        }
317
    }
318
319
    /**
320
     * Get the source.
321
     *
322
     * @return mixed Returns the source.
323
     */
324 12
    public function getSource() {
325 12
        return $this->source;
326
    }
327
328
    /**
329
     * Set the source.
330
     *
331
     * @param mixed $source
332
     * @return $this
333
     */
334 65
    public function setSource($source) {
335 65
        $this->source = $source;
336 65
        return $this;
337
    }
338
339
    /**
340
     * Get the path.
341
     *
342
     * @return mixed Returns the path.
343
     */
344 7
    public function getPath() {
345 7
        return $this->path;
346
    }
347
348
    /**
349
     * Set the path.
350
     *
351
     * @param mixed $path
352
     * @return $this
353
     */
354 65
    public function setPath($path) {
355 65
        $this->path = $path;
356 65
        return $this;
357
    }
358
359
    /**
360
     * Get the entire node property array.
361
     *
362
     * @return \SplObjectStorage Returns the node properties.
363
     */
364 12
    public function getNodePropArray() {
365 12
        return $this->nodeProps;
366
    }
367
}
368