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 ( 096e96...a1f699 )
by Zordius
19:00
created

Compiler::partial()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 19
ccs 14
cts 14
cp 1
rs 8.8571
cc 6
eloc 15
nc 13
nop 2
crap 6
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-2015 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 717
    public static function compileTemplate(&$context, $template) {
44 717
        array_unshift($context['parsed'], array());
45 717
        Validator::verify($context, $template);
46
47 717
        if (count($context['error'])) {
48 70
            return;
49
        }
50
51
        // Do PHP code generation.
52 648
        Parser::setDelimiter($context);
53
54
        // Handle dynamic partials
55 648
        Partial::handleDynamic($context);
56
57 648
        $code = '';
58 648
        foreach ($context['parsed'][0] as $info) {
59 648
            if (is_array($info)) {
60 607
                $context['tokens']['current']++;
61 607
                $tmpl = static::compileToken($context, $info);
62 607
                if ($tmpl == $context['ops']['seperator']) {
63 1
                    $tmpl = '';
64
                } else {
65 606
                    $tmpl = "'$tmpl'";
66
                }
67 607
                $code .= $tmpl;
68
            } else {
69 648
                $code .= $info;
70
            }
71
        }
72
73 648
        static::$lastParsed = array_shift($context['parsed']);
74
75 648
        return $code;
76
    }
77
78
    /**
79
     * Compose LightnCandy render codes for include()
80
     *
81
     * @param array<string,array|string|integer> $context Current context
82
     * @param string $code generated PHP code
83
     *
84
     * @return string Composed PHP code
85
     */
86 647
    public static function composePHPRender($context, $code) {
87 647
        $flagJStrue = Expression::boolString($context['flags']['jstrue']);
88 647
        $flagJSObj = Expression::boolString($context['flags']['jsobj']);
89 647
        $flagSPVar = Expression::boolString($context['flags']['spvar']);
90 647
        $flagProp = Expression::boolString($context['flags']['prop']);
91 647
        $flagMethod = Expression::boolString($context['flags']['method']);
92 647
        $flagLambda = Expression::boolString($context['flags']['lambda']);
93 647
        $flagMustlok = Expression::boolString($context['flags']['mustlok']);
94 647
        $flagMustlam = Expression::boolString($context['flags']['mustlam']);
95 647
        $flagEcho = Expression::boolString($context['flags']['echo']);
96 647
        $flagPartNC = Expression::boolString($context['flags']['partnc']);
97 647
        $flagKnownHlp = Expression::boolString($context['flags']['knohlp']);
98
99 647
        $constants = Exporter::constants($context);
100 647
        $helpers = Exporter::helpers($context);
101 647
        $partials = implode(",\n", $context['partialCode']);
102 647
        $debug = Runtime::DEBUG_ERROR_LOG;
103 647
        $use = $context['flags']['standalone'] ? Exporter::runtime($context) : "use {$context['runtime']} as LR;";
104
105
        // Return generated PHP code string.
106 647
        return "use {$context['safestring']} as SafeString;{$use}return function (\$in, \$options = null) {
107
    \$helpers = $helpers;
108
    \$partials = array($partials);
109
    \$cx = array(
110
        'flags' => array(
111
            'jstrue' => $flagJStrue,
112
            'jsobj' => $flagJSObj,
113
            'spvar' => $flagSPVar,
114
            'prop' => $flagProp,
115
            'method' => $flagMethod,
116
            'lambda' => $flagLambda,
117
            'mustlok' => $flagMustlok,
118
            'mustlam' => $flagMustlam,
119
            'echo' => $flagEcho,
120
            'partnc' => $flagPartNC,
121
            'knohlp' => $flagKnownHlp,
122
            'debug' => isset(\$options['debug']) ? \$options['debug'] : $debug,
123
        ),
124
        '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 647
        'sp_vars' => isset(\$options['data']) ? array_merge(array('root' => \$in), \$options['data']) : array('root' => \$in),
129
        'blparam' => array(),
130 647
        'runtime' => '{$context['runtime']}',
131 647
    );
132 647
    {$context['renderex']}
133
    {$context['ops']['op_start']}'$code'{$context['ops']['op_end']}
134
};";
135
    }
