RequirementCollection::add()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
/*
13
 * Users of PHP 5.2 should be able to run the requirements checks.
14
 * This is why the file and all classes must be compatible with PHP 5.2+
15
 * (e.g. not using namespaces and closures).
16
 *
17
 * ************** CAUTION **************
18
 *
19
 * DO NOT EDIT THIS FILE as it will be overridden by Composer as part of
20
 * the installation/update process. The original file resides in the
21
 * SensioDistributionBundle.
22
 *
23
 * ************** CAUTION **************
24
 */
25
26
/**
27
 * Represents a single PHP requirement, e.g. an installed extension.
28
 * It can be a mandatory requirement or an optional recommendation.
29
 * There is a special subclass, named PhpIniRequirement, to check a php.ini configuration.
30
 *
31
 * @author Tobias Schultze <http://tobion.de>
32
 */
33
class Requirement
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
34
{
35
    private $fulfilled;
36
    private $testMessage;
37
    private $helpText;
38
    private $helpHtml;
39
    private $optional;
40
41
    /**
42
     * Constructor that initializes the requirement.
43
     *
44
     * @param bool        $fulfilled   Whether the requirement is fulfilled
45
     * @param string      $testMessage The message for testing the requirement
46
     * @param string      $helpHtml    The help text formatted in HTML for resolving the problem
47
     * @param string|null $helpText    The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
48
     * @param bool        $optional    Whether this is only an optional recommendation not a mandatory requirement
49
     */
50
    public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false)
51
    {
52
        $this->fulfilled = (bool) $fulfilled;
53
        $this->testMessage = (string) $testMessage;
54
        $this->helpHtml = (string) $helpHtml;
55
        $this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText;
56
        $this->optional = (bool) $optional;
57
    }
58
59
    /**
60
     * Returns whether the requirement is fulfilled.
61
     *
62
     * @return bool true if fulfilled, otherwise false
63
     */
64
    public function isFulfilled()
65
    {
66
        return $this->fulfilled;
67
    }
68
69
    /**
70
     * Returns the message for testing the requirement.
71
     *
72
     * @return string The test message
73
     */
74
    public function getTestMessage()
75
    {
76
        return $this->testMessage;
77
    }
78
79
    /**
80
     * Returns the help text for resolving the problem.
81
     *
82
     * @return string The help text
83
     */
84
    public function getHelpText()
85
    {
86
        return $this->helpText;
87
    }
88
89
    /**
90
     * Returns the help text formatted in HTML.
91
     *
92
     * @return string The HTML help
93
     */
94
    public function getHelpHtml()
95
    {
96
        return $this->helpHtml;
97
    }
98
99
    /**
100
     * Returns whether this is only an optional recommendation and not a mandatory requirement.
101
     *
102
     * @return bool true if optional, false if mandatory
103
     */
104
    public function isOptional()
105
    {
106
        return $this->optional;
107
    }
108
}
109
110
/**
111
 * Represents a PHP requirement in form of a php.ini configuration.
112
 *
113
 * @author Tobias Schultze <http://tobion.de>
114
 */
115
class PhpIniRequirement extends Requirement
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
116
{
117
    /**
118
     * Constructor that initializes the requirement.
119
     *
120
     * @param string        $cfgName           The configuration name used for ini_get()
121
     * @param bool|callback $evaluation        Either a boolean indicating whether the configuration should evaluate to true or false,
122
     *                                         or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
123
     * @param bool          $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
124
     *                                         This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
125
     *                                         Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
126
     * @param string|null   $testMessage       The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
127
     * @param string|null   $helpHtml          The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
128
     * @param string|null   $helpText          The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
129
     * @param bool          $optional          Whether this is only an optional recommendation not a mandatory requirement
130
     */
131
    public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false)
