Twig_Extension_Core::setDateFormat()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.4285
cc 3
eloc 5
nc 4
nop 2
1
<?php
2
3
if (!defined('ENT_SUBSTITUTE')) {
4
    // use 0 as hhvm does not support several flags yet
5
    define('ENT_SUBSTITUTE', 0);
6
}
7
8
/*
9
 * This file is part of Twig.
10
 *
11
 * (c) 2009 Fabien Potencier
12
 *
13
 * For the full copyright and license information, please view the LICENSE
14
 * file that was distributed with this source code.
15
 */
16
class Twig_Extension_Core extends Twig_Extension
17
{
18
    protected $dateFormats = array('F j, Y H:i', '%d days');
19
    protected $numberFormat = array(0, '.', ',');
20
    protected $timezone = null;
21
    protected $escapers = array();
22
23
    /**
24
     * Defines a new escaper to be used via the escape filter.
25
     *
26
     * @param string   $strategy The strategy name that should be used as a strategy in the escape call
27
     * @param callable $callable A valid PHP callable
28
     */
29
    public function setEscaper($strategy, $callable)
30
    {
31
        $this->escapers[$strategy] = $callable;
32
    }
33
34
    /**
35
     * Gets all defined escapers.
36
     *
37
     * @return array An array of escapers
38
     */
39
    public function getEscapers()
40
    {
41
        return $this->escapers;
42
    }
43
44
    /**
45
     * Sets the default format to be used by the date filter.
46
     *
47
     * @param string $format             The default date format string
48
     * @param string $dateIntervalFormat The default date interval format string
49
     */
50
    public function setDateFormat($format = null, $dateIntervalFormat = null)
51
    {
52
        if (null !== $format) {
53
            $this->dateFormats[0] = $format;
54
        }
55
56
        if (null !== $dateIntervalFormat) {
57
            $this->dateFormats[1] = $dateIntervalFormat;
58
        }
59
    }
60
61
    /**
62
     * Gets the default format to be used by the date filter.
63
     *
64
     * @return array The default date format string and the default date interval format string
65
     */
66
    public function getDateFormat()
67
    {
68
        return $this->dateFormats;
69
    }
70
71
    /**
72
     * Sets the default timezone to be used by the date filter.
73
     *
74
     * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object
75
     */
76
    public function setTimezone($timezone)
77
    {
78
        $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
79
    }
80
81
    /**
82
     * Gets the default timezone to be used by the date filter.
83
     *
84
     * @return DateTimeZone The default timezone currently in use
85
     */
86
    public function getTimezone()
87
    {
88
        if (null === $this->timezone) {
89
            $this->timezone = new DateTimeZone(date_default_timezone_get());
90
        }
91
92
        return $this->timezone;
93
    }
94
95
    /**
96
     * Sets the default format to be used by the number_format filter.
97
     *
98
     * @param int    $decimal      The number of decimal places to use.
99
     * @param string $decimalPoint The character(s) to use for the decimal point.
100
     * @param string $thousandSep  The character(s) to use for the thousands separator.
101
     */
102
    public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
103
    {
104
        $this->numberFormat = array($decimal, $decimalPoint, $thousandSep);
105
    }
106
107
    /**
108
     * Get the default format used by the number_format filter.
109
     *
110
     * @return array The arguments for number_format()
111
     */
112
    public function getNumberFormat()
113
    {
114
        return $this->numberFormat;
115
    }
116
117
    /**
118
     * Returns the token parser instance to add to the existing list.
119
     *
120
     * @return Twig_TokenParser[] An array of Twig_TokenParser instances
121
     */
122
    public function getTokenParsers()
123
    {
124
        return array(
125
            new Twig_TokenParser_For(),
126
            new Twig_TokenParser_If(),
127
            new Twig_TokenParser_Extends(),
128
            new Twig_TokenParser_Include(),
129
            new Twig_TokenParser_Block(),
130
            new Twig_TokenParser_Use(),
131
            new Twig_TokenParser_Filter(),
132
            new Twig_TokenParser_Macro(),
133
            new Twig_TokenParser_Import(),
134
            new Twig_TokenParser_From(),
135
            new Twig_TokenParser_Set(),
136
            new Twig_TokenParser_Spaceless(),
137
            new Twig_TokenParser_Flush(),
138
            new Twig_TokenParser_Do(),
139
            new Twig_TokenParser_Embed(),
140
        );
141
    }
142
143
    /**
144
     * Returns a list of filters to add to the existing list.
145
     *
146
     * @return array An array of filters
147
     */
148
    public function getFilters()
149
    {
150
        $filters = array(
151
            // formatting filters
152
            new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)),
153
            new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)),
154
            new Twig_SimpleFilter('format', 'sprintf'),
155
            new Twig_SimpleFilter('replace', 'strtr'),
156
            new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)),
157
            new Twig_SimpleFilter('abs', 'abs'),
158
            new Twig_SimpleFilter('round', 'twig_round'),
159
160
            // encoding
161
            new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'),
162
            new Twig_SimpleFilter('json_encode', 'twig_jsonencode_filter'),
163
            new Twig_SimpleFilter('convert_encoding', 'twig_convert_encoding'),
164
165
            // string filters
166
            new Twig_SimpleFilter('title', 'twig_title_string_filter', array('needs_environment' => true)),
167
            new Twig_SimpleFilter('capitalize', 'twig_capitalize_string_filter', array('needs_environment' => true)),
168
            new Twig_SimpleFilter('upper', 'strtoupper'),
169
            new Twig_SimpleFilter('lower', 'strtolower'),
170
            new Twig_SimpleFilter('striptags', 'strip_tags'),
171
            new Twig_SimpleFilter('trim', 'trim'),
