Completed
Push — master ( cc173b...1bbcf8 )
by Peter
21:26
created

var/SymfonyRequirements.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
    private $requirements = array();
172
173
    /**
174
     * Gets the current RequirementCollection as an Iterator.
175
     *
176
     * @return Traversable A Traversable interface
177
     */
178
    public function getIterator()
179
    {
180
        return new ArrayIterator($this->requirements);
181
    }
182
183
    /**
184
     * Adds a Requirement.
185
     *
186
     * @param Requirement $requirement A Requirement instance
187
     */
188
    public function add(Requirement $requirement)
189
    {
190
        $this->requirements[] = $requirement;
191
    }
192
193
    /**
194
     * Adds a mandatory requirement.
195
     *
196
     * @param bool        $fulfilled   Whether the requirement is fulfilled
197
     * @param string      $testMessage The message for testing the requirement
198
     * @param string      $helpHtml    The help text formatted in HTML for resolving the problem
199
     * @param string|null $helpText    The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
200
     */
201
    public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null)
202
    {
203
        $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false));
204
    }
205
206
    /**
207
     * Adds an optional recommendation.
208
     *
209
     * @param bool        $fulfilled   Whether the recommendation is fulfilled
210
     * @param string      $testMessage The message for testing the recommendation
211
     * @param string      $helpHtml    The help text formatted in HTML for resolving the problem
212
     * @param string|null $helpText    The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
213
     */
214
    public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null)
215
    {
216
        $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true));
217
    }
218
219
    /**
220
     * Adds a mandatory requirement in form of a php.ini configuration.
221
     *
222
     * @param string        $cfgName           The configuration name used for ini_get()
223
     * @param bool|callback $evaluation        Either a boolean indicating whether the configuration should evaluate to true or false,
224
     *                                         or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
225
     * @param bool          $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
226
     *                                         This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
227
     *                                         Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
228
     * @param string        $testMessage       The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
229
     * @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)
230
     * @param string|null   $helpText          The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
231
     */
232
    public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
233
    {
234
        $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false));
235
    }
236
237
    /**
238
     * Adds an optional recommendation in form of a php.ini configuration.
239
     *
240
     * @param string        $cfgName           The configuration name used for ini_get()
241
     * @param bool|callback $evaluation        Either a boolean indicating whether the configuration should evaluate to true or false,
242
     *                                         or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
243
     * @param bool          $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
244
     *                                         This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
245
     *                                         Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
246
     * @param string        $testMessage       The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
247
     * @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)
248
     * @param string|null   $helpText          The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
249
     */
250
    public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
251
    {
252
        $this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true));
253
    }
254
255
    /**
256
     * Adds a requirement collection to the current set of requirements.
257
     *
258
     * @param RequirementCollection $collection A RequirementCollection instance
259
     */
260
    public function addCollection(RequirementCollection $collection)
261
    {
262
        $this->requirements = array_merge($this->requirements, $collection->all());
263
    }
264
265
    /**
266
     * Returns both requirements and recommendations.
267
     *
268
     * @return array Array of Requirement instances
269
     */
270
    public function all()
271
    {
272
        return $this->requirements;
273
    }
274
275
    /**
276
     * Returns all mandatory requirements.
277
     *
278
     * @return array Array of Requirement instances
279
     */
280
    public function getRequirements()
281
    {
282
        $array = array();
283
        foreach ($this->requirements as $req) {
284
            if (!$req->isOptional()) {
285
                $array[] = $req;
286
            }
287
        }
288
289
        return $array;
290
    }
291
292
    /**
293
     * Returns the mandatory requirements that were not met.
294
     *
295
     * @return array Array of Requirement instances
296
     */
297
    public function getFailedRequirements()
298
    {
299
        $array = array();
300
        foreach ($this->requirements as $req) {
301
            if (!$req->isFulfilled() && !$req->isOptional()) {
302
                $array[] = $req;
303
            }
304
        }
305
306
        return $array;
307
    }
308
309
    /**
310
     * Returns all optional recommendations.
311
     *
312
     * @return array Array of Requirement instances
313
     */
314
    public function getRecommendations()