132
    {
133
        $cfgValue = ini_get($cfgName);
134
135
        if (is_callable($evaluation)) {
136
            if (null === $testMessage || null === $helpHtml) {
137
                throw new InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.');
138
            }
139
140
            $fulfilled = call_user_func($evaluation, $cfgValue);
141
        } else {
142
            if (null === $testMessage) {
143
                $testMessage = sprintf('%s %s be %s in php.ini',
144
                    $cfgName,
145
                    $optional ? 'should' : 'must',
146
                    $evaluation ? 'enabled' : 'disabled'
147
                );
148
            }
149
150
            if (null === $helpHtml) {
151
                $helpHtml = sprintf('Set <strong>%s</strong> to <strong>%s</strong> in php.ini<a href="#phpini">*</a>.',
152
                    $cfgName,
153
                    $evaluation ? 'on' : 'off'
154
                );
155
            }
156
157
            $fulfilled = $evaluation == $cfgValue;
158
        }
159
160
        parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional);
161
    }
162
}
163
164
/**
165
 * A RequirementCollection represents a set of Requirement instances.
166
 *
167
 * @author Tobias Schultze <http://tobion.de>
168
 */
169
class RequirementCollection implements IteratorAggregate
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
170
{
171
    /**
172
     * @var Requirement[]
173
     */
174
    private $requirements = array();
175
176
    /**
177
     * Gets the current RequirementCollection as an Iterator.
178
     *
179
     * @return Traversable A Traversable interface
180
     */
181
    public function getIterator()
182
    {
183
        return new ArrayIterator($this->requirements);
184
    }
185
186
    /**
187
     * Adds a Requirement.
188
     *
189
     * @param Requirement $requirement A Requirement instance
190
     */
191
    public function add(Requirement $requirement)
192
    {
193
        $this->requirements[] = $requirement;
194
    }
195
196
    /**
197
     * Adds a mandatory requirement.
198
     *
199
     * @param bool        $fulfilled   Whether the requirement is fulfilled
200
     * @param string      $testMessage The message for testing the requirement
201
     * @param string      $helpHtml    The help text formatted in HTML for resolving the problem
202
     * @param string|null $helpText    The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
203
     */
204
    public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null)
205
    {
206
        $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false));
207
    }
208
209
    /**
210
     * Adds an optional recommendation.
211
     *
212
     * @param bool        $fulfilled   Whether the recommendation is fulfilled
213
     * @param string      $testMessage The message for testing the recommendation
214
     * @param string      $helpHtml    The help text formatted in HTML for resolving the problem
215
     * @param string|null $helpText    The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
216
     */
217
    public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null)
218
    {
219
        $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true));
220
    }
221
222
    /**
223
     * Adds a mandatory requirement in form of a php.ini configuration.
224
     *
225
     * @param string        $cfgName           The configuration name used for ini_get()
226
     * @param bool|callback $evaluation        Either a boolean indicating whether the configuration should evaluate to true or false,
227
     *                                         or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
228
     * @param bool          $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
229
     *                                         This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
230
     *                                         Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
231
     * @param string        $testMessage       The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
232
     * @param string        $helpHtml          The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
233
     * @param string|null   $helpText          The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
234
     */
235
    public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
236
    {
237
        $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false));
238
    }
239
240
    /**
241
     * Adds an optional recommendation in form of a php.ini configuration.
242
     *
243
     * @param string        $cfgName           The configuration name used for ini_get()
244
     * @param bool|callback $evaluation        Either a boolean indicating whether the configuration should evaluate to true or false,
245
     *                                         or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
246
     * @param bool          $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
247
     *                                         This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
248
     *                                         Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
249
     * @param string        $testMessage       The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
250
     * @param string        $helpHtml          The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
251
     * @param string|null   $helpText          The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
252
     */
253
    public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
254
    {
255
        $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true));
256
    }
257
258
    /**
259
     * Adds a requirement collection to the current set of requirements.
260
     *
261
     * @param RequirementCollection $collection A RequirementCollection instance
262
     */
263
    public function addCollection(RequirementCollection $collection)
264
    {
265
        $this->requirements = array_merge($this->requirements, $collection->all());
266
    }
267
268
    /**
269
     * Returns both requirements and recommendations.
270
     *
271
     * @return Requirement[]
272
     */
273
    public function all()
274
    {
275
        return $this->requirements;
276
    }
277
278
    /**
279
     * Returns all mandatory requirements.
280
     *
281
     * @return Requirement[]
282
     */
283
    public function getRequirements()
284
    {
285
        $array = array();
286
        foreach ($this->requirements as $req) {
287
            if (!$req->isOptional()) {
288
                $array[] = $req;
289
            }
290
        }
291
292
        return $array;
293
    }