172
            new Twig_SimpleFilter('nl2br', 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
173
174
            // array helpers
175
            new Twig_SimpleFilter('join', 'twig_join_filter'),
176
            new Twig_SimpleFilter('split', 'twig_split_filter', array('needs_environment' => true)),
177
            new Twig_SimpleFilter('sort', 'twig_sort_filter'),
178
            new Twig_SimpleFilter('merge', 'twig_array_merge'),
179
            new Twig_SimpleFilter('batch', 'twig_array_batch'),
180
181
            // string/array filters
182
            new Twig_SimpleFilter('reverse', 'twig_reverse_filter', array('needs_environment' => true)),
183
            new Twig_SimpleFilter('length', 'twig_length_filter', array('needs_environment' => true)),
184
            new Twig_SimpleFilter('slice', 'twig_slice', array('needs_environment' => true)),
185
            new Twig_SimpleFilter('first', 'twig_first', array('needs_environment' => true)),
186
            new Twig_SimpleFilter('last', 'twig_last', array('needs_environment' => true)),
187
188
            // iteration and runtime
189
            new Twig_SimpleFilter('default', '_twig_default_filter', array('node_class' => 'Twig_Node_Expression_Filter_Default')),
190
            new Twig_SimpleFilter('keys', 'twig_get_array_keys_filter'),
191
192
            // escaping
193
            new Twig_SimpleFilter('escape', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
194
            new Twig_SimpleFilter('e', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
195
        );
196
197
        if (function_exists('mb_get_info')) {
198
            $filters[] = new Twig_SimpleFilter('upper', 'twig_upper_filter', array('needs_environment' => true));
199
            $filters[] = new Twig_SimpleFilter('lower', 'twig_lower_filter', array('needs_environment' => true));
200
        }
201
202
        return $filters;
203
    }
204
205
    /**
206
     * Returns a list of global functions to add to the existing list.
207
     *
208
     * @return array An array of global functions
209
     */
210
    public function getFunctions()
211
    {
212
        return array(
213
            new Twig_SimpleFunction('max', 'max'),
214
            new Twig_SimpleFunction('min', 'min'),
215
            new Twig_SimpleFunction('range', 'range'),
216
            new Twig_SimpleFunction('constant', 'twig_constant'),
217
            new Twig_SimpleFunction('cycle', 'twig_cycle'),
218
            new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)),
219
            new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)),
220
            new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))),
221
            new Twig_SimpleFunction('source', 'twig_source', array('needs_environment' => true, 'is_safe' => array('all'))),
222
        );
223
    }
224
225
    /**
226
     * Returns a list of tests to add to the existing list.
227
     *
228
     * @return array An array of tests
229
     */
230
    public function getTests()
231
    {
232
        return array(
233
            new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')),
234
            new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')),
235
            new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')),
236
            new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')),
237
            new Twig_SimpleTest('same as', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')),
238
            new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
239
            new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
240
            new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')),
241
            new Twig_SimpleTest('divisible by', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')),
242
            new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')),
243
            new Twig_SimpleTest('empty', 'twig_test_empty'),
244
            new Twig_SimpleTest('iterable', 'twig_test_iterable'),
245
        );
246
    }
247
248
    /**
249
     * Returns a list of operators to add to the existing list.
250
     *
251
     * @return array An array of operators
252
     */
253
    public function getOperators()
254
    {
255
        return array(
256
            array(
257
                'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
258
                '-'   => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
259
                '+'   => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
260
            ),
261
            array(
262
                'or'          => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
263
                'and'         => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
264
                'b-or'        => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
265
                'b-xor'       => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
266
                'b-and'       => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
267
                '=='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
268
                '!='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
269
                '<'           => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
270
                '>'           => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
271
                '>='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
272
                '<='          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
273
                'not in'      => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
274
                'in'          => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
275
                'matches'     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
276
                'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
277
                'ends with'   => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
278
                '..'          => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
279
                '+'           => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
280
                '-'           => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
281
                '~'           => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
282
                '*'           => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
283
                '/'           => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
284
                '//'          => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
285
                '%'           => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
286
                'is'          => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
287
                'is not'      => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
288
                '**'          => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
289
            ),
290
        );
291
    }
292
293
    public function parseNotTestExpression(Twig_Parser $parser, Twig_NodeInterface $node)
294
    {
295
        return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
296
    }
297
298
    public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $node)
299
    {
300
        $stream = $parser->getStream();
301
        $name = $this->getTestName($parser, $node->getLine());
302
        $class = $this->getTestNodeClass($parser, $name);
303
        $arguments = null;
304
        if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
305
            $arguments = $parser->getExpressionParser()->parseArguments(true);
306
        }
307
308
        return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
309
    }
310
311
    protected function getTestName(Twig_Parser $parser, $line)
312
    {
313
        $stream = $parser->getStream();
314
        $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
315
        $env = $parser->getEnvironment();
316
        $testMap = $env->getTests();
317
318
        if (isset($testMap[$name])) {
319
            return $name;
320
        }
321
322
        if ($stream->test(Twig_Token::NAME_TYPE)) {
323
            // try 2-words tests
324
            $name = $name.' '.$parser->getCurrentToken()->getValue();
325
326
            if (isset($testMap[$name])) {
327
                $parser->getStream()->next();
328
329
                return $name;
330
            }
331
        }
332
333
        $message = sprintf('The test "%s" does not exist', $name);
334
        if ($alternatives = $env->computeAlternatives($name, array_keys($testMap))) {
335
            $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
336
        }
337
338
        throw new Twig_Error_Syntax($message, $line, $parser->getFilename());
339
    }
340
341
    protected function getTestNodeClass(Twig_Parser $parser, $name)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
342
    {
343
        $env = $parser->getEnvironment();
344
        $testMap = $env->getTests();
345
346
        if ($testMap[$name] instanceof Twig_SimpleTest) {
347
            return $testMap[$name]->getNodeClass();
0 ignored issues
show
Bug introduced by
The method getNodeClass() does not seem to exist on object<Twig_TestInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
348
        }
349
350
        return $testMap[$name] instanceof Twig_Test_Node ? $testMap[$name]->getClass() : 'Twig_Node_Expression_Test';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Twig_TestInterface as the method getClass() does only exist in the following implementations of said interface: Twig_Test_Node, Twig_Test_Node.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
351
    }
352
353
    /**
354
     * Returns the name of the extension.
355
     *
356
     * @return string The extension name
357
     */
358
    public function getName()
359
    {
360
        return 'core';
361
    }
362
}
363
364
/**
365
 * Cycles over a value.
366
 *
367
 * @param ArrayAccess|array $values   An array or an ArrayAccess instance
368
 * @param int               $position The cycle position
369
 *
370
 * @return string The next value in the cycle
371
 */