136
137
    /**
138
     * Get function name for standalone or none standalone template.
139
     *
140
     * @param array<string,array|string|integer> $context Current context of compiler progress.
141
     * @param string $name base function name
142
     * @param string $tag original handlabars tag for debug
143
     *
144
     * @return string compiled Function name
145
     *
146
     * @expect 'LR::test(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime'), 'test', ''
147
     * @expect 'LR::test2(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'runtime' => 'Runtime'), 'test2', ''
148
     * @expect "lala_abctest3(" when input array('flags' => array('standalone' => 1, 'debug' => 0), 'runtime' => 'Runtime', 'funcprefix' => 'lala_abc'), 'test3', ''
149 583
     * @expect 'LR::debug(\'abc\', \'test\', ' when input array('flags' => array('standalone' => 0, 'debug' => 1), 'runtime' => 'Runtime', 'funcprefix' => 'haha456'), 'test', 'abc'
150 583
     */
151
    protected static function getFuncName(&$context, $name, $tag) {
152 583
        static::addUsageCount($context, 'runtime', $name);
153 10
154 10
        if ($context['flags']['debug'] && ($name != 'miss')) {
155 10
            $dbg = "'$tag', '$name', ";
156
            $name = 'debug';
157 581
            static::addUsageCount($context, 'runtime', 'debug');
158
        } else {
159
            $dbg = '';
160 583
        }
161
162
        return $context['flags']['standalone'] ? "{$context['funcprefix']}$name($dbg" : "LR::$name($dbg";
163
    }
164
165
    /**
166
     * Get string presentation of variables
167
     *
168
     * @param array<string,array|string|integer> $context current compile context
169
     * @param array<array> $vn variable name array.
170
     * @param array<string>|null $blockParams block param list
171
     *
172
     * @return array<string|array> variable names
173
     *
174
     * @expect array('array(array($in),array())', array('this')) when input array('flags'=>array('spvar'=>true)), array(null)
175
     * @expect array('array(array($in,$in),array())', array('this', 'this')) when input array('flags'=>array('spvar'=>true)), array(null, null)
176 256
     * @expect array('array(array(),array(\'a\'=>$in))', array('this')) when input array('flags'=>array('spvar'=>true)), array('a' => null)
177 256
     */
178 256
    protected static function getVariableNames(&$context, $vn, $blockParams = null) {
179 256
        $vars = array(array(), array());
180 218
        $exps = array();
181 218
        foreach ($vn as $i => $v) {
182 36
            $V = static::getVariableNameOrSubExpression($context, $v);
183
            if (is_string($i)) {
184 204
                $vars[1][] = "'$i'=>{$V[0]}";
185
            } else {
186 218
                $vars[0][] = $V[0];
187
            }
188 256
            $exps[] = $V[1];
189 256
        }
190
        $bp = $blockParams ? (',array(' . Expression::listString($blockParams) . ')') : '';
191
        return array('array(array(' . implode(',', $vars[0]) . '),array(' . implode(',', $vars[1]) . ")$bp)", $exps);
192
    }
193
194
    /**
195
     * Get string presentation of a sub expression
196
     *
197
     * @param array<string,array|string|integer> $context current compile context
198
     * @param array<boolean|integer|string|array> $vars parsed arguments list
199
     *
200 41
     * @return array<string> code representing passed expression
201 41
     */
202
    public static function compileSubExpression(&$context, $vars) {
203 41
        $ret = static::customHelper($context, $vars, true, true);
204 4
205
        if (($ret === null) && $context['flags']['lambda']) {
206
            $ret = static::compileVariable($context, $vars, true, true);
207 41
        }
208
209
        return array($ret ? $ret : '', 'FIXME: $subExpression');
210
    }
211
212
    /**
213
     * Get string presentation of a subexpression or a variable
214
     *
215
     * @param array<array|string|integer> $context current compile context
216
     * @param array<array|string|integer> $var variable parsed path
217
     *
218 364
     * @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...
219 364
     */