294
295
    /**
296
     * Returns the mandatory requirements that were not met.
297
     *
298
     * @return Requirement[]
299
     */
300
    public function getFailedRequirements()
301
    {
302
        $array = array();
303
        foreach ($this->requirements as $req) {
304
            if (!$req->isFulfilled() && !$req->isOptional()) {
305
                $array[] = $req;
306
            }
307
        }
308
309
        return $array;
310
    }
311
312
    /**
313
     * Returns all optional recommendations.
314
     *
315
     * @return Requirement[]
316
     */
317
    public function getRecommendations()
318
    {
319
        $array = array();
320
        foreach ($this->requirements as $req) {
321
            if ($req->isOptional()) {
322
                $array[] = $req;
323
            }
324
        }
325
326
        return $array;
327
    }
328
329
    /**
330
     * Returns the recommendations that were not met.
331
     *
332
     * @return Requirement[]
333
     */
334
    public function getFailedRecommendations()
335
    {
336
        $array = array();
337
        foreach ($this->requirements as $req) {
338
            if (!$req->isFulfilled() && $req->isOptional()) {
339
                $array[] = $req;
340
            }
341
        }
342
343
        return $array;
344
    }
345
346
    /**
347
     * Returns whether a php.ini configuration is not correct.
348
     *
349
     * @return bool php.ini configuration problem?
350
     */
351
    public function hasPhpIniConfigIssue()
352
    {
353
        foreach ($this->requirements as $req) {
354
            if (!$req->isFulfilled() && $req instanceof PhpIniRequirement) {
355
                return true;
356
            }
357
        }
358
359
        return false;
360
    }
361
362
    /**
363
     * Returns the PHP configuration file (php.ini) path.
364
     *
365
     * @return string|false php.ini file path
366
     */
367
    public function getPhpIniConfigPath()
368
    {
369
        return get_cfg_var('cfg_file_path');
370
    }
371
}
372
373
/**
374
 * This class specifies all requirements and optional recommendations that
375
 * are necessary to run the Symfony Standard Edition.
376
 *
377
 * @author Tobias Schultze <http://tobion.de>
378
 * @author Fabien Potencier <[email protected]>
379
 */