372
function twig_cycle($values, $position)
373
{
374
    if (!is_array($values) && !$values instanceof ArrayAccess) {
375
        return $values;
376
    }
377
378
    return $values[$position % count($values)];
379
}
380
381
/**
382
 * Returns a random value depending on the supplied parameter type:
383
 * - a random item from a Traversable or array
384
 * - a random character from a string
385
 * - a random integer between 0 and the integer parameter
386
 *
387
 * @param Twig_Environment                 $env    A Twig_Environment instance
388
 * @param Traversable|array|int|string     $values The values to pick a random item from
389
 *
390
 * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is).
391
 *
392
 * @return mixed A random value from the given sequence
393
 */
394
function twig_random(Twig_Environment $env, $values = null)
395
{
396
    if (null === $values) {
397
        return mt_rand();
398
    }
399
400
    if (is_int($values) || is_float($values)) {
401
        return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
402
    }
403
404
    if ($values instanceof Traversable) {
405
        $values = iterator_to_array($values);
406
    } elseif (is_string($values)) {
407
        if ('' === $values) {
408
            return '';
409
        }
410
        if (null !== $charset = $env->getCharset()) {
411
            if ('UTF-8' != $charset) {
412
                $values = twig_convert_encoding($values, 'UTF-8', $charset);
413
            }
414
415
            // unicode version of str_split()
416
            // split at all positions, but not after the start and not before the end
417
            $values = preg_split('/(?<!^)(?!$)/u', $values);
418
419
            if ('UTF-8' != $charset) {
420
                foreach ($values as $i => $value) {
421
                    $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
422
                }
423
            }
424
        } else {
425
            return $values[mt_rand(0, strlen($values) - 1)];
426
        }
427
    }
428
429
    if (!is_array($values)) {
430
        return $values;
431
    }
432
433
    if (0 === count($values)) {
434
        throw new Twig_Error_Runtime('The random function cannot pick from an empty array.');
435
    }
436
437
    return $values[array_rand($values, 1)];
438
}
439
440
/**
441
 * Converts a date to the given format.
442
 *
443
 * <pre>
444
 *   {{ post.published_at|date("m/d/Y") }}
445
 * </pre>
446
 *
447
 * @param Twig_Environment                               $env      A Twig_Environment instance
448
 * @param DateTime|DateTimeInterface|DateInterval|string $date     A date
449
 * @param string|null                                    $format   The target format, null to use the default
450
 * @param DateTimeZone|string|null|false                 $timezone The target timezone, null to use the default, false to leave unchanged
451
 *
452
 * @return string The formatted date
453
 */
454
function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null)
455
{
456
    if (null === $format) {
457
        $formats = $env->getExtension('core')->getDateFormat();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Twig_ExtensionInterface as the method getDateFormat() does only exist in the following implementations of said interface: Twig_Extension_Core, Twig_Extension_Core.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
458
        $format = $date instanceof DateInterval ? $formats[1] : $formats[0];
459
    }
460
461
    if ($date instanceof DateInterval) {
462
        return $date->format($format);
463
    }
464
465
    return twig_date_converter($env, $date, $timezone)->format($format);
466
}
467
468
/**
469
 * Returns a new date object modified
470
 *
471
 * <pre>
472
 *   {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
473
 * </pre>
474
 *
475
 * @param Twig_Environment  $env      A Twig_Environment instance
476
 * @param DateTime|string   $date     A date
477
 * @param string            $modifier A modifier string
478
 *
479
 * @return DateTime A new date object
480
 */
481
function twig_date_modify_filter(Twig_Environment $env, $date, $modifier)
482
{
483
    $date = twig_date_converter($env, $date, false);
484
    $resultDate = $date->modify($modifier);
485
486
    // This is a hack to ensure PHP 5.2 support and support for DateTimeImmutable
487
    // DateTime::modify does not return the modified DateTime object < 5.3.0
488
    // and DateTimeImmutable does not modify $date.
489
    return null === $resultDate ? $date : $resultDate;
490
}
491
492
/**
493
 * Converts an input to a DateTime instance.
494
 *
495
 * <pre>
496
 *    {% if date(user.created_at) < date('+2days') %}
497
 *      {# do something #}
498
 *    {% endif %}
499
 * </pre>
500
 *
501
 * @param Twig_Environment                       $env      A Twig_Environment instance
502
 * @param DateTime|DateTimeInterface|string|null $date     A date
503
 * @param DateTimeZone|string|null|false         $timezone The target timezone, null to use the default, false to leave unchanged
504
 *
505
 * @return DateTime A DateTime instance
506
 */
507
function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null)
508
{
509
    // determine the timezone
510
    if (false !== $timezone) {
511
        if (null === $timezone) {
512
            $timezone = $env->getExtension('core')->getTimezone();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Twig_ExtensionInterface as the method getTimezone() does only exist in the following implementations of said interface: Twig_Extension_Core, Twig_Extension_Core.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
513
        } elseif (!$timezone instanceof DateTimeZone) {
514
            $timezone = new DateTimeZone($timezone);
515
        }
516
    }
517
518
    // immutable dates
519
    if ($date instanceof DateTimeImmutable) {
0 ignored issues
show
Bug introduced by
The class DateTimeImmutable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
520
        return false !== $timezone ? $date->setTimezone($timezone) : $date;
521
    }
522
523
    if ($date instanceof DateTime || $date instanceof DateTimeInterface) {
524
        $date = clone $date;
525
        if (false !== $timezone) {
526
            $date->setTimezone($timezone);
527
        }
528
529
        return $date;
530
    }
531
532
    $asString = (string) $date;
533
    if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
534
        $date = '@'.$date;
535
    }
536
537
    $date = new DateTime($date, $env->getExtension('core')->getTimezone());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Twig_ExtensionInterface as the method getTimezone() does only exist in the following implementations of said interface: Twig_Extension_Core, Twig_Extension_Core.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
538
    if (false !== $timezone) {
539
        $date->setTimezone($timezone);
540
    }
541
542
    return $date;
543
}
544
545
/**
546
 * Rounds a number.
547
 *
548
 * @param int|float     $value     The value to round
549
 * @param int|float     $precision The rounding precision
550
 * @param string        $method    The method to use for rounding
551
 *
552
 * @return int|float     The rounded number
553
 */