315
    {
316
        $array = array();
317
        foreach ($this->requirements as $req) {
318
            if ($req->isOptional()) {
319
                $array[] = $req;
320
            }
321
        }
322
323
        return $array;
324
    }
325
326
    /**
327
     * Returns the recommendations that were not met.
328
     *
329
     * @return array Array of Requirement instances
330
     */
331
    public function getFailedRecommendations()
332
    {
333
        $array = array();
334
        foreach ($this->requirements as $req) {
335
            if (!$req->isFulfilled() && $req->isOptional()) {
336
                $array[] = $req;
337
            }
338
        }
339
340
        return $array;
341
    }
342
343
    /**
344
     * Returns whether a php.ini configuration is not correct.
345
     *
346
     * @return bool php.ini configuration problem?
347
     */
348
    public function hasPhpIniConfigIssue()
349
    {
350
        foreach ($this->requirements as $req) {
351
            if (!$req->isFulfilled() && $req instanceof PhpIniRequirement) {
352
                return true;
353
            }
354
        }
355
356
        return false;
357
    }
358
359
    /**
360
     * Returns the PHP configuration file (php.ini) path.
361
     *
362
     * @return string|false php.ini file path
363
     */
364
    public function getPhpIniConfigPath()
365
    {
366
        return get_cfg_var('cfg_file_path');
367
    }
368
}
369
370
/**
371
 * This class specifies all requirements and optional recommendations that
372
 * are necessary to run the Symfony Standard Edition.
373
 *
374
 * @author Tobias Schultze <http://tobion.de>
375
 * @author Fabien Potencier <[email protected]>
376
 */
