GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( dcda12...2eb2ae )
by Zordius
02:32
created

Compiler   D

Complexity

Total Complexity 144

Size/Duplication

Total Lines 660
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 98.52%

Importance

Changes 26
Bugs 2 Features 0
Metric Value
wmc 144
c 26
b 2
f 0
lcom 1
cbo 5
dl 0
loc 660
ccs 267
cts 271
cp 0.9852
rs 4.6236

23 Methods

Rating   Name   Duplication   Size   Complexity  
B compileTemplate() 0 31 4
B composePHPRender() 0 56 6
A getFuncName() 0 13 4
A getVariableNames() 0 15 4
A compileSubExpression() 0 9 4
A getVariableNameOrSubExpression() 0 3 2
F getVariableName() 0 67 35
C compileToken() 0 27 8
B partial() 0 20 7
A inline() 0 11 3
A invertedSection() 0 4 1
A blockCustomHelper() 0 9 3
D blockEnd() 0 31 10
B blockBegin() 0 20 11
B section() 0 18 9
A with() 0 7 4
A customHelper() 0 13 4
A doElse() 0 10 3
A compileLog() 0 5 1
B compileLookup() 0 9 7
B compileOutput() 0 8 9
A compileVariable() 0 9 3
A addUsageCount() 0 6 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
/*
3
4
Copyrights for code authored by Yahoo! Inc. is licensed under the following terms:
5
MIT License
6
Copyright (c) 2013-2016 Yahoo! Inc. All Rights Reserved.
7
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
11
Origin: https://github.com/zordius/lightncandy
12
*/
13
14
/**
15
 * file of LightnCandy Compiler
16
 *
17
 * @package    LightnCandy
18
 * @author     Zordius <[email protected]>
19
 */
20
21
namespace LightnCandy;
22
23
use \LightnCandy\Validator;
24
use \LightnCandy\Token;
25
use \LightnCandy\Expression;
26
use \LightnCandy\Parser;
27
28
/**
29
 * LightnCandy Compiler
30
 */