554
function twig_round($value, $precision = 0, $method = 'common')
555
{
556
    if ('common' == $method) {
557
        return round($value, $precision);
558
    }
559
560
    if ('ceil' != $method && 'floor' != $method) {
561
        throw new Twig_Error_Runtime('The round filter only supports the "common", "ceil", and "floor" methods.');
562
    }
563
564
    return $method($value * pow(10, $precision)) / pow(10, $precision);
565
}
566
567
/**
568
 * Number format filter.
569
 *
570
 * All of the formatting options can be left null, in that case the defaults will
571
 * be used.  Supplying any of the parameters will override the defaults set in the
572
 * environment object.
573
 *
574
 * @param Twig_Environment    $env          A Twig_Environment instance
575
 * @param mixed               $number       A float/int/string of the number to format
576
 * @param int                 $decimal      The number of decimal points to display.
577
 * @param string              $decimalPoint The character(s) to use for the decimal point.
578
 * @param string              $thousandSep  The character(s) to use for the thousands separator.
579
 *
580
 * @return string The formatted number
581
 */
582
function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
583
{
584
    $defaults = $env->getExtension('core')->getNumberFormat();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Twig_ExtensionInterface as the method getNumberFormat() does only exist in the following implementations of said interface: Twig_Extension_Core, Twig_Extension_Core.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
585
    if (null === $decimal) {
586
        $decimal = $defaults[0];
587
    }
588
589
    if (null === $decimalPoint) {
590
        $decimalPoint = $defaults[1];
591
    }
592
593
    if (null === $thousandSep) {
594
        $thousandSep = $defaults[2];
595
    }
596
597
    return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);
598
}
599
600
/**
601
 * URL encodes (RFC 3986) a string as a path segment or an array as a query string.
602
 *
603
 * @param string|array $url A URL or an array of query parameters
604
 *
605
 * @return string The URL encoded value
606
 */
607
function twig_urlencode_filter($url)
608
{
609
    if (is_array($url)) {
610
        if (defined('PHP_QUERY_RFC3986')) {
611
            return http_build_query($url, '', '&', PHP_QUERY_RFC3986);
612
        }
613
614
        return http_build_query($url, '', '&');
615
    }
616
617
    return rawurlencode($url);
618
}
619
620
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
621
    /**
622
     * JSON encodes a variable.
623
     *
624
     * @param mixed $value   The value to encode.
625
     * @param int   $options Not used on PHP 5.2.x
626
     *
627
     * @return mixed The JSON encoded value
628
     */
629
    function twig_jsonencode_filter($value, $options = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $options 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...
630
    {
631
        if ($value instanceof Twig_Markup) {
632
            $value = (string) $value;
633
        } elseif (is_array($value)) {
634
            array_walk_recursive($value, '_twig_markup2string');
635
        }
636
637
        return json_encode($value);
638
    }
639
} else {
640
    /**
641
     * JSON encodes a variable.
642
     *
643
     * @param mixed $value   The value to encode.
644
     * @param int   $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT
645
     *
646
     * @return mixed The JSON encoded value
647
     */
648
    function twig_jsonencode_filter($value, $options = 0)
0 ignored issues
show
Best Practice introduced by
The function twig_jsonencode_filter() has been defined more than once; this definition is ignored, only the first definition in this file (L629-638) is considered.

This check looks for functions that have already been defined in the same file.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
649
    {
650
        if ($value instanceof Twig_Markup) {
651
            $value = (string) $value;
652
        } elseif (is_array($value)) {
653
            array_walk_recursive($value, '_twig_markup2string');
654
        }
655
656
        return json_encode($value, $options);
657
    }
658
}
659
660
function _twig_markup2string(&$value)
661
{
662
    if ($value instanceof Twig_Markup) {
663
        $value = (string) $value;
664
    }
665
}
666
667
/**
668
 * Merges an array with another one.
669
 *
670
 * <pre>
671
 *  {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
672
 *
673
 *  {% set items = items|merge({ 'peugeot': 'car' }) %}
674
 *
675
 *  {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
676
 * </pre>
677
 *
678
 * @param array $arr1 An array
679
 * @param array $arr2 An array
680
 *
681
 * @return array The merged array
682
 */
683
function twig_array_merge($arr1, $arr2)
684
{
685
    if (!is_array($arr1) || !is_array($arr2)) {
686
        throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or hashes; %s and %s given.', gettype($arr1), gettype($arr2)));
687
    }
688
689
    return array_merge($arr1, $arr2);
690
}
691
692
/**
693
 * Slices a variable.
694
 *
695
 * @param Twig_Environment $env          A Twig_Environment instance
696
 * @param mixed            $item         A variable
697
 * @param int              $start        Start of the slice
698
 * @param int              $length       Size of the slice
699
 * @param bool             $preserveKeys Whether to preserve key or not (when the input is an array)
700
 *
701
 * @return mixed The sliced variable
702
 */
703
function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
704
{
705
    if ($item instanceof Traversable) {
706
        if ($item instanceof IteratorAggregate) {
707
            $item = $item->getIterator();
708
        }
709
710
        if ($start >= 0 && $length >= 0) {
711
            try {
712
                return iterator_to_array(new LimitIterator($item, $start, $length === null ? -1 : $length), $preserveKeys);
713
            } catch (OutOfBoundsException $exception) {
714
                return array();
715
            }
716
        }
717
718
        $item = iterator_to_array($item, $preserveKeys);
719
    }
720
721
    if (is_array($item)) {
722
        return array_slice($item, $start, $length, $preserveKeys);
723
    }
724
725
    $item = (string) $item;
726
727
    if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) {
728
        return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset);
729
    }
730
731
    return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length));
732
}
733
734
/**
735
 * Returns the first element of the item.
736
 *
737
 * @param Twig_Environment $env  A Twig_Environment instance
738
 * @param mixed            $item A variable
739
 *
740
 * @return mixed The first element of the item
741
 */
742
function twig_first(Twig_Environment $env, $item)
743
{
744
    $elements = twig_slice($env, $item, 0, 1, false);
745
746
    return is_string($elements) ? $elements : current($elements);
747
}
748
749
/**
750
 * Returns the last element of the item.
751
 *
752
 * @param Twig_Environment $env  A Twig_Environment instance
753
 * @param mixed            $item A variable
754
 *
755
 * @return mixed The last element of the item
756
 */