377
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...
378
{
379
    const REQUIRED_PHP_VERSION = '5.3.3';
380
381
    /**
382
     * Constructor that initializes the requirements.
383
     */
384
    public function __construct()
385
    {
386
        /* mandatory requirements follow */
387
388
        $installedPhpVersion = phpversion();
389
390
        $this->addRequirement(
391
            version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>='),
392
            sprintf('PHP version must be at least %s (%s installed)', self::REQUIRED_PHP_VERSION, $installedPhpVersion),
393
            sprintf('You are running PHP version "<strong>%s</strong>", but Symfony needs at least PHP "<strong>%s</strong>" to run.
394
                Before using Symfony, upgrade your PHP installation, preferably to the latest version.',
395
                $installedPhpVersion, self::REQUIRED_PHP_VERSION),
396
            sprintf('Install PHP %s or newer (installed version is %s)', self::REQUIRED_PHP_VERSION, $installedPhpVersion)
397
        );
398
399
        $this->addRequirement(
400
            version_compare($installedPhpVersion, '5.3.16', '!='),
401
            'PHP version must not be 5.3.16 as Symfony won\'t work properly with it',
402
            'Install PHP 5.3.17 or newer (or downgrade to an earlier PHP version)'
403
        );
404
405
        $this->addRequirement(
406
            is_dir(__DIR__.'/../vendor/composer'),
407
            'Vendor libraries must be installed',
408
            'Vendor libraries are missing. Install composer following instructions from <a href="http://getcomposer.org/">http://getcomposer.org/</a>. '.
409
                'Then run "<strong>php composer.phar install</strong>" to install them.'
410
        );
411
412
        $cacheDir = is_dir(__DIR__.'/../var/cache') ? __DIR__.'/../var/cache' : __DIR__.'/cache';
413
414
        $this->addRequirement(
415
            is_writable($cacheDir),
416
            'app/cache/ or var/cache/ directory must be writable',
417
            'Change the permissions of either "<strong>app/cache/</strong>" or  "<strong>var/cache/</strong>" directory so that the web server can write into it.'
418
        );
419
420
        $logsDir = is_dir(__DIR__.'/../var/logs') ? __DIR__.'/../var/logs' : __DIR__.'/logs';
421
422
        $this->addRequirement(
423
            is_writable($logsDir),
424
            'app/logs/ or var/logs/ directory must be writable',
425
            'Change the permissions of either "<strong>app/logs/</strong>" or  "<strong>var/logs/</strong>" directory so that the web server can write into it.'
426
        );
427
428
        if (version_compare($installedPhpVersion, '7.0.0', '<')) {
429
            $this->addPhpIniRequirement(
430
                'date.timezone', true, false,
431
                'date.timezone setting must be set',
432
                'Set the "<strong>date.timezone</strong>" setting in php.ini<a href="#phpini">*</a> (like Europe/Paris).'
433
            );
434
        }
435
436
        if (version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>=')) {
437
            $timezones = array();
438
            foreach (DateTimeZone::listAbbreviations() as $abbreviations) {
439
                foreach ($abbreviations as $abbreviation) {
440
                    $timezones[$abbreviation['timezone_id']] = true;
441
                }
442
            }
443
444
            $this->addRequirement(
445
                isset($timezones[@date_default_timezone_get()]),
446
                sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()),
447
                '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>.'
448
            );
449
        }
450
451
        $this->addRequirement(
452
            function_exists('iconv'),
453
            'iconv() must be available',
454
            'Install and enable the <strong>iconv</strong> extension.'
455
        );
456
457
        $this->addRequirement(
458
            function_exists('json_encode'),
459
            'json_encode() must be available',
460
            'Install and enable the <strong>JSON</strong> extension.'
461
        );
462
463
        $this->addRequirement(
464
            function_exists('session_start'),
465
            'session_start() must be available',
466
            'Install and enable the <strong>session</strong> extension.'
467
        );
468
469
        $this->addRequirement(
470
            function_exists('ctype_alpha'),
471
            'ctype_alpha() must be available',
472
            'Install and enable the <strong>ctype</strong> extension.'
473
        );
474
475
        $this->addRequirement(
476
            function_exists('token_get_all'),
477
            'token_get_all() must be available',
478
            'Install and enable the <strong>Tokenizer</strong> extension.'
479
        );
480
481
        $this->addRequirement(
482
            function_exists('simplexml_import_dom'),
483
            'simplexml_import_dom() must be available',
484
            'Install and enable the <strong>SimpleXML</strong> extension.'
485
        );
486
487
        if (function_exists('apc_store') && ini_get('apc.enabled')) {
488
            if (version_compare($installedPhpVersion, '5.4.0', '>=')) {
489
                $this->addRequirement(
490
                    version_compare(phpversion('apc'), '3.1.13', '>='),
491
                    'APC version must be at least 3.1.13 when using PHP 5.4',
492
                    'Upgrade your <strong>APC</strong> extension (3.1.13+).'
493
                );
494
            } else {
495
                $this->addRequirement(
496
                    version_compare(phpversion('apc'), '3.0.17', '>='),
497
                    'APC version must be at least 3.0.17',
498
                    'Upgrade your <strong>APC</strong> extension (3.0.17+).'
499
                );
500
            }
501
        }
502
503
        $this->addPhpIniRequirement('detect_unicode', false);
504
505
        if (extension_loaded('suhosin')) {
506
            $this->addPhpIniRequirement(
507
                'suhosin.executor.include.whitelist',
508
                create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'),
509
                false,
510
                'suhosin.executor.include.whitelist must be configured correctly in php.ini',
511
                'Add "<strong>phar</strong>" to <strong>suhosin.executor.include.whitelist</strong> in php.ini<a href="#phpini">*</a>.'
512
            );
513
        }
514
515
        if (extension_loaded('xdebug')) {
516
            $this->addPhpIniRequirement(
517
                'xdebug.show_exception_trace', false, true
518
            );
519
520
            $this->addPhpIniRequirement(
521
                'xdebug.scream', false, true
522
            );
523
524
            $this->addPhpIniRecommendation(
525
                'xdebug.max_nesting_level',
526
                create_function('$cfgValue', 'return $cfgValue > 100;'),
527
                true,
528
                'xdebug.max_nesting_level should be above 100 in php.ini',
529
                '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.'
530
            );
531
        }
532
533
        $pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null;
534
535
        $this->addRequirement(
536
            null !== $pcreVersion,
537
            'PCRE extension must be available',
538
            'Install the <strong>PCRE</strong> extension (version 8.0+).'
539
        );
540
541
        if (extension_loaded('mbstring')) {
542
            $this->addPhpIniRequirement(
543
                'mbstring.func_overload',
544
                create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
545
                true,
546
                'string functions should not be overloaded',
547
                '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.'
548
            );
549
        }
550
551
        /* optional recommendations follow */
552
553
        if (file_exists(__DIR__.'/../vendor/composer')) {
554
            require_once __DIR__.'/../vendor/autoload.php';
555
556
            try {
557
                $r = new ReflectionClass('Sensio\Bundle\DistributionBundle\SensioDistributionBundle');
558
559
                $contents = file_get_contents(dirname($r->getFileName()).'/Resources/skeleton/app/SymfonyRequirements.php');
560
            } catch (ReflectionException $e) {
561
                $contents = '';
562
            }
563
            $this->addRecommendation(
564
                file_get_contents(__FILE__) === $contents,
565
                'Requirements file should be up-to-date',
566
                'Your requirements file is outdated. Run composer install and re-check your configuration.'
567
            );
568
        }
569
570
        $this->addRecommendation(
571
            version_compare($installedPhpVersion, '5.3.4', '>='),
572
            'You should use at least PHP 5.3.4 due to PHP bug #52083 in earlier versions',
573
            '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.'
574
        );
575
576
        $this->addRecommendation(
577
            version_compare($installedPhpVersion, '5.3.8', '>='),
578
            'When using annotations you should have at least PHP 5.3.8 due to PHP bug #55156',
579
            'Install PHP 5.3.8 or newer if your project uses annotations.'
580
        );
581
582
        $this->addRecommendation(
583
            version_compare($installedPhpVersion, '5.4.0', '!='),
584
            'You should not use PHP 5.4.0 due to the PHP bug #61453',
585
            '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.'
586
        );
587
588
        $this->addRecommendation(
589
            version_compare($installedPhpVersion, '5.4.11', '>='),
590
            '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)',
591
            'Install PHP 5.4.11 or newer if your project uses the logout handler from the Symfony Security Component.'
592
        );
593
594
        $this->addRecommendation(
595
            (version_compare($installedPhpVersion, '5.3.18', '>=') && version_compare($installedPhpVersion, '5.4.0', '<'))
596
            ||
597
            version_compare($installedPhpVersion, '5.4.8', '>='),
598
            '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',
599
            'Install PHP 5.3.18+ or PHP 5.4.8+ if you want nice error messages for all fatal errors in the development environment.'
600
        );
601
602
        if (null !== $pcreVersion) {
603
            $this->addRecommendation(
604
                $pcreVersion >= 8.0,
605
                sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion),
606
                '<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.'
607
            );
608
        }