220
    protected static function getVariableNameOrSubExpression(&$context, $var) {
221
        return Parser::isSubExp($var) ? static::compileSubExpression($context, $var[1]) : static::getVariableName($context, $var);
222
    }
223
224
    /**
225
     * Get string presentation of a variable
226
     *
227
     * @param array<array|string|integer> $var variable parsed path
228
     * @param array<array|string|integer> $context current compile context
229
     * @param array<string>|null $lookup extra lookup string as valid PHP variable name
230
     *
231
     * @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...
232
     *
233
     * @expect array('$in', 'this') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(null)
234
     * @expect array('((isset($in[\'true\']) && is_array($in)) ? $in[\'true\'] : null)', '[true]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0, 'lambda'=>0)), array('true')
235
     * @expect array('((isset($in[\'false\']) && is_array($in)) ? $in[\'false\'] : null)', '[false]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0, 'lambda'=>0)), array('false')
236
     * @expect array('true', 'true') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'true')
237
     * @expect array('false', 'false') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, 'false')
238
     * @expect array('((isset($in[\'2\']) && is_array($in)) ? $in[\'2\'] : null)', '[2]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0, 'lambda'=>0)), array('2')
239
     * @expect array('2', '2') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0)), array(-1, '2')
240
     * @expect array('((isset($in[\'@index\']) && is_array($in)) ? $in[\'@index\'] : null)', '[@index]') when input array('flags'=>array('spvar'=>false,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0, 'lambda'=>0)), array('@index')
241
     * @expect array("((isset(\$cx['sp_vars']['index']) && is_array(\$cx['sp_vars'])) ? \$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)), array('@index')
242
     * @expect array("((isset(\$cx['sp_vars']['key']) && is_array(\$cx['sp_vars'])) ? \$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)), array('@key')
243
     * @expect array("((isset(\$cx['sp_vars']['first']) && is_array(\$cx['sp_vars'])) ? \$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)), array('@first')
244
     * @expect array("((isset(\$cx['sp_vars']['last']) && is_array(\$cx['sp_vars'])) ? \$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)), array('@last')
245
     * @expect array('((isset($in[\'"a"\']) && is_array($in)) ? $in[\'"a"\'] : null)', '["a"]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0, 'lambda'=>0)), array('"a"')
246
     * @expect array('"a"', '"a"') when input array('flags'=>array('spvar'=>true,'debug'=>0)), array(-1, '"a"')
247
     * @expect array('((isset($in[\'a\']) && is_array($in)) ? $in[\'a\'] : null)', '[a]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0, 'lambda'=>0)), array('a')
248
     * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-1])) ? $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)), array(1,'a')
249
     * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-3])) ? $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)), array(3,'a')
250
     * @expect array('((isset($in[\'id\']) && is_array($in)) ? $in[\'id\'] : null)', 'this.[id]') when input array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0,'mustlam'=>0, 'lambda'=>0)), array(null, 'id')
251 579
     * @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,'standalone'=>0), 'runtime' => 'Runtime'), array(null, 'id')
252 579
     */