757
function twig_last(Twig_Environment $env, $item)
758
{
759
    $elements = twig_slice($env, $item, -1, 1, false);
760
761
    return is_string($elements) ? $elements : current($elements);
762
}
763
764
/**
765
 * Joins the values to a string.
766
 *
767
 * The separator between elements is an empty string per default, you can define it with the optional parameter.
768
 *
769
 * <pre>
770
 *  {{ [1, 2, 3]|join('|') }}
771
 *  {# returns 1|2|3 #}
772
 *
773
 *  {{ [1, 2, 3]|join }}
774
 *  {# returns 123 #}
775
 * </pre>
776
 *
777
 * @param array  $value An array
778
 * @param string $glue  The separator
779
 *
780
 * @return string The concatenated string
781
 */
782
function twig_join_filter($value, $glue = '')
783
{
784
    if ($value instanceof Traversable) {
785
        $value = iterator_to_array($value, false);
786
    }
787
788
    return implode($glue, (array) $value);
789
}
790
791
/**
792
 * Splits the string into an array.
793
 *
794
 * <pre>
795
 *  {{ "one,two,three"|split(',') }}
796
 *  {# returns [one, two, three] #}
797
 *
798
 *  {{ "one,two,three,four,five"|split(',', 3) }}
799
 *  {# returns [one, two, "three,four,five"] #}
800
 *
801
 *  {{ "123"|split('') }}
802
 *  {# returns [1, 2, 3] #}
803
 *
804
 *  {{ "aabbcc"|split('', 2) }}
805
 *  {# returns [aa, bb, cc] #}
806
 * </pre>
807
 *
808
 * @param string  $value     A string
809
 * @param string  $delimiter The delimiter
810
 * @param int     $limit     The limit
811
 *
812
 * @return array The split string as an array
813
 */
814
function twig_split_filter(Twig_Environment $env, $value, $delimiter, $limit = null)
815
{
816
    if (!empty($delimiter)) {
817
        return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
818
    }
819
820
    if (!function_exists('mb_get_info') || null === $charset = $env->getCharset()) {
821
        return str_split($value, null === $limit ? 1 : $limit);
822
    }
823
824
    if ($limit <= 1) {
825
        return preg_split('/(?<!^)(?!$)/u', $value);
826
    }
827
828
    $length = mb_strlen($value, $charset);
829
    if ($length < $limit) {
830
        return array($value);
831
    }
832
833
    $r = array();
834
    for ($i = 0; $i < $length; $i += $limit) {
835
        $r[] = mb_substr($value, $i, $limit, $charset);
836
    }
837
838
    return $r;
839
}
840
841
// The '_default' filter is used internally to avoid using the ternary operator
842
// which costs a lot for big contexts (before PHP 5.4). So, on average,
843
// a function call is cheaper.
844
function _twig_default_filter($value, $default = '')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
845
{
846
    if (twig_test_empty($value)) {
847
        return $default;
848
    }
849
850
    return $value;
851
}
852
853
/**
854
 * Returns the keys for the given array.
855
 *
856
 * It is useful when you want to iterate over the keys of an array:
857
 *
858
 * <pre>
859
 *  {% for key in array|keys %}
860
 *      {# ... #}
861
 *  {% endfor %}
862
 * </pre>
863
 *
864
 * @param array $array An array
865
 *
866
 * @return array The keys
867
 */
868
function twig_get_array_keys_filter($array)
869
{
870
    if (is_object($array) && $array instanceof Traversable) {
871
        return array_keys(iterator_to_array($array));
872
    }
873
874
    if (!is_array($array)) {
875
        return array();
876
    }
877
878
    return array_keys($array);
879
}
880
881
/**
882
 * Reverses a variable.
883
 *
884
 * @param Twig_Environment         $env          A Twig_Environment instance
885
 * @param array|Traversable|string $item         An array, a Traversable instance, or a string
886
 * @param bool                     $preserveKeys Whether to preserve key or not
887
 *
888
 * @return mixed The reversed input
889
 */
890
function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
891
{
892
    if (is_object($item) && $item instanceof Traversable) {
893
        return array_reverse(iterator_to_array($item), $preserveKeys);
894
    }
895
896
    if (is_array($item)) {
897
        return array_reverse($item, $preserveKeys);
898
    }
899
900
    if (null !== $charset = $env->getCharset()) {
901
        $string = (string) $item;
902
903
        if ('UTF-8' != $charset) {
904
            $item = twig_convert_encoding($string, 'UTF-8', $charset);
905
        }
906
907
        preg_match_all('/./us', $item, $matches);
908
909
        $string = implode('', array_reverse($matches[0]));
910
911
        if ('UTF-8' != $charset) {
912
            $string = twig_convert_encoding($string, $charset, 'UTF-8');
913
        }
914
915
        return $string;
916
    }
917
918
    return strrev((string) $item);
919
}
920
921
/**
922
 * Sorts an array.
923
 *
924
 * @param array $array An array
925
 */
926
function twig_sort_filter($array)
927
{
928
    asort($array);
929
930
    return $array;
931
}
932
933
/* used internally */
934
function twig_in_filter($value, $compare)
935
{
936
    if (is_array($compare)) {
937
        return in_array($value, $compare, is_object($value) || is_resource($value));
938
    } elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) {
939
        return '' === $value || false !== strpos($compare, (string) $value);
940
    } elseif ($compare instanceof Traversable) {
941
        return in_array($value, iterator_to_array($compare, false), is_object($value) || is_resource($value));
942
    }
943
944
    return false;
945
}
946
947
/**
948
 * Escapes a string.
949
 *
950
 * @param Twig_Environment $env        A Twig_Environment instance
951
 * @param string           $string     The value to be escaped
952
 * @param string           $strategy   The escaping strategy
953
 * @param string           $charset    The charset
954
 * @param bool             $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
955
 */