31
class Compiler extends Validator
32
{
33
    public static $lastParsed;
34
35
    /**
36
     * Compile template into PHP code
37
     *
38
     * @param array<string,array|string|integer> $context Current context
39
     * @param string $template handlebars template
40
     *
41
     * @return string|null generated PHP code
42
     */
43 744
    public static function compileTemplate(&$context, $template) {
44 744
        array_unshift($context['parsed'], array());
45 744
        Validator::verify($context, $template);
46 744
        static::$lastParsed = $context['parsed'];
47
48 744
        if (count($context['error'])) {
49 77
            return;
50
        }
51
52 668
        Parser::setDelimiter($context);
53
54 668
        $context['compile'] = true;
55
56
        // Handle dynamic partials
57 668
        Partial::handleDynamic($context);
58
59
        // Do PHP code generation.
60 668
        $code = '';
61 668
        foreach ($context['parsed'][0] as $info) {
62 668
            if (is_array($info)) {
63 627
                $context['tokens']['current']++;
64 627
                $code .= "'" . static::compileToken($context, $info) . "'";
65
            } else {
66 668
                $code .= $info;
67
            }
68
        }
69
70 668
        array_shift($context['parsed']);
71
72 668
        return $code;
73
    }
74
75
    /**
76
     * Compose LightnCandy render codes for include()
77
     *
78
     * @param array<string,array|string|integer> $context Current context
79
     * @param string $code generated PHP code
80
     *
81
     * @return string Composed PHP code
82
     */
83 667
    public static function composePHPRender($context, $code) {
84 667
        $flagJStrue = Expression::boolString($context['flags']['jstrue']);
85 667
        $flagJSObj = Expression::boolString($context['flags']['jsobj']);
86 667
        $flagSPVar = Expression::boolString($context['flags']['spvar']);
87 667
        $flagProp = Expression::boolString($context['flags']['prop']);
88 667
        $flagMethod = Expression::boolString($context['flags']['method']);
89 667
        $flagLambda = Expression::boolString($context['flags']['lambda']);
90 667
        $flagMustlok = Expression::boolString($context['flags']['mustlok']);
91 667
        $flagMustlam = Expression::boolString($context['flags']['mustlam']);
92 667
        $flagEcho = Expression::boolString($context['flags']['echo']);
93 667
        $flagPartNC = Expression::boolString($context['flags']['partnc']);
94 667
        $flagKnownHlp = Expression::boolString($context['flags']['knohlp']);
95
96 667
        $constants = Exporter::constants($context);
97 667
        $helpers = Exporter::helpers($context);
98 667
        $partials = implode(",\n", $context['partialCode']);
99 667
        $debug = Runtime::DEBUG_ERROR_LOG;
100 667
        $use = $context['flags']['standalone'] ? Exporter::runtime($context) : "use {$context['runtime']} as {$context['runtimealias']};";
101 667
        $safeString = (($context['usedFeature']['enc'] > 0) && ($context['flags']['standalone'] === 0)) ? "use {$context['safestring']} as SafeString;" : '';
102 667
        $exportSafeString = (($context['usedFeature']['enc'] > 0) && ($context['flags']['standalone'] >0)) ? Exporter::safestring($context) : '';
103
104
        // Return generated PHP code string.
105
        return <<<VAREND
106 667
$safeString{$use}{$exportSafeString}return function (\$in = null, \$options = null) {
107 667
    \$helpers = $helpers;
108 667
    \$partials = array($partials);
109
    \$cx = array(
110
        'flags' => array(
111 667
            'jstrue' => $flagJStrue,
112 667
            'jsobj' => $flagJSObj,
113 667
            'spvar' => $flagSPVar,
114 667
            'prop' => $flagProp,
115 667
            'method' => $flagMethod,
116 667
            'lambda' => $flagLambda,
117 667
            'mustlok' => $flagMustlok,
118 667
            'mustlam' => $flagMustlam,
119 667
            'echo' => $flagEcho,
120 667
            'partnc' => $flagPartNC,
121 667
            'knohlp' => $flagKnownHlp,
122 667
            'debug' => isset(\$options['debug']) ? \$options['debug'] : $debug,
123
        ),
124 667
        'constants' => $constants,
125
        'helpers' => isset(\$options['helpers']) ? array_merge(\$helpers, \$options['helpers']) : \$helpers,
126
        'partials' => isset(\$options['partials']) ? array_merge(\$partials, \$options['partials']) : \$partials,
127
        'scopes' => array(),
128
        'sp_vars' => isset(\$options['data']) ? array_merge(array('root' => \$in), \$options['data']) : array('root' => \$in),
129
        'blparam' => array(),
130
        'partialid' => 0,
131 667
        'runtime' => '{$context['runtime']}',
132
    );
133 667
    {$context['renderex']}
134 667
    {$context['ops']['op_start']}'$code'{$context['ops']['op_end']}
135 667
};
136
VAREND
137
        ;
138
    }
139
140
    /**
141
     * Get function name for standalone or none standalone template.
142
     *
143
     * @param array<string,array|string|integer> $context Current context of compiler progress.
144
     * @param string $name base function name
145
     * @param string $tag original handlabars tag for debug
146
     *
147
     * @return string compiled Function name
148
     *
149
     * @expect 'LR::test(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 'LR'), 'test', ''
150
     * @expect 'LL::test2(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 'LL'), 'test2', ''
151
     * @expect "lala_abctest3(" when input array('flags' => array('standalone' => 1, 'debug' => 0), 'runtime' => 'Runtime', 'runtimealias' => 0, 'funcprefix' => 'lala_abc'), 'test3', ''
152
     * @expect 'RR::debug(\'abc\', \'test\', ' when input array('flags' => array('standalone' => 0, 'debug' => 1), 'runtime' => 'Runtime', 'runtimealias' => 'RR', 'funcprefix' => 'haha456'), 'test', 'abc'
153
     */
154 602
    protected static function getFuncName(&$context, $name, $tag) {
155 602
        static::addUsageCount($context, 'runtime', $name);
156
157 602
        if ($context['flags']['debug'] && ($name != 'miss')) {
158 10
            $dbg = "'$tag', '$name', ";
159 10
            $name = 'debug';
160 10
            static::addUsageCount($context, 'runtime', 'debug');
161
        } else {
162 600
            $dbg = '';
163
        }
164
165 602
        return $context['flags']['standalone'] ? "{$context['funcprefix']}$name($dbg" : "{$context['runtimealias']}::$name($dbg";
166
    }
167
168
    /**
169
     * Get string presentation of variables
170
     *
171
     * @param array<string,array|string|integer> $context current compile context
172
     * @param array<array> $vn variable name array.
173
     * @param array<string>|null $blockParams block param list
174
     *
175
     * @return array<string|array> variable names
176
     *
177
     * @expect array('array(array($in),array())', array('this')) when input array('flags'=>array('spvar'=>true)), array(null)
178
     * @expect array('array(array($in,$in),array())', array('this', 'this')) when input array('flags'=>array('spvar'=>true)), array(null, null)
179
     * @expect array('array(array(),array(\'a\'=>$in))', array('this')) when input array('flags'=>array('spvar'=>true)), array('a' => null)
180
     */
181 270
    protected static function getVariableNames(&$context, $vn, $blockParams = null) {
182 270
        $vars = array(array(), array());
183 270
        $exps = array();
184 270
        foreach ($vn as $i => $v) {
185 228
            $V = static::getVariableNameOrSubExpression($context, $v);
186 228
            if (is_string($i)) {
187 37
                $vars[1][] = "'$i'=>{$V[0]}";
188
            } else {
189 214
                $vars[0][] = $V[0];
190
            }
191 228
            $exps[] = $V[1];
192
        }
193 270
        $bp = $blockParams ? (',array(' . Expression::listString($blockParams) . ')') : '';
194 270
        return array('array(array(' . implode(',', $vars[0]) . '),array(' . implode(',', $vars[1]) . ")$bp)", $exps);
195
    }
196
197
    /**
198
     * Get string presentation of a sub expression
199
     *
200
     * @param array<string,array|string|integer> $context current compile context
201
     * @param array<boolean|integer|string|array> $vars parsed arguments list
202
     *
203
     * @return array<string> code representing passed expression
204
     */
205 41
    public static function compileSubExpression(&$context, $vars) {
206 41
        $ret = static::customHelper($context, $vars, true, true);
207
208 41
        if (($ret === null) && $context['flags']['lambda']) {
209 4
            $ret = static::compileVariable($context, $vars, true, true);
210
        }
211
212 41
        return array($ret ? $ret : '', 'FIXME: $subExpression');
213
    }
214
215
    /**
216
     * Get string presentation of a subexpression or a variable
217
     *
218
     * @param array<array|string|integer> $context current compile context
219
     * @param array<array|string|integer> $var variable parsed path
220
     *
221
     * @return array<string> variable names
0 ignored issues
show
Documentation introduced by
Should the return type not be array<array|string|integer>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
222
     */
223 378
    protected static function getVariableNameOrSubExpression(&$context, $var) {
224 378
        return Parser::isSubExp($var) ? static::compileSubExpression($context, $var[1]) : static::getVariableName($context, $var);
225
    }
226
227
    /**
228
     * Get string presentation of a variable
229
     *
230
     * @param array<array|string|integer> $var variable parsed path
231
     * @param array<array|string|integer> $context current compile context
232
     * @param array<string>|null $lookup extra lookup string as valid PHP variable name
233
     *
234
     * @return array<string> variable names
0 ignored issues
show
Documentation introduced by
Should the return type not be array<array|string|integer>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
235
     *
236
     * @expect array('$in', 'this') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(null)
237
     * @expect array('((is_array($in) && isset($in[\'true\'])) ? $in[\'true\'] : null)', '[true]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('true')
238
     * @expect array('((is_array($in) && isset($in[\'false\'])) ? $in[\'false\'] : null)', '[false]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('false')
239
     * @expect array('true', 'true') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'true')
240
     * @expect array('false', 'false') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'false')
241
     * @expect array('((is_array($in) && isset($in[\'2\'])) ? $in[\'2\'] : null)', '[2]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('2')
242
     * @expect array('2', '2') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0)), array(-1, '2')
243
     * @expect array('((is_array($in) && isset($in[\'@index\'])) ? $in[\'@index\'] : null)', '[@index]') when input array('flags'=>array('spvar'=>false,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@index')
244
     * @expect array("(isset(\$cx['sp_vars']['index']) ? \$cx['sp_vars']['index'] : null)", '@[index]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@index')
245
     * @expect array("(isset(\$cx['sp_vars']['key']) ? \$cx['sp_vars']['key'] : null)", '@[key]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@key')
246
     * @expect array("(isset(\$cx['sp_vars']['first']) ? \$cx['sp_vars']['first'] : null)", '@[first]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@first')
247
     * @expect array("(isset(\$cx['sp_vars']['last']) ? \$cx['sp_vars']['last'] : null)", '@[last]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('@last')
248
     * @expect array('((is_array($in) && isset($in[\'"a"\'])) ? $in[\'"a"\'] : null)', '["a"]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('"a"')
249
     * @expect array('"a"', '"a"') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, '"a"')
250
     * @expect array('((is_array($in) && isset($in[\'a\'])) ? $in[\'a\'] : null)', '[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array('a')
251
     * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-1]) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-1]) && isset($cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'] : null)', '../[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(1,'a')
252
     * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-3]) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-3]) && isset($cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'] : null)', '../../../[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(3,'a')
253
     * @expect array('((is_array($in) && isset($in[\'id\'])) ? $in[\'id\'] : null)', 'this.[id]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0)), array(null, 'id')
254
     * @expect array('LR::v($cx, $in, isset($in) ? $in : null, array(\'id\'))', 'this.[id]') when input array('flags'=>array('prop'=>true,'spvar'=>true,'debug'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0,'lambda'=>0,'jslen'=>0,'standalone'=>0), 'runtime' => 'Runtime', 'runtimealias' => 'LR'), array(null, 'id')
255
     */
256 595
    protected static function getVariableName(&$context, $var, $lookup = null, $args = null) {
257 595
        if (isset($var[0]) && ($var[0] === Parser::LITERAL)) {
258 83
            if ($var[1] === "undefined") {
259 2
                $var[1] = "null";
260
            }
261 83
            return array($var[1], preg_replace('/\'(.*)\'/', '$1', $var[1]));
262
        }
263
264 554
        list($levels, $spvar, $var) = Expression::analyze($context, $var);
0 ignored issues
show
Documentation introduced by
$var is of type array<integer,array|stri...array|string|integer"}>, but the function expects a array<integer,array|string|integer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
265 554
        $exp = Expression::toString($levels, $spvar, $var);
266 554
        $base = $spvar ? "\$cx['sp_vars']" : '$in';
267 554
        $is_array_check = $spvar ? false : true;
268
269
        // change base when trace to parent
270 554
        if ($levels > 0) {
271 40
            if ($spvar) {
272 2
                $base .= str_repeat("['_parent']", $levels);
273
            } else {
274 38
                $base = "\$cx['scopes'][count(\$cx['scopes'])-$levels]";
275
            }
276
        }
277
278 554
        if ((count($var) == 0) || (($var[0] === null) && (count($var) == 1))) {
279 139
            return array($base, $exp);
280
        }
281
282 497
        if ($var[0] === null) {
283 1
            array_shift($var);
284
        }
285
286
        // To support recursive context lookup, instance properties + methods and lambdas
287
        // the only way is using slower rendering time variable resolver.
288 497
        if ($context['flags']['prop'] || $context['flags']['method'] || $context['flags']['mustlok'] || $context['flags']['mustlam'] || $context['flags']['lambda']) {
289 386
            $L = $lookup ? ", $lookup[0]" : '';
290 386
            $A = $args ? ",$args[0]" : '';
291 386
            $E = $args ? ' ' . implode(' ', $args[1]) : '';
292 386
            return array(static::getFuncName($context, 'v', $exp) . "\$cx, \$in, isset($base) ? $base : null, array(" . Expression::listString($var) . "$L)$A)", $lookup ? "lookup $exp $lookup[1]" : "$exp$E");
293
        }
294
295 112
        $n = Expression::arrayString($var);
296 112
        $k = array_pop($var);
297 112
        $L = $lookup ? "[{$lookup[0]}]" : '';
298 112
        $p = $lookup ? $n : (count($var) ? Expression::arrayString($var) : '');
299
300 112
        $checks = array();
301 112
        if ($levels > 0) {
302 10
            $checks[] = "isset($base)";
303
        }
304 112
        if ($is_array_check) {
305 109
            $checks[] = "is_array($base$p)";
306
        }
307 112
        $checks[] = "isset($base$n$L)";
308 112
        $check = ((count($checks) > 1) ? '(' : '') . implode(' && ', $checks) . ((count($checks) > 1) ? ')' : '');
309
310 112
        $lenStart = '';
311 112
        $lenEnd = '';
312
313 112
        if ($context['flags']['jslen']) {
314 50
            if (($lookup === null) && ($k === 'length')) {
315 1
                array_pop($checks);
316 1
                $lenStart = '(' . ((count($checks) > 1) ? '(' : '') . implode(' && ', $checks) . ((count($checks) > 1) ? ')' : '') . " ? count($base" . Expression::arrayString($var) . ') : ';
317 1
                $lenEnd = ')';
318
            }
319
        }
320
321 112
        return array("($check ? $base$n$L : $lenStart" . ($context['flags']['debug'] ? (static::getFuncName($context, 'miss', '') . "\$cx, '$exp')") : 'null' ) . ")$lenEnd", $lookup ? "lookup $exp $lookup[1]" : $exp);
322
    }
323
324
    /**
325
     * Return compiled PHP code for a handlebars token
326
     *
327
     * @param array<string,array|string|integer> $context current compile context
328
     * @param array<string,array|boolean> $info parsed information
329
     *
330
     * @return string Return compiled code segment for the token
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|integer|double|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
331
     */
332 627
    protected static function compileToken(&$context, $info) {
333 627
        list($raw, $vars, $token, $indent) = $info;
334
335 627
        $context['tokens']['partialind'] = $indent;
336 627
        $context['currentToken'] = $token;
337
338 627
        if ($ret = static::operator($token[Token::POS_OP], $context, $vars)) {
339 385
            return $ret;
340
        }
341
342 488
        if (isset($vars[0][0])) {
343 455
            if ($ret = static::customHelper($context, $vars, $raw, true)) {
344 116
                return static::compileOutput($context, $ret, 'FIXME: helper', $raw, false);
345
            }
346 346
            if ($context['flags']['else'] && ($vars[0][0] === 'else')) {
347 30
                return static::doElse($context, $vars);
348
            }
349 327
            if ($vars[0][0] === 'lookup') {
350 2
                return static::compileLookup($context, $vars, $raw);
351
            }
352 325
            if ($vars[0][0] === 'log') {
353 2
                return static::compileLog($context, $vars, $raw);
354
            }
355
        }
356
357 362
        return static::compileVariable($context, $vars, $raw, false);
358
    }
359
360
    /**
361
     * handle partial
362
     *
363
     * @param array<string,array|string|integer> $context current compile context
364
     * @param array<boolean|integer|string|array> $vars parsed arguments list
365
     *
366
     * @return string Return compiled code segment for the partial
367
     */
368 91
    public static function partial(&$context, $vars) {
369 91
        Parser::getBlockParams($vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|integer|string|array>, but the function expects a array<integer,boolean|integer|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
370 91
        $pid = Parser::getPartialBlock($vars);
371 91
        $p = array_shift($vars);
372 91
        if ($context['flags']['runpart']) {
373 82
            if (!isset($vars[0])) {
374 75
                $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array();
375
            }
376 82
            $v = static::getVariableNames($context, $vars);
377 82
            $tag = ">$p[0] " .implode(' ', $v[1]);
378 82
            if (Parser::isSubExp($p)) {
379 4
                list($p) = static::compileSubExpression($context, $p[1]);
380
            } else {
381 78
                $p = "'$p[0]'";
382
            }
383 82
            $sp = $context['tokens']['partialind'] ? ", '{$context['tokens']['partialind']}'" : '';
384 82
            return $context['ops']['seperator'] . static::getFuncName($context, 'p', $tag) . "\$cx, $p, $v[0],$pid$sp){$context['ops']['seperator']}";
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $context['ops']['...['ops']['seperator']}"; (string) is incompatible with the return type of the parent method LightnCandy\Validator::partial of type integer|double|boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
385
        }
386 9
        return isset($context['usedPartial'][$p[0]]) ? "{$context['ops']['seperator']}'" . Partial::compileStatic($context, $p[0]) . "'{$context['ops']['seperator']}" : $context['ops']['seperator'];
387
    }
388
389
    /**
390
     * handle inline partial
391
     *
392
     * @param array<string,array|string|integer> $context current compile context
393
     * @param array<boolean|integer|string|array> $vars parsed arguments list
394
     *
395
     * @return string Return compiled code segment for the partial
396
     */
397 10
    public static function inline(&$context, $vars) {
398 10
        Parser::getBlockParams($vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|integer|string|array>, but the function expects a array<integer,boolean|integer|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
399 10
        list($code) = array_shift($vars);
400 10
        $p = array_shift($vars);
401 10
        if (!isset($vars[0])) {
402 10
            $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array();
403
        }
404 10
        $v = static::getVariableNames($context, $vars);
405 10
        $tag = ">*inline $p[0]" .implode(' ', $v[1]);
406 10
        return $context['ops']['seperator'] . static::getFuncName($context, 'in', $tag) . "\$cx, '{$p[0]}', $code){$context['ops']['seperator']}";
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $context['ops']['...['ops']['seperator']}"; (string) is incompatible with the return type of the parent method LightnCandy\Validator::inline of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
407
    }
408
409
    /**
410
     * Return compiled PHP code for a handlebars inverted section begin token
411
     *
412
     * @param array<string,array|string|integer> $context current compile context
413
     * @param array<boolean|integer|string|array> $vars parsed arguments list
414
     *
415
     * @return string Return compiled code segment for the token
416
     */
417 38
    protected static function invertedSection(&$context, $vars) {
418 38
        $v = static::getVariableName($context, $vars[0]);
419 38
        return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'isec', '^' . $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}";
420
    }
421
422
    /**
423
     * Return compiled PHP code for a handlebars block custom helper begin token
424
     *
425
     * @param array<string,array|string|integer> $context current compile context
426
     * @param array<boolean|integer|string|array> $vars parsed arguments list
427
     * @param boolean $inverted the logic will be inverted
428
     *
429
     * @return string Return compiled code segment for the token
430
     */
431 62
    protected static function blockCustomHelper(&$context, $vars, $inverted = false) {
432 62
        $bp = Parser::getBlockParams($vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|integer|string|array>, but the function expects a array<integer,boolean|integer|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
433 62
        $ch = array_shift($vars);
434 62
        $inverted = $inverted ? 'true' : 'false';
435 62
        static::addUsageCount($context, 'helpers', $ch[0]);
436 62
        $v = static::getVariableNames($context, $vars, $bp);
437
438 62
        return $context['ops']['seperator'] . static::getFuncName($context, 'hbch', ($inverted ? '^' : '#') . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, \$in, $inverted, function(\$cx, \$in) {{$context['ops']['f_start']}";
439
    }
440
441
    /**
442
     * Return compiled PHP code for a handlebars block end token
443
     *
444
     * @param array<string,array|string|integer> $context current compile context
445
     * @param array<boolean|integer|string|array> $vars parsed arguments list
446
     * @param string|null $matchop should also match to this operator
447
     *
448
     * @return string Return compiled code segment for the token
449
     */
450 325
    protected static function blockEnd(&$context, &$vars, $matchop = NULL) {
451 325
        $pop = $context['stack'][count($context['stack']) - 1];
452 325
        switch ($context['currentToken'][Token::POS_INNERTAG]) {
453 325
            case 'if':
454 270
            case 'unless':
455 79
                if ($pop === ':') {
456 26
                    array_pop($context['stack']);
457 26
                    return "{$context['ops']['cnd_end']}";
458
                }
459 56
                if (!$context['flags']['nohbh']) {
460 54
                    return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
461
                }
462 2
                break;
463 266
            case 'with':
464 33
                if (!$context['flags']['nohbh']) {
465 32
                    return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
466
                }
467
        }
468
469 244
        if ($pop === ':') {
470
            array_pop($context['stack']);
471
            return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
472
        }
473
474
        switch($pop) {
475 244
            case '#':
476 219
                return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
477 33
            case '^':
478 33
                return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
479
        }
480
    }
481
482
    /**
483
     * Return compiled PHP code for a handlebars block begin token
484
     *
485
     * @param array<string,array|string|integer> $context current compile context
486
     * @param array<boolean|integer|string|array> $vars parsed arguments list
487
     *
488
     * @return string Return compiled code segment for the token
489
     */
490 242
    protected static function blockBegin(&$context, $vars) {
491 242
        $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array());
492 242
        if (!$context['flags']['nohbh']) {
493 204
            switch (isset($vars[0][0]) ? $vars[0][0] : null) {
494 204
                case 'if':
495 69
                    $includeZero = (isset($vars['includeZero'][1]) && $vars['includeZero'][1]) ? 'true' : 'false';
496 69
                    return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]}, {$includeZero})){$context['ops']['cnd_then']}";
0 ignored issues
show
Bug introduced by
It seems like $v[1] can also be of type array; however, LightnCandy\Compiler::getFuncName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug Best Practice introduced by
The return type of return "{$context['ops']...t['ops']['cnd_then']}"; (string) is incompatible with the return type of the parent method LightnCandy\Validator::blockBegin of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
497 152
                case 'unless':
498 4
                    return "{$context['ops']['cnd_start']}(!" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]}, false)){$context['ops']['cnd_then']}";
0 ignored issues
show
Bug introduced by
It seems like $v[1] can also be of type array; however, LightnCandy\Compiler::getFuncName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug Best Practice introduced by
The return type of return "{$context['ops']...t['ops']['cnd_then']}"; (string) is incompatible with the return type of the parent method LightnCandy\Validator::blockBegin of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
499 149
                case 'each':
500 47
                    return static::section($context, $vars, true);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|in...er|string|array|null"}>, but the function expects a array<integer,boolean|integer|string|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug Best Practice introduced by
The return type of return static::section($context, $vars, true); (string) is incompatible with the return type of the parent method LightnCandy\Validator::blockBegin of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
501 104
                case 'with':
502 28
                    if ($r = static::with($context, $vars)) {
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|in...er|string|array|null"}>, but the function expects a array<integer,boolean|integer|string|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
503 28
                        return $r;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $r; (string) is incompatible with the return type of the parent method LightnCandy\Validator::blockBegin of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
504
                    }
505
            }