380
class SymfonyRequirements extends RequirementCollection
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
381
{
382
    const LEGACY_REQUIRED_PHP_VERSION = '5.3.3';
383
    const REQUIRED_PHP_VERSION = '5.5.9';
384
385
    /**
386
     * Constructor that initializes the requirements.
387
     */
388
    public function __construct()
389
    {
390
        /* mandatory requirements follow */
391
392
        $installedPhpVersion = phpversion();
393
        $requiredPhpVersion = $this->getPhpRequiredVersion();
394
395
        $this->addRecommendation(
396
            $requiredPhpVersion,
0 ignored issues
show
Bug introduced by
It seems like $requiredPhpVersion defined by $this->getPhpRequiredVersion() on line 393 can also be of type string; however, RequirementCollection::addRecommendation() does only seem to accept boolean, 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...
397
            'Vendors should be installed in order to check all requirements.',
398
            'Run the <code>composer install</code> command.',
399
            'Run the "composer install" command.'
400
        );
401
402
        if (false !== $requiredPhpVersion) {
403
            $this->addRequirement(
404
                version_compare($installedPhpVersion, $requiredPhpVersion, '>='),
405
                sprintf('PHP version must be at least %s (%s installed)', $requiredPhpVersion, $installedPhpVersion),
406
                sprintf('You are running PHP version "<strong>%s</strong>", but Symfony needs at least PHP "<strong>%s</strong>" to run.
407
                Before using Symfony, upgrade your PHP installation, preferably to the latest version.',
408
                    $installedPhpVersion, $requiredPhpVersion),
409
                sprintf('Install PHP %s or newer (installed version is %s)', $requiredPhpVersion, $installedPhpVersion)
410
            );
411
        }
412
413
        $this->addRequirement(
414
            version_compare($installedPhpVersion, '5.3.16', '!='),
415
            'PHP version must not be 5.3.16 as Symfony won\'t work properly with it',
416
            'Install PHP 5.3.17 or newer (or downgrade to an earlier PHP version)'
417
        );
418
419
        $this->addRequirement(
420
            is_dir(__DIR__.'/../vendor/composer'),
421
            'Vendor libraries must be installed',
422
            'Vendor libraries are missing. Install composer following instructions from <a href="http://getcomposer.org/">http://getcomposer.org/</a>. '.
423
                'Then run "<strong>php composer.phar install</strong>" to install them.'
424
        );
425
426
        $cacheDir = is_dir(__DIR__.'/../var/cache') ? __DIR__.'/../var/cache' : __DIR__.'/cache';
427
428
        $this->addRequirement(
429
            is_writable($cacheDir),
430
            'app/cache/ or var/cache/ directory must be writable',
431
            'Change the permissions of either "<strong>app/cache/</strong>" or  "<strong>var/cache/</strong>" directory so that the web server can write into it.'
432
        );
433
434
        $logsDir = is_dir(__DIR__.'/../var/logs') ? __DIR__.'/../var/logs' : __DIR__.'/logs';
435
436
        $this->addRequirement(
437
            is_writable($logsDir),
438
            'app/logs/ or var/logs/ directory must be writable',
439
            'Change the permissions of either "<strong>app/logs/</strong>" or  "<strong>var/logs/</strong>" directory so that the web server can write into it.'
440
        );
441
442
        if (version_compare($installedPhpVersion, '7.0.0', '<')) {
443
            $this->addPhpIniRequirement(
444
                'date.timezone', true, false,
445
                'date.timezone setting must be set',
446
                'Set the "<strong>date.timezone</strong>" setting in php.ini<a href="#phpini">*</a> (like Europe/Paris).'
447
            );
448
        }
449
450
        if (false !== $requiredPhpVersion && version_compare($installedPhpVersion, $requiredPhpVersion, '>=')) {
451
            $timezones = array();
452
            foreach (DateTimeZone::listAbbreviations() as $abbreviations) {
453
                foreach ($abbreviations as $abbreviation) {
454
                    $timezones[$abbreviation['timezone_id']] = true;
455
                }
456
            }
457
458
            $this->addRequirement(
459
                isset($timezones[@date_default_timezone_get()]),
460
                sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()),
461
                'Your default timezone is not supported by PHP. Check for typos in your <strong>php.ini</strong> file and have a look at the list of deprecated timezones at <a href="http://php.net/manual/en/timezones.others.php">http://php.net/manual/en/timezones.others.php</a>.'
462
            );
463
        }
464
465
        $this->addRequirement(
466
            function_exists('iconv'),
467
            'iconv() must be available',
468
            'Install and enable the <strong>iconv</strong> extension.'
469
        );
470
471
        $this->addRequirement(
472
            function_exists('json_encode'),
473
            'json_encode() must be available',
474
            'Install and enable the <strong>JSON</strong> extension.'
475
        );
476
477
        $this->addRequirement(
478
            function_exists('session_start'),
479
            'session_start() must be available',
480
            'Install and enable the <strong>session</strong> extension.'
481
        );
482
483
        $this->addRequirement(
484
            function_exists('ctype_alpha'),
485
            'ctype_alpha() must be available',
486
            'Install and enable the <strong>ctype</strong> extension.'
487
        );
488
489
        $this->addRequirement(
490
            function_exists('token_get_all'),
491
            'token_get_all() must be available',
492
            'Install and enable the <strong>Tokenizer</strong> extension.'
493
        );
494
495
        $this->addRequirement(
496
            function_exists('simplexml_import_dom'),
497
            'simplexml_import_dom() must be available',
498
            'Install and enable the <strong>SimpleXML</strong> extension.'
499
        );
500
501
        if (function_exists('apc_store') && ini_get('apc.enabled')) {
502
            if (version_compare($installedPhpVersion, '5.4.0', '>=')) {
503
                $this->addRequirement(
504
                    version_compare(phpversion('apc'), '3.1.13', '>='),
505
                    'APC version must be at least 3.1.13 when using PHP 5.4',
506
                    'Upgrade your <strong>APC</strong> extension (3.1.13+).'
507
                );
508
            } else {
509
                $this->addRequirement(
510
                    version_compare(phpversion('apc'), '3.0.17', '>='),
511
                    'APC version must be at least 3.0.17',
512
                    'Upgrade your <strong>APC</strong> extension (3.0.17+).'
513
                );
514
            }
515
        }
516
517
        $this->addPhpIniRequirement('detect_unicode', false);
518
519
        if (extension_loaded('suhosin')) {
520
            $this->addPhpIniRequirement(
521
                'suhosin.executor.include.whitelist',
522
                create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'),
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
523
                false,
524
                'suhosin.executor.include.whitelist must be configured correctly in php.ini',
525
                'Add "<strong>phar</strong>" to <strong>suhosin.executor.include.whitelist</strong> in php.ini<a href="#phpini">*</a>.'
526
            );
527
        }
528
529
        if (extension_loaded('xdebug')) {
530
            $this->addPhpIniRequirement(
531
                'xdebug.show_exception_trace', false, true
532
            );
533
534
            $this->addPhpIniRequirement(
535
                'xdebug.scream', false, true
536
            );
537
538
            $this->addPhpIniRecommendation(
539
                'xdebug.max_nesting_level',
540
                create_function('$cfgValue', 'return $cfgValue > 100;'),
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
541
                true,
542
                'xdebug.max_nesting_level should be above 100 in php.ini',
543
                'Set "<strong>xdebug.max_nesting_level</strong>" to e.g. "<strong>250</strong>" in php.ini<a href="#phpini">*</a> to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.'
544
            );
545
        }
546
547
        $pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null;
548
549
        $this->addRequirement(
550
            null !== $pcreVersion,
551
            'PCRE extension must be available',
552
            'Install the <strong>PCRE</strong> extension (version 8.0+).'
553
        );
554
555
        if (extension_loaded('mbstring')) {
556
            $this->addPhpIniRequirement(
557
                'mbstring.func_overload',
558
                create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
559
                true,
560
                'string functions should not be overloaded',
561
                'Set "<strong>mbstring.func_overload</strong>" to <strong>0</strong> in php.ini<a href="#phpini">*</a> to disable function overloading by the mbstring extension.'
562
            );
563
        }
564
565
        /* optional recommendations follow */
566
567
        if (file_exists(__DIR__.'/../vendor/composer')) {
568
            require_once __DIR__.'/../vendor/autoload.php';
569
570
            try {
571
                $r = new ReflectionClass('Sensio\Bundle\DistributionBundle\SensioDistributionBundle');
572
573
                $contents = file_get_contents(dirname($r->getFileName()).'/Resources/skeleton/app/SymfonyRequirements.php');
574
            } catch (ReflectionException $e) {
575
                $contents = '';
576
            }
577
            $this->addRecommendation(
578
                file_get_contents(__FILE__) === $contents,
579
                'Requirements file should be up-to-date',
580
                'Your requirements file is outdated. Run composer install and re-check your configuration.'
581
            );
582
        }
583
584
        $this->addRecommendation(
585
            version_compare($installedPhpVersion, '5.3.4', '>='),
586
            'You should use at least PHP 5.3.4 due to PHP bug #52083 in earlier versions',
587
            'Your project might malfunction randomly due to PHP bug #52083 ("Notice: Trying to get property of non-object"). Install PHP 5.3.4 or newer.'
588
        );
589
590
        $this->addRecommendation(
591
            version_compare($installedPhpVersion, '5.3.8', '>='),
592
            'When using annotations you should have at least PHP 5.3.8 due to PHP bug #55156',
593
            'Install PHP 5.3.8 or newer if your project uses annotations.'
594
        );
595
596
        $this->addRecommendation(
597
            version_compare($installedPhpVersion, '5.4.0', '!='),
598
            'You should not use PHP 5.4.0 due to the PHP bug #61453',
599
            'Your project might not work properly due to the PHP bug #61453 ("Cannot dump definitions which have method calls"). Install PHP 5.4.1 or newer.'
600
        );
601
602
        $this->addRecommendation(
603
            version_compare($installedPhpVersion, '5.4.11', '>='),
604
            'When using the logout handler from the Symfony Security Component, you should have at least PHP 5.4.11 due to PHP bug #63379 (as a workaround, you can also set invalidate_session to false in the security logout handler configuration)',
605
            'Install PHP 5.4.11 or newer if your project uses the logout handler from the Symfony Security Component.'
606
        );
607
608
        $this->addRecommendation(
609
            (version_compare($installedPhpVersion, '5.3.18', '>=') && version_compare($installedPhpVersion, '5.4.0', '<'))
610
            ||
611
            version_compare($installedPhpVersion, '5.4.8', '>='),
612
            'You should use PHP 5.3.18+ or PHP 5.4.8+ to always get nice error messages for fatal errors in the development environment due to PHP bug #61767/#60909',
613
            'Install PHP 5.3.18+ or PHP 5.4.8+ if you want nice error messages for all fatal errors in the development environment.'
614
        );
615
616
        if (null !== $pcreVersion) {
617
            $this->addRecommendation(
618
                $pcreVersion >= 8.0,
619
                sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion),
620
                '<strong>PCRE 8.0+</strong> is preconfigured in PHP since 5.3.2 but you are using an outdated version of it. Symfony probably works anyway but it is recommended to upgrade your PCRE extension.'
621
            );
622
        }