609
610
        $this->addRecommendation(
611
            class_exists('DomDocument'),
612
            'PHP-DOM and PHP-XML modules should be installed',
613
            'Install and enable the <strong>PHP-DOM</strong> and the <strong>PHP-XML</strong> modules.'
614
        );
615
616
        $this->addRecommendation(
617
            function_exists('mb_strlen'),
618
            'mb_strlen() should be available',
619
            'Install and enable the <strong>mbstring</strong> extension.'
620
        );
621
622
        $this->addRecommendation(
623
            function_exists('iconv'),
624
            'iconv() should be available',
625
            'Install and enable the <strong>iconv</strong> extension.'
626
        );
627
628
        $this->addRecommendation(
629
            function_exists('utf8_decode'),
630
            'utf8_decode() should be available',
631
            'Install and enable the <strong>XML</strong> extension.'
632
        );
633
634
        $this->addRecommendation(
635
            function_exists('filter_var'),
636
            'filter_var() should be available',
637
            'Install and enable the <strong>filter</strong> extension.'
638
        );
639
640
        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
641
            $this->addRecommendation(
642
                function_exists('posix_isatty'),
643
                'posix_isatty() should be available',
644
                'Install and enable the <strong>php_posix</strong> extension (used to colorize the CLI output).'
645
            );
646
        }
647
648
        $this->addRecommendation(
649
            extension_loaded('intl'),
650
            'intl extension should be available',
651
            'Install and enable the <strong>intl</strong> extension (used for validators).'
652
        );
