Passed
Push — develop ( 067c9c...bf1d25 )
by nguereza
04:45
created

AbstractBlock::parse()   C

Complexity

Conditions 12
Paths 18

Size

Total Lines 91
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 61
nc 18
nop 1
dl 0
loc 91
rs 6.4242
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Platine Template
5
 *
6
 * Platine Template is a template engine that has taken a lot of inspiration from Django.
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Template
11
 * Copyright (c) 2014 Guz Alexander, http://guzalexander.com
12
 * Copyright (c) 2011, 2012 Harald Hanek, http://www.delacap.com
13
 * Copyright (c) 2006 Mateo Murphy
14
 *
15
 * Permission is hereby granted, free of charge, to any person obtaining a copy
16
 * of this software and associated documentation files (the "Software"), to deal
17
 * in the Software without restriction, including without limitation the rights
18
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
 * copies of the Software, and to permit persons to whom the Software is
20
 * furnished to do so, subject to the following conditions:
21
 *
22
 * The above copyright notice and this permission notice shall be included in all
23
 * copies or substantial portions of the Software.
24
 *
25
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
 * SOFTWARE.
32
 */
33
34
/**
35
 *  @file AbstractBlock.php
36
 *
37
 *  The base Template block class
38
 *
39
 *  @package    Platine\Template\Parser
40
 *  @author Platine Developers Team
41
 *  @copyright  Copyright (c) 2020
42
 *  @license    http://opensource.org/licenses/MIT  MIT License
43
 *  @link   http://www.iacademy.cf
44
 *  @version 1.0.0
45
 *  @filesource
46
 */
47
48
declare(strict_types=1);
49
50
namespace Platine\Template\Parser;
51
52
use InvalidArgumentException;
53
use Platine\Template\Exception\ParseException;
54
use Platine\Template\Exception\RenderException;
55
56
/**
57
 * Class AbstractBlock
58
 * @package Platine\Template\Parser
59
 */