623
624
        $this->addRecommendation(
625
            class_exists('DomDocument'),
626
            'PHP-DOM and PHP-XML modules should be installed',
627
            'Install and enable the <strong>PHP-DOM</strong> and the <strong>PHP-XML</strong> modules.'
628
        );
629
630
        $this->addRecommendation(
631
            function_exists('mb_strlen'),
632
            'mb_strlen() should be available',
633
            'Install and enable the <strong>mbstring</strong> extension.'
634
        );
635
636
        $this->addRecommendation(
637
            function_exists('utf8_decode'),
638
            'utf8_decode() should be available',
639
            'Install and enable the <strong>XML</strong> extension.'
640
        );
641
642
        $this->addRecommendation(
643
            function_exists('filter_var'),
644
            'filter_var() should be available',
645
            'Install and enable the <strong>filter</strong> extension.'
646
        );
647
648
        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
649
            $this->addRecommendation(
650
                function_exists('posix_isatty'),
651
                'posix_isatty() should be available',
652
                'Install and enable the <strong>php_posix</strong> extension (used to colorize the CLI output).'
653
            );
654
        }
655
656
        $this->addRecommendation(
657
            extension_loaded('intl'),
658
            'intl extension should be available',
659
            'Install and enable the <strong>intl</strong> extension (used for validators).'
660
        );