653
654
        if (extension_loaded('intl')) {
655
            // in some WAMP server installations, new Collator() returns null
656
            $this->addRecommendation(
657
                null !== new Collator('fr_FR'),
658
                'intl extension should be correctly configured',
659
                'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.'
660
            );
661
662
            // check for compatible ICU versions (only done when you have the intl extension)
663
            if (defined('INTL_ICU_VERSION')) {
664
                $version = INTL_ICU_VERSION;
665
            } else {
666
                $reflector = new ReflectionExtension('intl');
667
668
                ob_start();
669
                $reflector->info();
670
                $output = strip_tags(ob_get_clean());
671
672
                preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches);
673
                $version = $matches[1];
674
            }
675
676
            $this->addRecommendation(
677
                version_compare($version, '4.0', '>='),
678
                'intl ICU version should be at least 4+',
679
                'Upgrade your <strong>intl</strong> extension with a newer ICU version (4+).'
680
            );
681
682
            if (class_exists('Symfony\Component\Intl\Intl')) {
683
                $this->addRecommendation(
684
                    \Symfony\Component\Intl\Intl::getIcuDataVersion() === \Symfony\Component\Intl\Intl::getIcuVersion(),
685
                    sprintf('intl ICU version installed on your system (%s) should match the ICU data bundled with Symfony (%s)', \Symfony\Component\Intl\Intl::getIcuVersion(), \Symfony\Component\Intl\Intl::getIcuDataVersion()),
686
                    'In most cases you should be fine, but please verify there is no inconsistencies between data provided by Symfony and the intl extension. See https://github.com/symfony/symfony/issues/15007 for an example of inconsistencies you might run into.'
687
                );
688
            }
689
690
            $this->addPhpIniRecommendation(
691
                'intl.error_level',
692
                create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
693
                true,
694
                'intl.error_level should be 0 in php.ini',
695
                '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.'
696
            );
697
        }
698
699
        $accelerator =
700
            (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'))
701
            ||
702
            (extension_loaded('apc') && ini_get('apc.enabled'))
703
            ||
704
            (extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable'))
705
            ||
706
            (extension_loaded('Zend OPcache') && ini_get('opcache.enable'))
707
            ||
708
            (extension_loaded('xcache') && ini_get('xcache.cacher'))
709
            ||
710
            (extension_loaded('wincache') && ini_get('wincache.ocenabled'))
711
        ;
712
713
        $this->addRecommendation(
714
            $accelerator,
715
            'a PHP accelerator should be installed',
716
            'Install and/or enable a <strong>PHP accelerator</strong> (highly recommended).'
717
        );
718
719
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
720
            $this->addRecommendation(
721
                $this->getRealpathCacheSize() > 1000,
722
                'realpath_cache_size should be above 1024 in php.ini',
723
                'Set "<strong>realpath_cache_size</strong>" to e.g. "<strong>1024</strong>" in php.ini<a href="#phpini">*</a> to improve performance on windows.'
724
            );
725
        }
726
727
        $this->addPhpIniRecommendation('short_open_tag', false);
728
729
        $this->addPhpIniRecommendation('magic_quotes_gpc', false, true);
730
731
        $this->addPhpIniRecommendation('register_globals', false, true);
732
733
        $this->addPhpIniRecommendation('session.auto_start', false);
734
735
        $this->addRecommendation(
736
            class_exists('PDO'),
737
            'PDO should be installed',
738
            'Install <strong>PDO</strong> (mandatory for Doctrine).'
739
        );
740
741
        if (class_exists('PDO')) {
742
            $drivers = PDO::getAvailableDrivers();
743
            $this->addRecommendation(
744
                count($drivers) > 0,
745
                sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'),
746
                'Install <strong>PDO drivers</strong> (mandatory for Doctrine).'
747
            );
748
        }
749
    }
750
751
    /**
752
     * Loads realpath_cache_size from php.ini and converts it to int.
753
     *
754
     * (e.g. 16k is converted to 16384 int)
755
     *
756
     * @return int
757
     */
758
    protected function getRealpathCacheSize()
759
    {
760
        $size = ini_get('realpath_cache_size');
761
        $size = trim($size);
762
        $unit = strtolower(substr($size, -1, 1));
763
        switch ($unit) {
764
            case 'g':
765
                return $size * 1024 * 1024 * 1024;
766
            case 'm':
767
                return $size * 1024 * 1024;
768
            case 'k':
769
                return $size * 1024;
770
            default:
771
                return (int) $size;
772
        }
773
    }
774
}
775