60
class AbstractBlock extends AbstractTag
61
{
62
63
    /**
64
     * The node list
65
     * @var AbstractTag[]|Variable[]|string[]
66
     */
67
    protected array $nodeList = [];
68
69
    /**
70
     * Whenever next token should be ltrim'med.
71
     * @var bool
72
     */
73
    protected bool $trimWhitespace = false;
74
75
    /**
76
     * Return the node list
77
     * @return AbstractTag[]|Variable[]|string[]
78
     */
79
    public function getNodeList(): array
80
    {
81
        return $this->nodeList;
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
    */
87
    public function parse(array &$tokens): void
88
    {
89
        $startRegex = '/^' . Token::BLOCK_OPEN . '/';
90
        $variableStartRegex = '/^' . Token::VARIABLE_OPEN . '/';
91
        $tagRegex = '/^'
92
                        . Token::BLOCK_OPEN
93
                        . Token::WHITESPACE_CONTROL
94
                        . '?\s*(\w+)\s*(.*?)'
95
                        . Token::WHITESPACE_CONTROL
96
                        . '?'
97
                        . Token::BLOCK_CLOSE
98
                        . '$/';
99
100
        $lexerStart = new Lexer($startRegex);
101
        $lexerVariableStart = new Lexer($variableStartRegex);
102
        $lexerTag = new Lexer($tagRegex);
103
104
        $this->nodeList = [];
105
        //Custom tags
106
        $tags = $this->parser->getTemplate()->getTags();
107
        while (count($tokens) > 0) {
108
            $token = (string) array_shift($tokens);
109
            if ($lexerStart->match($token)) {
110
                $this->whitespaceHandler($token);
111
                if ($lexerTag->match($token)) {
112
                    // If we found the proper block
113
                    // delimitor just end parsing here
114
                    // and let the outer block proceed
115
                    if ($lexerTag->getStringMatch(1) === $this->blockDelimiter()) {
116
                        $this->endTag();
117
                        return;
118
                    }
119
120
                    $tagNameClass = null;
121
                    if (array_key_exists($lexerTag->getStringMatch(1), $tags)) {
122
                        $tagNameClass = $tags[$lexerTag->getStringMatch(1)];
123
                    } else {
124
                        //check for core tags
125
                        $coreTagName = $lexerTag->getStringMatch(1);
126
                        $coreTagNameClass = 'Platine\\Template\\Tag\\' . ucwords($coreTagName) . 'Tag';
127
                        if (class_exists($coreTagNameClass)) {
128
                            $tagNameClass = $coreTagNameClass;
129
                        }
130
                    }
131
132
                    if ($tagNameClass !== null) {
133
                        $node = new $tagNameClass(
134
                            $lexerTag->getStringMatch(2),
135
                            $tokens,
136
                            $this->parser
137
                        );
138
139
                        if (!$node instanceof AbstractTag) {
140
                            throw new InvalidArgumentException(sprintf(
141
                                'Tag class [%s] must extends base classes [%s] or [%s]',
142
                                $tagNameClass,
143
                                AbstractTag::class,
144
                                AbstractBlock::class
145
                            ));
146
                        }
147
148
                        $this->nodeList[] = $node;
149
150
                        if ($lexerTag->getStringMatch(1) === 'extends') {
151
                            return;
152
                        }
153
                    } else {
154
                        $this->unknownTag($lexerTag->getStringMatch(1), $lexerTag->getStringMatch(2), $tokens);
155
                    }
156
                } else {
157
                    throw new ParseException(sprintf(
158
                        'Tag [%s] was not properly terminated (won\'t match [%s])',
159
                        $token,
160
                        $lexerTag
161
                    ));
162
                }
163
            } elseif ($lexerVariableStart->match($token)) {
164
                $this->whitespaceHandler($token);
165
                $this->nodeList[] = $this->createVariable($token);
166
            } else {
167
                // This is neither a tag or a variable, proceed with an ltrim
168
                if ($this->trimWhitespace) {
169
                    $token = ltrim($token);
170
                }
171
172
                $this->trimWhitespace = false;
173
                $this->nodeList[] = $token;
174
            }
175
        }
176
177
        $this->assertMissingDelimiter();
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
    */
183
    public function render(Context $context): string
184
    {
185
        return $this->renderAll($this->nodeList, $context);
186
    }
187
188
    /**
189
     * Renders all the given node list's nodes
190
     * @param AbstractTag[]|Variable[]|string[] $list
191
     * @param Context $context
192
     * @return string
193
     */
194
    protected function renderAll(array $list, Context $context): string
195
    {
196
        $result = '';
197
        foreach ($list as $token) {
198
            $value = $token;
199
            if (is_object($token)) {
200
                //Variable or AbstractTag
201
                $value = $token->render($context);
202
            }
203
204
            if (is_array($value)) {
205
                throw new RenderException('Implicit rendering of arrays not supported.'
206
                        . ' Use index operator.');
207
            }
208
209
            $result .= $value;
210
211
            if ($context->hasRegister('break') || $context->hasRegister('continue')) {
212
                break;
213
            }
214
215
            $context->tick();
216
        }
217
218
        return $result;
219
    }
220
221
    /**
222
     * An action to execute when the end tag is reached
223
     * @return void
224
     */
225
    protected function endTag(): void
226
    {
227
        //Do nothing now
228
    }
229
230
    /**
231
     * Handler for unknown tags
232
     * @param string $tag
233
     * @param string $param
234
     * @param array<int, string> $tokens
235
     * @return void
236
     * @throws ParseException
237
     */
238
    protected function unknownTag(string $tag, string $param, array $tokens): void
0 ignored issues
show
Unused Code introduced by
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

238
    protected function unknownTag(string $tag, /** @scrutinizer ignore-unused */ string $param, array $tokens): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
239
    {
240
        switch ($tag) {
241
            case 'else':
242
                throw new ParseException(sprintf(
243
                    '[%s] does not expect "else" tag',
244
                    $this->getTagName()
245
                ));
246
            case 'end':
247
                throw new ParseException(sprintf(
248
                    '"end" is not a valid delimiter for [%s] tags.Use [%s]',
249
                    $this->getName(),
250
                    $this->blockDelimiter()
251
                ));
252
            default:
253
                throw new ParseException(sprintf(
254
                    'Unknown template tag [%s]',
255
                    $tag
256
                ));
257
        }
258
    }
259
260
    /**
261
     * This method is called at the end
262
     * of parsing, and will throw an error unless this method is sub classed.
263
     * @return void
264
     * @throws ParseException
265
     */
266
    protected function assertMissingDelimiter(): void
267
    {
268
        throw new ParseException(sprintf(
269
            '[%s] tag was never closed',
270
            $this->getTagName()
271
        ));
272
    }
273
274
    /**
275
     * Returns the string that delimits the end of the block
276
     * @return string
277
     */
278
    protected function blockDelimiter(): string
279
    {
280
        return 'end' . $this->getTagName();
281
    }
282
283
    /**
284
     * Handle the white space.
285
     * @param string $token
286
     * @return void
287
     */
288
    protected function whitespaceHandler(string $token): void
289
    {
290
        $char = Token::WHITESPACE_CONTROL;
291
        if (substr($token, 2, 1) === $char) {
292
            $previousToken = end($this->nodeList);
293
            if (is_string($previousToken)) {
294
                // this can also be a tag or a variable
295
                $this->nodeList[key($this->nodeList)] = ltrim($previousToken);
296
            }
297
        }
298
299
        $this->trimWhitespace = substr($token, -3, 1) === $char;
300
    }
301
302
    /**
303
     * Create a variable for the given token
304
     * @param string $token
305
     * @return Variable
306
     * @throws ParseException
307
     */
308
    private function createVariable(string $token): Variable
309
    {
310
        $variableRegex = '/^'
311
                            . Token::VARIABLE_OPEN
312
                            . Token::WHITESPACE_CONTROL
313
                            . '?(.*?)'
314
                            . Token::WHITESPACE_CONTROL
315
                            . '?'
316
                            . Token::VARIABLE_CLOSE
317
                            . '$/';
318
        $lexer = new Lexer($variableRegex);
319
320
        if ($lexer->match($token)) {
321
            return new Variable($lexer->getStringMatch(1), $this->parser);
322
        }
323
324
        throw new ParseException(sprintf(
325
            'Variable [%s] was not properly terminated',
326
            $token
327
        ));
328
    }
329
}
330