253 79
    protected static function getVariableName(&$context, $var, $lookup = null, $args = null) {
254 2
        if (isset($var[0]) && ($var[0] === Parser::LITERAL)) {
255
            if ($var[1] === "undefined") {
256 79
                $var[1] = "null";
257
            }
258
            return array($var[1], preg_replace('/\'(.*)\'/', '$1', $var[1]));
259 540
        }
260 540
261 540
        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...
262
        $exp = Expression::toString($levels, $spvar, $var);
263
        $base = $spvar ? "\$cx['sp_vars']" : '$in';
264 540
265 39
        // change base when trace to parent
266 2
        if ($levels > 0) {
267
            if ($spvar) {
268 37
                $base .= str_repeat("['_parent']", $levels);
269
            } else {
270
                $base = "\$cx['scopes'][count(\$cx['scopes'])-$levels]";
271
            }
272 540
        }
273 132
274
        if ((count($var) == 0) || (($var[0] === null) && (count($var) == 1))) {
275
            return array($base, $exp);
276 485
        }
277 1
278
        if ($var[0] === null) {
279
            array_shift($var);
280
        }
281
282 485
        // To support recursive context lookup, instance properties + methods and lambdas
283 386
        // the only way is using slower rendering time variable resolver.
284 386
        if ($context['flags']['prop'] || $context['flags']['method'] || $context['flags']['mustlok'] || $context['flags']['mustlam'] || $context['flags']['lambda']) {
285 386
            $L = $lookup ? ", $lookup[0]" : '';
286 386
            $A = $args ? ",$args[0]" : '';
287
            $E = $args ? ' ' . implode(' ', $args[1]) : '';
288
            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");
289 100
        }
290 100
291 100
        $n = Expression::arrayString($var);
292 100
        array_pop($var);
293
        $L = $lookup ? "[{$lookup[0]}]" : '';
294 100
        $p = $lookup ? $n : (count($var) ? Expression::arrayString($var) : '');
295
296
        return array("((isset($base$n$L) && is_array($base$p)) ? $base$n$L : " . ($context['flags']['debug'] ? (static::getFuncName($context, 'miss', '') . "\$cx, '$exp')") : 'null' ) . ')', $lookup ? "lookup $exp $lookup[1]" : $exp);
297
    }
298
299
    /**
300
     * Return compiled PHP code for a handlebars token
301
     *
302
     * @param array<string,array|string|integer> $context current compile context
303
     * @param array<string,array|boolean> $info parsed information
304
     *
305 607
     * @return string Return compiled code segment for the token
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean|integer|double?

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...
306 607
     */
307
    protected static function compileToken(&$context, $info) {
308 607
        list($raw, $vars, $token, $indent) = $info;
309 607
310
        $context['tokens']['partialind'] = $indent;
311
        $context['currentToken'] = $token;
312 607
313
        // Do not touch the tag, keep it as is.
314
        if ($raw === -1) {
315
            return ".'" . Token::toString($token) . "'.";
316 607
        }
317 372
318
        if ($ret = static::operator($token[Token::POS_OP], $context, $vars)) {
319
            return $ret;
320 472
        }
321 440
322 112
        if (isset($vars[0][0])) {
323
            if ($ret = static::customHelper($context, $vars, $raw, true)) {
324 335
                return static::compileOutput($context, $ret, 'FIXME: helper', $raw, false);
325 25
            }
326
            if ($vars[0][0] === 'else') {
327 322
                return static::doElse($context, $vars);
328 2
            }
329
            if ($vars[0][0] === 'lookup') {
330 320
                return static::compileLookup($context, $vars, $raw);
331 1
            }
332
            if ($vars[0][0] === 'log') {
333
                return static::compileLog($context, $vars, $raw);
334
            }
335 356
        }
336
337
        return static::compileVariable($context, $vars, $raw, false);
338
    }
339
340
    /**
341
     * handle partial
342
     *
343
     * @param array<string,array|string|integer> $context current compile context
344
     * @param array<boolean|integer|string|array> $vars parsed arguments list
345
     *
346 86
     * @return string Return compiled code segment for the partial
347
     */
348 86
    public static function partial(&$context, $vars) {
349 1
        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...
350
        $p = array_shift($vars);
351 85
        if ($context['flags']['runpart']) {
352 85
            if (!isset($vars[0])) {
353 85
                $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array();
354 77
            }
355 70
            $v = static::getVariableNames($context, $vars);
356
            $tag = ">$p[0] " .implode(' ', $v[1]);
357 77
            if (Parser::isSubExp($p)) {
358 77
                list($p) = static::compileSubExpression($context, $p[1]);
359 77
            } else {
360 4
                $p = "'$p[0]'";
361
            }
362 73
            $sp = $context['tokens']['partialind'] ? ", '{$context['tokens']['partialind']}'" : '';
363
            return $context['ops']['seperator'] . static::getFuncName($context, 'p', $tag) . "\$cx, $p, $v[0]$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...
364 77
        }
365 77
        return "{$context['ops']['seperator']}'" . Partial::compileStatic($context, $p[0]) . "'{$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...
366
    }