661
662
        if (extension_loaded('intl')) {
663
            // in some WAMP server installations, new Collator() returns null
664
            $this->addRecommendation(
665
                null !== new Collator('fr_FR'),
666
                'intl extension should be correctly configured',
667
                'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.'
668
            );
669
670
            // check for compatible ICU versions (only done when you have the intl extension)
671
            if (defined('INTL_ICU_VERSION')) {
672
                $version = INTL_ICU_VERSION;
673
            } else {
674
                $reflector = new ReflectionExtension('intl');
675
676
                ob_start();
677
                $reflector->info();
678
                $output = strip_tags(ob_get_clean());
679
680
                preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches);
681
                $version = $matches[1];
682
            }
683
684
            $this->addRecommendation(
685
                version_compare($version, '4.0', '>='),
686
                'intl ICU version should be at least 4+',
687
                'Upgrade your <strong>intl</strong> extension with a newer ICU version (4+).'
688
            );
689
690
            if (class_exists('Symfony\Component\Intl\Intl')) {
691
                $this->addRecommendation(
692
                    \Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion(),
693
                    sprintf('intl ICU version installed on your system is outdated (%s) and does not match the ICU data bundled with Symfony (%s)', \Symfony\Component\Intl\Intl::getIcuVersion(), \Symfony\Component\Intl\Intl::getIcuDataVersion()),
694
                    'To get the latest internationalization data upgrade the ICU system package and the intl PHP extension.'
695
                );
696
                if (\Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion()) {
697
                    $this->addRecommendation(
698
                        \Symfony\Component\Intl\Intl::getIcuDataVersion() === \Symfony\Component\Intl\Intl::getIcuVersion(),
699
                        sprintf('intl ICU version installed on your system (%s) does not match the ICU data bundled with Symfony (%s)', \Symfony\Component\Intl\Intl::getIcuVersion(), \Symfony\Component\Intl\Intl::getIcuDataVersion()),
700
                        'To avoid internationalization data inconsistencies upgrade the symfony/intl component.'
701
                    );
702
                }
703
            }