506
        }
507
508 116
        return static::section($context, $vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|in...er|string|array|null"}>, but the function expects a array<integer,boolean|integer|string|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug Best Practice introduced by
The return type of return static::section($context, $vars); (string) is incompatible with the return type of the parent method LightnCandy\Validator::blockBegin of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
509
    }
510
511
    /**
512
     * compile {{#foo}} token
513
     *
514
     * @param array<string,array|string|integer> $context current compile context
515
     * @param array<boolean|integer|string|array> $vars parsed arguments list
516
     * @param boolean $isEach the section is #each
517
     *
518
     * @return string|null Return compiled code segment for the token
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
519
     */
520 163
    protected static function section(&$context, $vars, $isEach = false) {
521 163
        $bs = 'null';
522 163
        $be = '';
523 163
        if ($isEach) {
524 47
            $bp = Parser::getBlockParams($vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|integer|string|array>, but the function expects a array<integer,boolean|integer|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
525 47
            $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null';
526 47
            $be = $bp ? " as |$bp[0] $bp[1]|" : '';
527 47
            array_shift($vars);
528
        }
529 163
        if ($context['flags']['lambda'] && !$isEach) {
530 69
            $V = array_shift($vars);
531 69
            $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array('')));
532
        } else {
533 94
            $v = static::getVariableNameOrSubExpression($context, $vars[0]);
534
        }