956
function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
957
{
958
    if ($autoescape && $string instanceof Twig_Markup) {
959
        return $string;
960
    }
961
962
    if (!is_string($string)) {
963
        if (is_object($string) && method_exists($string, '__toString')) {
964
            $string = (string) $string;
965
        } else {
966
            return $string;
967
        }
968
    }
969
970
    if (null === $charset) {
971
        $charset = $env->getCharset();
972
    }
973
974
    switch ($strategy) {
975
        case 'html':
976
            // see http://php.net/htmlspecialchars
977
978
            // Using a static variable to avoid initializing the array
979
            // each time the function is called. Moving the declaration on the
980
            // top of the function slow downs other escaping strategies.
981
            static $htmlspecialcharsCharsets;
982
983
            if (null === $htmlspecialcharsCharsets) {
984
                if (defined('HHVM_VERSION')) {
985
                    $htmlspecialcharsCharsets = array('utf-8' => true, 'UTF-8' => true);
986
                } else {
987
                    $htmlspecialcharsCharsets = array(
988
                        'ISO-8859-1' => true, 'ISO8859-1' => true,
989
                        'ISO-8859-15' => true, 'ISO8859-15' => true,
990
                        'utf-8' => true, 'UTF-8' => true,
991
                        'CP866' => true, 'IBM866' => true, '866' => true,
992
                        'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
993
                        '1251' => true,
994
                        'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
995
                        'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
996
                        'BIG5' => true, '950' => true,
997
                        'GB2312' => true, '936' => true,
998
                        'BIG5-HKSCS' => true,
999
                        'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
1000
                        'EUC-JP' => true, 'EUCJP' => true,
1001
                        'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
1002
                    );
1003
                }
1004
            }
1005
1006
            if (isset($htmlspecialcharsCharsets[$charset])) {
1007
                return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
1008
            }
1009
1010
            if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
1011
                // cache the lowercase variant for future iterations
1012
                $htmlspecialcharsCharsets[$charset] = true;
1013
1014
                return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
1015
            }
1016
1017
            $string = twig_convert_encoding($string, 'UTF-8', $charset);
1018
            $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1019
1020
            return twig_convert_encoding($string, $charset, 'UTF-8');
1021
1022
        case 'js':
1023
            // escape all non-alphanumeric characters
1024
            // into their \xHH or \uHHHH representations
1025
            if ('UTF-8' != $charset) {
1026
                $string = twig_convert_encoding($string, 'UTF-8', $charset);
1027
            }
1028
1029
            if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
1030
                throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
1031
            }
1032
1033
            $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string);
1034
1035
            if ('UTF-8' != $charset) {
1036
                $string = twig_convert_encoding($string, $charset, 'UTF-8');
1037
            }
1038
1039
            return $string;
1040
1041
        case 'css':
1042
            if ('UTF-8' != $charset) {
1043
                $string = twig_convert_encoding($string, 'UTF-8', $charset);
1044
            }
1045
1046
            if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
1047
                throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
1048
            }
1049
1050
            $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string);
1051
1052
            if ('UTF-8' != $charset) {
1053
                $string = twig_convert_encoding($string, $charset, 'UTF-8');
1054
            }
1055
1056
            return $string;
1057
1058
        case 'html_attr':
1059
            if ('UTF-8' != $charset) {
1060
                $string = twig_convert_encoding($string, 'UTF-8', $charset);
1061
            }
1062
1063
            if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
1064
                throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
1065
            }
1066
1067
            $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string);
1068
1069
            if ('UTF-8' != $charset) {
1070
                $string = twig_convert_encoding($string, $charset, 'UTF-8');
1071
            }
1072
1073
            return $string;
1074
1075
        case 'url':
1076
            // hackish test to avoid version_compare that is much slower, this works unless PHP releases a 5.10.*
1077
            // at that point however PHP 5.2.* support can be removed
1078
            if (PHP_VERSION < '5.3.0') {
1079
                return str_replace('%7E', '~', rawurlencode($string));
1080
            }
1081
1082
            return rawurlencode($string);
1083
1084
        default:
1085
            static $escapers;
1086
1087
            if (null === $escapers) {
1088
                $escapers = $env->getExtension('core')->getEscapers();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Twig_ExtensionInterface as the method getEscapers() does only exist in the following implementations of said interface: Twig_Extension_Core, Twig_Extension_Core.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1089
            }
1090
1091
            if (isset($escapers[$strategy])) {
1092
                return call_user_func($escapers[$strategy], $env, $string, $charset);
1093
            }
1094
1095
            $validStrategies = implode(', ', array_merge(array('html', 'js', 'url', 'css', 'html_attr'), array_keys($escapers)));
1096
1097
            throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
1098
    }
1099
}
1100
1101
/* used internally */
1102
function twig_escape_filter_is_safe(Twig_Node $filterArgs)
1103
{
1104
    foreach ($filterArgs as $arg) {
1105
        if ($arg instanceof Twig_Node_Expression_Constant) {
1106
            return array($arg->getAttribute('value'));
1107
        }
1108
1109
        return array();
1110
    }
1111
1112
    return array('html');
1113
}
1114
1115
if (function_exists('mb_convert_encoding')) {
1116
    function twig_convert_encoding($string, $to, $from)
0 ignored issues
show
Unused Code introduced by
The parameter $string 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...
Unused Code introduced by
The parameter $to 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...
Unused Code introduced by
The parameter $from 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...
1117
    {
1118
        return mb_convert_encoding($string, $to, $from);
1119
    }
1120
} elseif (function_exists('iconv')) {
1121
    function twig_convert_encoding($string, $to, $from)
0 ignored issues
show
Best Practice introduced by
The function twig_convert_encoding() has been defined more than once; this definition is ignored, only the first definition in this file (L1116-1119) is considered.

This check looks for functions that have already been defined in the same file.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1122
    {
1123
        return iconv($from, $to, $string);
1124
    }
1125
} else {
1126
    function twig_convert_encoding($string, $to, $from)
0 ignored issues
show
Unused Code introduced by
The parameter $string 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...
Unused Code introduced by
The parameter $to 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...
Unused Code introduced by
The parameter $from 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...
Best Practice introduced by
The function twig_convert_encoding() has been defined more than once; this definition is ignored, only the first definition in this file (L1116-1119) is considered.

This check looks for functions that have already been defined in the same file.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1127
    {
1128
        throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
1129
    }
1130
}
1131
1132
function _twig_escape_js_callback($matches)
1133
{
1134
    $char = $matches[0];
1135
1136
    // \xHH
1137
    if (!isset($char[1])) {
1138
        return '\\x'.strtoupper(substr('00'.bin2hex($char), -2));
1139
    }
1140
1141
    // \uHHHH
1142
    $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
1143
1144
    return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4));