367 8
368
    /**
369
     * handle inline partial
370
     *
371
     * @param array<string,array|string|integer> $context current compile context
372
     * @param array<boolean|integer|string|array> $vars parsed arguments list
373
     *
374
     * @return string Return compiled code segment for the partial
375
     */
376
    public static function inline(&$context, $vars) {
377
        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...
378 10
        list($code) = array_shift($vars);
379 10
        $p = array_shift($vars);
380 10
        if (!isset($vars[0])) {
381 10
            $vars[0] = $context['flags']['partnc'] ? array(0, 'null') : array();
382 10
        }
383 10
        $v = static::getVariableNames($context, $vars);
384
        $tag = ">*inline $p[0]" .implode(' ', $v[1]);
385 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...
386 10
    }
387 10
388
    /**
389
     * Return compiled PHP code for a handlebars inverted section begin token
390
     *
391
     * @param array<string,array|string|integer> $context current compile context
392
     * @param array<boolean|integer|string|array> $vars parsed arguments list
393
     *
394
     * @return string Return compiled code segment for the token
395
     */
396
    protected static function invertedSection(&$context, $vars) {
397
        $v = static::getVariableName($context, $vars[0]);
398 38
        return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'isec', '^' . $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}";
399 38
    }
400 38
401
    /**
402
     * Return compiled PHP code for a handlebars block custom helper begin token
403
     *
404
     * @param array<string,array|string|integer> $context current compile context
405
     * @param array<boolean|integer|string|array> $vars parsed arguments list
406
     * @param boolean $inverted the logic will be inverted
407
     *
408
     * @return string Return compiled code segment for the token
409
     */
410
    protected static function blockCustomHelper(&$context, $vars, $inverted = false) {
411
        $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...
412 58
        $ch = array_shift($vars);
413 58
        $inverted = $inverted ? 'true' : 'false';
414 58
        static::addUsageCount($context, 'helpers', $ch[0]);
415 58
        $v = static::getVariableNames($context, $vars, $bp);
416 58
417 58
        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']}";
418
    }
419 58
420
    /**
421
     * Return compiled PHP code for a handlebars block end token
422
     *
423
     * @param array<string,array|string|integer> $context current compile context
424
     * @param array<boolean|integer|string|array> $vars parsed arguments list
425
     * @param string|null $matchop should also match to this operator
426
     *
427
     * @return string Return compiled code segment for the token
428
     */
429
    protected static function blockEnd(&$context, &$vars, $matchop = NULL) {
430
        $pop = $context['stack'][count($context['stack']) - 1];
431 316
        $elsifend = isset($vars[0][-1]) ? str_repeat($context['ops']['cnd_nend'], $vars[0][-1]) : '';
432 316
        switch ($context['currentToken'][Token::POS_INNERTAG]) {
433 316
            case 'if':
434 316
            case 'unless':
435
                if ($pop === ':') {
436
                    array_pop($context['stack']);
437 69
                    return "$elsifend{$context['ops']['cnd_end']}";
438 19
                }
439 19
                if (!$context['flags']['nohbh']) {
440
                    return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
441 50
                }
442 48
                break;
443
            case 'with':
444 2
                if (!$context['flags']['nohbh']) {
445 236
                    return "$elsifend{$context['ops']['f_end']}}){$context['ops']['seperator']}";
446 32
                }
447 31
        }
448
449
        if ($pop === ':') {
450
            array_pop($context['stack']);
451 239
            return "$elsifend{$context['ops']['f_end']}}){$context['ops']['seperator']}";
452 2
        }
453 2
454
        if ($elsifend !== '') {
455
            $elsifend = "{$context['ops']['cnd_else']}''$elsifend";
456 237
        }
457 3
458
        switch($pop) {
459
            case '#':
460
                return "$elsifend{$context['ops']['f_end']}}){$context['ops']['seperator']}";
461 237
            case '^':
462 212
                return "$elsifend{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
463
        }
464 33
    }
465
466
    /**
467
     * Return compiled PHP code for a handlebars block begin token
468
     *
469
     * @param array<string,array|string|integer> $context current compile context
470
     * @param array<boolean|integer|string|array> $vars parsed arguments list
471
     *
472
     * @return string Return compiled code segment for the token
473
     */