535 163
        $each = $isEach ? 'true' : 'false';
536 163
        return $context['ops']['seperator'] . static::getFuncName($context, 'sec', ($isEach ? 'each ' : '') . $v[1] . $be) . "\$cx, {$v[0]}, $bs, \$in, $each, function(\$cx, \$in) {{$context['ops']['f_start']}";
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $context['ops']['...xt['ops']['f_start']}"; (string) is incompatible with the return type of the parent method LightnCandy\Validator::section of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
537
    }
538
539
    /**
540
     * compile {{with}} token
541
     *
542
     * @param array<string,array|string|integer> $context current compile context
543
     * @param array<boolean|integer|string|array> $vars parsed arguments list
544
     *
545
     * @return string|null Return compiled code segment for the token
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
546
     */
547 28
    protected static function with(&$context, $vars) {
548 28
        $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array());
549 28
        $bp = Parser::getBlockParams($vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer,boolean|in...er|string|array|null"}>, but the function expects a array<integer,boolean|integer|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
550 28
        $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null';
551 28
        $be = $bp ? " as |$bp[0]|" : '';
552 28
        return $context['ops']['seperator'] . static::getFuncName($context, 'wi', 'with ' . $v[1] . $be) . "\$cx, {$v[0]}, $bs, \$in, function(\$cx, \$in) {{$context['ops']['f_start']}";
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $context['ops']['...xt['ops']['f_start']}"; (string) is incompatible with the return type of the parent method LightnCandy\Validator::with of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
553
    }
554
555
    /**
556
     * Return compiled PHP code for a handlebars custom helper token
557
     *
558
     * @param array<string,array|string|integer> $context current compile context
559
     * @param array<boolean|integer|string|array> $vars parsed arguments list
560
     * @param boolean $raw is this {{{ token or not
561
     * @param boolean $nosep true to compile without seperator
562
     *
563
     * @return string|null Return compiled code segment for the token when the token is custom helper
564
     */
565 463
    protected static function customHelper(&$context, $vars, $raw, $nosep) {
566 463
        if (!isset($context['helpers'][$vars[0][0]])) {
567 349
            return;
568
        }
569
570 127
        $fn = $raw ? 'raw' : $context['ops']['enc'];
571 127
        $ch = array_shift($vars);
572 127
        $v = static::getVariableNames($context, $vars);
573 127
        static::addUsageCount($context, 'helpers', $ch[0]);
574 127
        $sep = $nosep ? '' : $context['ops']['seperator'];
575
576 127
        return $sep . static::getFuncName($context, 'hbch', "$ch[0] " . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, '$fn', \$in)$sep";
577
    }
578
579
    /**
580
     * Return compiled PHP code for a handlebars else token
581
     *
582
     * @param array<string,array|string|integer> $context current compile context
583
     * @param array<boolean|integer|string|array> $vars parsed arguments list
584
     *
585
     * @return string Return compiled code segment for the token when the token is else
586
     */
587 52
    protected static function doElse(&$context, $vars) {
588 52
        switch ($context['stack'][count($context['stack']) - 2]) {
589 52
            case '[if]':
590 29
            case '[unless]':
591 26
                $context['stack'][] = ':';
592 26
                return "{$context['ops']['cnd_else']}";
593
            default:
594 29
                return "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['f_start']}";
595
        }
596
    }
597
598
    /**
599
     * Return compiled PHP code for a handlebars log token
600
     *
601
     * @param array<string,array|string|integer> $context current compile context
602
     * @param array<boolean|integer|string|array> $vars parsed arguments list
603
     * @param boolean $raw is this {{{ token or not
604
     *
605
     * @return string Return compiled code segment for the token
606
     */
607 2
    protected static function compileLog(&$context, &$vars, $raw) {
0 ignored issues
show
Unused Code introduced by
The parameter $raw is not used and could be removed.

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

Loading history...
608 2
        array_shift($vars);
609 2
        $v = static::getVariableNames($context, $vars);
610 2
        return $context['ops']['seperator'] . static::getFuncName($context, 'lo', $v[1]) . "\$cx, {$v[0]}){$context['ops']['seperator']}";
0 ignored issues
show
Documentation introduced by
$v[1] is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
611
    }
612
613
    /**
614
     * Return compiled PHP code for a handlebars lookup token
615
     *
616
     * @param array<string,array|string|integer> $context current compile context
617
     * @param array<boolean|integer|string|array> $vars parsed arguments list
618
     * @param boolean $raw is this {{{ token or not
619
     *
620
     * @return string Return compiled code segment for the token
621
     */
622 2
    protected static function compileLookup(&$context, &$vars, $raw) {
623 2
        $v2 = static::getVariableName($context, $vars[2]);
624 2
        $v = static::getVariableName($context, $vars[1], $v2);
0 ignored issues
show
Documentation introduced by
$v2 is of type array<integer,array|stri...rray<integer,string>"}>, but the function expects a array<integer,string>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
625 2
        if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug']) {
626 2
            return $context['ops']['seperator'] . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $v[1]) . "\$cx, {$v[0]}){$context['ops']['seperator']}";
0 ignored issues
show
Bug introduced by
It seems like $v[1] can also be of type array<integer,string>; however, LightnCandy\Compiler::getFuncName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
627
        } else {
628
            return $raw ? "{$context['ops']['seperator']}$v[0]{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlentities((string){$v[0]}, ENT_QUOTES, 'UTF-8'){$context['ops']['seperator']}";
629
        }