1145
}
1146
1147
function _twig_escape_css_callback($matches)
1148
{
1149
    $char = $matches[0];
1150
1151
    // \xHH
1152
    if (!isset($char[1])) {
1153
        $hex = ltrim(strtoupper(bin2hex($char)), '0');
1154
        if (0 === strlen($hex)) {
1155
            $hex = '0';
1156
        }
1157
1158
        return '\\'.$hex.' ';
1159
    }
1160
1161
    // \uHHHH
1162
    $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
1163
1164
    return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' ';
1165
}
1166
1167
/**
1168
 * This function is adapted from code coming from Zend Framework.
1169
 *
1170
 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
1171
 * @license   http://framework.zend.com/license/new-bsd New BSD License
1172
 */
1173
function _twig_escape_html_attr_callback($matches)
1174
{
1175
    /*
1176
     * While HTML supports far more named entities, the lowest common denominator
1177
     * has become HTML5's XML Serialisation which is restricted to the those named
1178
     * entities that XML supports. Using HTML entities would result in this error:
1179
     *     XML Parsing Error: undefined entity
1180
     */
1181
    static $entityMap = array(
1182
        34 => 'quot', /* quotation mark */
1183
        38 => 'amp',  /* ampersand */
1184
        60 => 'lt',   /* less-than sign */
1185
        62 => 'gt',   /* greater-than sign */
1186
    );
1187
1188
    $chr = $matches[0];
1189
    $ord = ord($chr);
1190
1191
    /**
1192
     * The following replaces characters undefined in HTML with the
1193
     * hex entity for the Unicode replacement character.
1194
     */
1195
    if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) {
1196
        return '&#xFFFD;';
1197
    }
1198
1199
    /**
1200
     * Check if the current character to escape has a name entity we should
1201
     * replace it with while grabbing the hex value of the character.
1202
     */
1203
    if (strlen($chr) == 1) {
1204
        $hex = strtoupper(substr('00'.bin2hex($chr), -2));
1205
    } else {
1206
        $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8');
1207
        $hex = strtoupper(substr('0000'.bin2hex($chr), -4));
1208
    }
1209
1210
    $int = hexdec($hex);
1211
    if (array_key_exists($int, $entityMap)) {
1212
        return sprintf('&%s;', $entityMap[$int]);
1213
    }
1214
1215
    /**
1216
     * Per OWASP recommendations, we'll use hex entities for any other
1217
     * characters where a named entity does not exist.
1218
     */
1219
    return sprintf('&#x%s;', $hex);
1220
}
1221
1222
// add multibyte extensions if possible
1223
if (function_exists('mb_get_info')) {
1224
    /**
1225
     * Returns the length of a variable.
1226
     *
1227
     * @param Twig_Environment $env   A Twig_Environment instance
1228
     * @param mixed            $thing A variable
1229
     *
1230
     * @return int The length of the value
1231
     */
1232
    function twig_length_filter(Twig_Environment $env, $thing)
0 ignored issues
show
Unused Code introduced by
The parameter $env 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...
1233
    {
1234
        return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
1235
    }
1236
1237
    /**
1238
     * Converts a string to uppercase.
1239
     *
1240
     * @param Twig_Environment $env    A Twig_Environment instance
1241
     * @param string           $string A string
1242
     *
1243
     * @return string The uppercased string
1244
     */
1245
    function twig_upper_filter(Twig_Environment $env, $string)
1246
    {
1247
        if (null !== ($charset = $env->getCharset())) {
1248
            return mb_strtoupper($string, $charset);
1249
        }
1250
1251
        return strtoupper($string);
1252
    }
1253
1254
    /**
1255
     * Converts a string to lowercase.
1256
     *
1257
     * @param Twig_Environment $env    A Twig_Environment instance
1258
     * @param string           $string A string
1259
     *
1260
     * @return string The lowercased string
1261
     */
1262
    function twig_lower_filter(Twig_Environment $env, $string)
1263
    {
1264
        if (null !== ($charset = $env->getCharset())) {
1265
            return mb_strtolower($string, $charset);
1266
        }
1267
1268
        return strtolower($string);
1269
    }
1270
1271
    /**
1272
     * Returns a titlecased string.
1273
     *
1274
     * @param Twig_Environment $env    A Twig_Environment instance
1275
     * @param string           $string A string
1276
     *
1277
     * @return string The titlecased string
1278
     */
1279
    function twig_title_string_filter(Twig_Environment $env, $string)
0 ignored issues
show
Unused Code introduced by
The parameter $env 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...
1280
    {
1281
        if (null !== ($charset = $env->getCharset())) {
1282
            return mb_convert_case($string, MB_CASE_TITLE, $charset);
1283
        }
1284
1285
        return ucwords(strtolower($string));
1286
    }
1287
1288
    /**
1289
     * Returns a capitalized string.
1290
     *
1291
     * @param Twig_Environment $env    A Twig_Environment instance
1292
     * @param string           $string A string
1293
     *
1294
     * @return string The capitalized string
1295
     */
1296
    function twig_capitalize_string_filter(Twig_Environment $env, $string)
0 ignored issues
show
Unused Code introduced by
The parameter $env 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...
1297
    {
1298
        if (null !== ($charset = $env->getCharset())) {
1299
            return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).
1300
                         mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset);
1301
        }
1302
1303
        return ucfirst(strtolower($string));
1304
    }