474
    protected static function blockBegin(&$context, $vars) {
475
        $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array());
476 235
        if (!$context['flags']['nohbh']) {
477 235
            switch (isset($vars[0][0]) ? $vars[0][0] : null) {
478 235
                case 'if':
479 197
                    $includeZero = (isset($vars['includeZero'][1]) && $vars['includeZero'][1]) ? 'true' : 'false';
480 4
                    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...
481 64
                case 'unless':
482 64
                    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...
483 4
                case 'each':
484 4
                    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...
485 4
                case 'with':
486 47
                    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...
487 77
                        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...
488 27
                    }
489 27
            }
490
        }
491
492
        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...
493
    }
494 115
495
    /**
496
     * compile {{#foo}} token
497
     *
498
     * @param array<string,array|string|integer> $context current compile context
499
     * @param array<boolean|integer|string|array> $vars parsed arguments list
500
     * @param boolean $isEach the section is #each
501
     *
502
     * @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...
503
     */
504
    protected static function section(&$context, $vars, $isEach = false) {
505
        $bs = 'null';
506 162
        $be = '';
507 162
        if ($isEach) {
508 162
            $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...
509 162
            $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null';
510 47
            $be = $bp ? " as |$bp[0] $bp[1]|" : '';
511 47
            array_shift($vars);
512 47
            if (!isset($vars[0])) {
513 47
                $vars[0] = array(null);
514 47
            }
515
        }
516
        if ($context['flags']['lambda'] && !$isEach) {
517
            $V = array_shift($vars);
518 162
            $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array('')));
519 69
        } else {
520 69
            $v = static::getVariableNameOrSubExpression($context, $vars[0]);
521
        }
522 93
        $each = $isEach ? 'true' : 'false';
523
        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...
524 162
    }
525 162
526
    /**
527
     * compile {{with}} token
528
     *
529
     * @param array<string,array|string|integer> $context current compile context
530
     * @param array<boolean|integer|string|array> $vars parsed arguments list
531
     *
532
     * @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...
533
     */
534
    protected static function with(&$context, $vars) {
535
        $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($context, $vars[1]) : array(null, array());
536 27
        $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...
537 27
        $bs = $bp ? ('array(' . Expression::listString($bp) . ')') : 'null';
538 27
        $be = $bp ? " as |$bp[0]|" : '';
539 27
        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...
540 27
    }
541 27
542
    /**
543
     * Return compiled PHP code for a handlebars custom helper token
544
     *
545
     * @param array<string,array|string|integer> $context current compile context
546
     * @param array<boolean|integer|string|array> $vars parsed arguments list
547
     * @param boolean $raw is this {{{ token or not
548
     * @param boolean $nosep true to compile without seperator
549
     *
550
     * @return string|null Return compiled code segment for the token when the token is custom helper
551
     */
552
    protected static function customHelper(&$context, $vars, $raw, $nosep) {
553
        if (!isset($context['helpers'][$vars[0][0]])) {
554 448
            return;
555 448
        }
556 338
557
        $fn = $raw ? 'raw' : $context['ops']['enc'];
558
        $ch = array_shift($vars);
559 123
        $v = static::getVariableNames($context, $vars);
560 123
        static::addUsageCount($context, 'helpers', $ch[0]);
561 123
        $sep = $nosep ? '' : $context['ops']['seperator'];
562 123
563 123
        return $sep . static::getFuncName($context, 'hbch', "$ch[0] " . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, '$fn', \$in)$sep";
564
    }
565 123
566
    /**
567
     * Return compiled PHP code for a handlebars else token
568
     *
569
     * @param array<string,array|string|integer> $context current compile context
570
     * @param array<boolean|integer|string|array> $vars parsed arguments list
571
     *
572
     * @return string Return compiled code segment for the token when the token is else
573
     */