630
    }
631
632
    /**
633
     * Return compiled PHP code for template output
634
     *
635
     * @param array<string,array|string|integer> $context current compile context
636
     * @param string $variable PHP code for the variable
637
     * @param string $expression normalized handlebars expression
638
     * @param boolean $raw is this {{{ token or not
639
     * @param boolean $nosep true to compile without seperator
640
     *
641
     * @return string Return compiled code segment for the token
642
     */
643 469
    protected static function compileOutput(&$context, $variable, $expression, $raw, $nosep) {
644 469
        $sep = $nosep ? '' : $context['ops']['seperator'];
645 469
        if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug'] || $nosep) {
646 423
            return $sep . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $expression) . "\$cx, $variable)$sep";
647
        } else {
648 46
            return $raw ? "$sep$variable{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlentities((string)$variable, ENT_QUOTES, 'UTF-8')$sep";
649
        }
650
    }
651
652
    /**
653
     * Return compiled PHP code for a handlebars variable token
654
     *
655
     * @param array<string,array|string|integer> $context current compile context
656
     * @param array<boolean|integer|string|array> $vars parsed arguments list
657
     * @param boolean $raw is this {{{ token or not
658
     * @param boolean $nosep true to compile without seperator
659
     *
660
     * @return string Return compiled code segment for the token
661
     */