1305
}
1306
// and byte fallback
1307
else {
1308
    /**
1309
     * Returns the length of a variable.
1310
     *
1311
     * @param Twig_Environment $env   A Twig_Environment instance
1312
     * @param mixed            $thing A variable
1313
     *
1314
     * @return int The length of the value
1315
     */
1316
    function twig_length_filter(Twig_Environment $env, $thing)
0 ignored issues
show
Unused Code introduced by
The parameter $env 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...
Best Practice introduced by
The function twig_length_filter() has been defined more than once; this definition is ignored, only the first definition in this file (L1232-1235) is considered.

This check looks for functions that have already been defined in the same file.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1317
    {
1318
        return is_scalar($thing) ? strlen($thing) : count($thing);
1319
    }
1320
1321
    /**
1322
     * Returns a titlecased string.
1323
     *
1324
     * @param Twig_Environment $env    A Twig_Environment instance
1325
     * @param string           $string A string
1326
     *
1327
     * @return string The titlecased string
1328
     */
1329
    function twig_title_string_filter(Twig_Environment $env, $string)
0 ignored issues
show
Unused Code introduced by
The parameter $env 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...
Best Practice introduced by
The function twig_title_string_filter() has been defined more than once; this definition is ignored, only the first definition in this file (L1279-1286) is considered.

This check looks for functions that have already been defined in the same file.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1330
    {
1331
        return ucwords(strtolower($string));
1332
    }
1333
1334
    /**
1335
     * Returns a capitalized string.
1336
     *
1337
     * @param Twig_Environment $env    A Twig_Environment instance
1338
     * @param string           $string A string
1339
     *
1340
     * @return string The capitalized string
1341
     */
1342
    function twig_capitalize_string_filter(Twig_Environment $env, $string)
0 ignored issues
show
Unused Code introduced by
The parameter $env 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...
Best Practice introduced by
The function twig_capitalize_string_filter() has been defined more than once; this definition is ignored, only the first definition in this file (L1296-1304) is considered.

This check looks for functions that have already been defined in the same file.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1343
    {
1344
        return ucfirst(strtolower($string));
1345
    }
1346
}
1347
1348
/* used internally */
1349
function twig_ensure_traversable($seq)
1350
{
1351
    if ($seq instanceof Traversable || is_array($seq)) {
1352
        return $seq;
1353
    }
1354
1355
    return array();
1356
}
1357
1358
/**
1359
 * Checks if a variable is empty.
1360
 *
1361
 * <pre>
1362
 * {# evaluates to true if the foo variable is null, false, or the empty string #}
1363
 * {% if foo is empty %}
1364
 *     {# ... #}
1365
 * {% endif %}
1366
 * </pre>
1367
 *
1368
 * @param mixed $value A variable
1369
 *
1370
 * @return bool    true if the value is empty, false otherwise
1371
 */
1372
function twig_test_empty($value)
1373
{
1374
    if ($value instanceof Countable) {
1375
        return 0 == count($value);
1376
    }
1377
1378
    return '' === $value || false === $value || null === $value || array() === $value;
1379
}
1380
1381
/**
1382
 * Checks if a variable is traversable.
1383
 *
1384
 * <pre>
1385
 * {# evaluates to true if the foo variable is an array or a traversable object #}
1386
 * {% if foo is traversable %}
1387
 *     {# ... #}
1388
 * {% endif %}
1389
 * </pre>
1390
 *
1391
 * @param mixed $value A variable
1392
 *
1393
 * @return bool    true if the value is traversable
1394
 */
1395
function twig_test_iterable($value)
1396
{
1397
    return $value instanceof Traversable || is_array($value);
1398
}
1399
1400
/**
1401
 * Renders a template.
1402
 *
1403
 * @param string|array $template       The template to render or an array of templates to try consecutively
1404
 * @param array        $variables      The variables to pass to the template
1405
 * @param bool         $with_context   Whether to pass the current context variables or not
0 ignored issues
show
Documentation introduced by
There is no parameter named $with_context. Did you maybe mean $context?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1406
 * @param bool         $ignore_missing Whether to ignore missing templates or not
0 ignored issues
show
Bug introduced by
There is no parameter named $ignore_missing. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1407
 * @param bool         $sandboxed      Whether to sandbox the template or not
1408
 *
1409
 * @return string The rendered template
1410
 */
1411
function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false)
1412
{
1413
    $alreadySandboxed = false;
1414
    $sandbox = null;
1415
    if ($withContext) {
1416
        $variables = array_merge($context, $variables);
1417
    }
1418
1419
    if ($isSandboxed = $sandboxed && $env->hasExtension('sandbox')) {
1420
        $sandbox = $env->getExtension('sandbox');
1421
        if (!$alreadySandboxed = $sandbox->isSandboxed()) {
1422
            $sandbox->enableSandbox();
1423
        }
1424
    }
1425
1426
    try {
1427
        return $env->resolveTemplate($template)->render($variables);
1428
    } catch (Twig_Error_Loader $e) {
1429
        if (!$ignoreMissing) {
1430
            throw $e;
1431
        }
1432
    }
1433
1434
    if ($isSandboxed && !$alreadySandboxed) {
1435
        $sandbox->disableSandbox();
1436
    }
1437
}
1438
1439
/**
1440
 * Returns a template content without rendering it.
1441
 *
1442
 * @param string $name The template name
1443
 *
1444
 * @return string The template source
1445
 */
1446
function twig_source(Twig_Environment $env, $name)
1447
{
1448
    return $env->getLoader()->getSource($name);
1449
}
1450
1451
/**
1452
 * Provides the ability to get constants from instances as well as class/global constants.
1453
 *
1454
 * @param string      $constant The name of the constant
1455
 * @param null|object $object   The object to get the constant from
1456
 *
1457
 * @return string
1458
 */
1459
function twig_constant($constant, $object = null)
1460
{
1461
    if (null !== $object) {
1462
        $constant = get_class($object).'::'.$constant;
1463
    }
1464
1465
    return constant($constant);
1466
}
1467
1468
/**
1469
 * Batches item.
1470
 *
1471
 * @param array   $items An array of items
1472
 * @param int     $size  The size of the batch
1473
 * @param mixed   $fill  A value used to fill missing items
1474
 *
1475
 * @return array
1476
 */
1477
function twig_array_batch($items, $size, $fill = null)
1478
{
1479
    if ($items instanceof Traversable) {
1480
        $items = iterator_to_array($items, false);
1481
    }
1482
1483
    $size = ceil($size);
1484
1485
    $result = array_chunk($items, $size, true);
1486
1487
    if (null !== $fill) {
1488
        $last = count($result) - 1;
1489
        if ($fillCount = $size - count($result[$last])) {
1490
            $result[$last] = array_merge(
1491
                $result[$last],
1492
                array_fill(0, $fillCount, $fill)
1493
            );
1494
        }
1495
    }
1496
1497
    return $result;
1498
}
1499