574
    protected static function doElse(&$context, $vars) {
575
        switch ($context['stack'][count($context['stack']) - 2]) {
576 47
            case '[if]':
577 47
            case '[unless]':
578
                $context['stack'][] = ':';
579 28
                return "{$context['ops']['cnd_else']}";
580 22
            default:
581 22
                return "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['f_start']}";
582
        }
583 28
    }
584
585
    /**
586
     * Return compiled PHP code for a handlebars log token
587
     *
588
     * @param array<string,array|string|integer> $context current compile context
589
     * @param array<boolean|integer|string|array> $vars parsed arguments list
590
     * @param boolean $raw is this {{{ token or not
591
     *
592
     * @return string Return compiled code segment for the token
593
     */
594
    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...
595
        array_shift($vars);
596 1
        $v = static::getVariableNames($context, $vars);
597 1
        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...
598 1
    }
599 1
600
    /**
601
     * Return compiled PHP code for a handlebars lookup token
602
     *
603
     * @param array<string,array|string|integer> $context current compile context
604
     * @param array<boolean|integer|string|array> $vars parsed arguments list
605
     * @param boolean $raw is this {{{ token or not
606
     *
607
     * @return string Return compiled code segment for the token
608
     */
609
    protected static function compileLookup(&$context, &$vars, $raw) {
610
        $v2 = static::getVariableName($context, $vars[2]);
611 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...
612 2
        if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug']) {
613 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...
614 2
        } else {
615 2
            return $raw ? "{$context['ops']['seperator']}$v[0]{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlentities((string){$v[0]}, ENT_QUOTES, 'UTF-8'){$context['ops']['seperator']}";
616
        }
617
    }
618
619
    /**
620
     * Return compiled PHP code for template output
621
     *
622
     * @param array<string,array|string|integer> $context current compile context
623
     * @param string $variable PHP code for the variable
624
     * @param string $expression normalized handlebars expression
625
     * @param boolean $raw is this {{{ token or not
626
     * @param boolean $nosep true to compile without seperator
627
     *
628
     * @return string Return compiled code segment for the token
629
     */
630
    protected static function compileOutput(&$context, $variable, $expression, $raw, $nosep) {
631
        $sep = $nosep ? '' : $context['ops']['seperator'];
632 459
        if ($context['flags']['hbesc'] || $context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug'] || $nosep) {
633 459
            return $sep . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $expression) . "\$cx, $variable)$sep";
634 459
        } else {
635 416
            return $raw ? "$sep$variable{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlentities((string)$variable, ENT_QUOTES, 'UTF-8')$sep";
636
        }
637 43
    }
638
639
    /**
640
     * Return compiled PHP code for a handlebars variable token
641
     *
642
     * @param array<string,array|string|integer> $context current compile context
643
     * @param array<boolean|integer|string|array> $vars parsed arguments list
644
     * @param boolean $raw is this {{{ token or not
645
     * @param boolean $nosep true to compile without seperator
646
     *
647
     * @return string Return compiled code segment for the token
648
     */
649
    protected static function compileVariable(&$context, &$vars, $raw, $nosep) {
650
        if ($context['flags']['lambda']) {
651 359
            $V = array_shift($vars);
652 359
            $v = static::getVariableName($context, $V, null, count($vars) ? static::getVariableNames($context, $vars) : array('0',array('')));
653 227
        } else {
654 227
            $v = static::getVariableName($context, $vars[0]);
655
        }
656 132
        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...
657
    }
658 359
659
    /**
660
     * Add usage count to context
661
     *
662
     * @param array<string,array|string|integer> $context current context
663
     * @param string $category category name, can be one of: 'var', 'helpers', 'runtime'
664
     * @param string $name used name
665
     * @param integer $count increment
666
     *
667
     * @expect 1 when input array('usedCount' => array('test' => array())), 'test', 'testname'
668
     * @expect 3 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname'
669
     * @expect 5 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname', 3
670
     */
671
    protected static function addUsageCount(&$context, $category, $name, $count = 1) {
672
        if (!isset($context['usedCount'][$category][$name])) {
673 583
            $context['usedCount'][$category][$name] = 0;
674 583
        }
675 583
        return ($context['usedCount'][$category][$name] += $count);
676
    }
677 583
}
678
679