662 365
    protected static function compileVariable(&$context, &$vars, $raw, $nosep) {
663 365
        if ($context['flags']['lambda']) {
664 227
            $V = array_shift($vars);
665 227
            $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array('')));
666
        } else {
667 138
            $v = static::getVariableName($context, $vars[0]);
668
        }
669 365
        return static::compileOutput($context, $v[0], $v[1], $raw, $nosep);
0 ignored issues
show
Bug introduced by
It seems like $v[1] can also be of type array<integer,string>; however, LightnCandy\Compiler::compileOutput() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
670
    }
671
672
    /**
673
     * Add usage count to context
674
     *
675
     * @param array<string,array|string|integer> $context current context
676
     * @param string $category category name, can be one of: 'var', 'helpers', 'runtime'
677
     * @param string $name used name
678
     * @param integer $count increment
679
     *
680
     * @expect 1 when input array('usedCount' => array('test' => array())), 'test', 'testname'
681
     * @expect 3 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname'
682
     * @expect 5 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname', 3
683
     */
684 602
    protected static function addUsageCount(&$context, $category, $name, $count = 1) {
685 602
        if (!isset($context['usedCount'][$category][$name])) {
686 602
            $context['usedCount'][$category][$name] = 0;
687
        }
688 602
        return ($context['usedCount'][$category][$name] += $count);
689
    }
690
}
691
692