704
705
            $this->addPhpIniRecommendation(
706
                'intl.error_level',
707
                create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
708
                true,
709
                'intl.error_level should be 0 in php.ini',
710
                'Set "<strong>intl.error_level</strong>" to "<strong>0</strong>" in php.ini<a href="#phpini">*</a> to inhibit the messages when an error occurs in ICU functions.'
711
            );
712
        }
713
714
        $accelerator =
715
            (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'))
716
            ||
717
            (extension_loaded('apc') && ini_get('apc.enabled'))
718
            ||
719
            (extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable'))
720
            ||
721
            (extension_loaded('Zend OPcache') && ini_get('opcache.enable'))
722
            ||
723
            (extension_loaded('xcache') && ini_get('xcache.cacher'))
724
            ||
725
            (extension_loaded('wincache') && ini_get('wincache.ocenabled'))
726
        ;
727
728
        $this->addRecommendation(
729
            $accelerator,
730
            'a PHP accelerator should be installed',
731
            'Install and/or enable a <strong>PHP accelerator</strong> (highly recommended).'
732
        );
733
734
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
735
            $this->addRecommendation(
736
                $this->getRealpathCacheSize() >= 5 * 1024 * 1024,
737
                'realpath_cache_size should be at least 5M in php.ini',
738
                'Setting "<strong>realpath_cache_size</strong>" to e.g. "<strong>5242880</strong>" or "<strong>5M</strong>" in php.ini<a href="#phpini">*</a> may improve performance on Windows significantly in some cases.'
739
            );
740
        }
741
742
        $this->addPhpIniRecommendation('short_open_tag', false);
743
744
        $this->addPhpIniRecommendation('magic_quotes_gpc', false, true);
745
746
        $this->addPhpIniRecommendation('register_globals', false, true);
747
748
        $this->addPhpIniRecommendation('session.auto_start', false);
749
750
        $this->addRecommendation(
751
            class_exists('PDO'),
752
            'PDO should be installed',
753
            'Install <strong>PDO</strong> (mandatory for Doctrine).'
754
        );
755
756
        if (class_exists('PDO')) {
757
            $drivers = PDO::getAvailableDrivers();
758
            $this->addRecommendation(
759
                count($drivers) > 0,
760
                sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'),
761
                'Install <strong>PDO drivers</strong> (mandatory for Doctrine).'
762
            );
763
        }
764
    }
765
766
    /**
767
     * Loads realpath_cache_size from php.ini and converts it to int.
768
     *
769
     * (e.g. 16k is converted to 16384 int)
770
     *
771
     * @return int
772
     */
773
    protected function getRealpathCacheSize()
774
    {
775
        $size = ini_get('realpath_cache_size');
776
        $size = trim($size);
777
        $unit = '';
778
        if (!ctype_digit($size)) {
779
            $unit = strtolower(substr($size, -1, 1));
780
            $size = (int) substr($size, 0, -1);
781
        }
782
        switch ($unit) {
783
            case 'g':
784
                return $size * 1024 * 1024 * 1024;
785
            case 'm':
786
                return $size * 1024 * 1024;
787
            case 'k':
788
                return $size * 1024;
789
            default:
790
                return (int) $size;
791
        }
792
    }
793
794
    /**
795
     * Defines PHP required version from Symfony version.
796
     *
797
     * @return string|false The PHP required version or false if it could not be guessed
798
     */
799
    protected function getPhpRequiredVersion()
800
    {
801
        if (!file_exists($path = __DIR__.'/../composer.lock')) {
802
            return false;
803
        }
804
805
        $composerLock = json_decode(file_get_contents($path), true);
806
        foreach ($composerLock['packages'] as $package) {
807
            $name = $package['name'];
808
            if ('symfony/symfony' !== $name && 'symfony/http-kernel' !== $name) {
809
                continue;
810
            }
811
812
            return (int) $package['version'][1] > 2 ? self::REQUIRED_PHP_VERSION : self::LEGACY_REQUIRED_PHP_VERSION;
813
        }
814
815
        return false;
816
    }
817
}
818