github.com/symfony-cli/symfony-cli@v0.0.0-20240514161054-ece2df437dfa/commands/data/check-requirements.php (about) 1 <?php 2 3 4 /* 5 * This file is part of the Symfony package. 6 * 7 * (c) Fabien Potencier <fabien@symfony.com> 8 * 9 * For the full copyright and license information, please view the LICENSE 10 * file that was distributed with this source code. 11 */ 12 13 namespace Symfony\Requirements; 14 15 /** 16 * Represents a single PHP requirement, e.g. an installed extension. 17 * It can be a mandatory requirement or an optional recommendation. 18 * There is a special subclass, named PhpConfigRequirement, to check a PHP 19 * configuration option. 20 * 21 * @author Tobias Schultze <http://tobion.de> 22 */ 23 class Requirement 24 { 25 private $fulfilled; 26 private $testMessage; 27 private $helpText; 28 private $helpHtml; 29 private $optional; 30 31 /** 32 * Constructor that initializes the requirement. 33 * 34 * @param bool $fulfilled Whether the requirement is fulfilled 35 * @param string $testMessage The message for testing the requirement 36 * @param string $helpHtml The help text formatted in HTML for resolving the problem 37 * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 38 * @param bool $optional Whether this is only an optional recommendation not a mandatory requirement 39 */ 40 public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false) 41 { 42 $this->fulfilled = (bool) $fulfilled; 43 $this->testMessage = (string) $testMessage; 44 $this->helpHtml = (string) $helpHtml; 45 $this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText; 46 $this->optional = (bool) $optional; 47 } 48 49 /** 50 * Returns whether the requirement is fulfilled. 51 * 52 * @return bool true if fulfilled, otherwise false 53 */ 54 public function isFulfilled() 55 { 56 return $this->fulfilled; 57 } 58 59 /** 60 * Returns the message for testing the requirement. 61 * 62 * @return string The test message 63 */ 64 public function getTestMessage() 65 { 66 return $this->testMessage; 67 } 68 69 /** 70 * Returns the help text for resolving the problem. 71 * 72 * @return string The help text 73 */ 74 public function getHelpText() 75 { 76 return $this->helpText; 77 } 78 79 /** 80 * Returns the help text formatted in HTML. 81 * 82 * @return string The HTML help 83 */ 84 public function getHelpHtml() 85 { 86 return $this->helpHtml; 87 } 88 89 /** 90 * Returns whether this is only an optional recommendation and not a mandatory requirement. 91 * 92 * @return bool true if optional, false if mandatory 93 */ 94 public function isOptional() 95 { 96 return $this->optional; 97 } 98 } 99 100 101 /* 102 * This file is part of the Symfony package. 103 * 104 * (c) Fabien Potencier <fabien@symfony.com> 105 * 106 * For the full copyright and license information, please view the LICENSE 107 * file that was distributed with this source code. 108 */ 109 110 namespace Symfony\Requirements; 111 112 /** 113 * Represents a requirement in form of a PHP configuration option. 114 * 115 * @author Tobias Schultze <http://tobion.de> 116 */ 117 class PhpConfigRequirement extends Requirement 118 { 119 /** 120 * Constructor that initializes the requirement. 121 * 122 * @param string $cfgName The configuration name used for ini_get() 123 * @param bool|callable $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 124 * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 125 * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 126 * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 127 * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 128 * @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 129 * @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) 130 * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 131 * @param bool $optional Whether this is only an optional recommendation not a mandatory requirement 132 */ 133 public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false) 134 { 135 $cfgValue = ini_get($cfgName); 136 137 if (is_callable($evaluation)) { 138 if (null === $testMessage || null === $helpHtml) { 139 throw new \InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.'); 140 } 141 142 $fulfilled = call_user_func($evaluation, $cfgValue); 143 } else { 144 if (null === $testMessage) { 145 $testMessage = sprintf('%s %s be %s in php.ini', 146 $cfgName, 147 $optional ? 'should' : 'must', 148 $evaluation ? 'enabled' : 'disabled' 149 ); 150 } 151 152 if (null === $helpHtml) { 153 $helpHtml = sprintf('Set <strong>%s</strong> to <strong>%s</strong> in php.ini<a href="#phpini">*</a>.', 154 $cfgName, 155 $evaluation ? 'on' : 'off' 156 ); 157 } 158 159 $fulfilled = $evaluation == $cfgValue; 160 } 161 162 parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional); 163 } 164 } 165 166 167 /* 168 * This file is part of the Symfony package. 169 * 170 * (c) Fabien Potencier <fabien@symfony.com> 171 * 172 * For the full copyright and license information, please view the LICENSE 173 * file that was distributed with this source code. 174 */ 175 176 namespace Symfony\Requirements; 177 178 /** 179 * A RequirementCollection represents a set of Requirement instances. 180 * 181 * @author Tobias Schultze <http://tobion.de> 182 */ 183 class RequirementCollection 184 { 185 /** 186 * @var Requirement[] 187 */ 188 private $requirements = array(); 189 190 /** 191 * Adds a Requirement. 192 * 193 * @param Requirement $requirement A Requirement instance 194 */ 195 public function add(Requirement $requirement) 196 { 197 $this->requirements[] = $requirement; 198 } 199 200 /** 201 * Adds a mandatory requirement. 202 * 203 * @param bool $fulfilled Whether the requirement is fulfilled 204 * @param string $testMessage The message for testing the requirement 205 * @param string $helpHtml The help text formatted in HTML for resolving the problem 206 * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 207 */ 208 public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null) 209 { 210 $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false)); 211 } 212 213 /** 214 * Adds an optional recommendation. 215 * 216 * @param bool $fulfilled Whether the recommendation is fulfilled 217 * @param string $testMessage The message for testing the recommendation 218 * @param string $helpHtml The help text formatted in HTML for resolving the problem 219 * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 220 */ 221 public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null) 222 { 223 $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true)); 224 } 225 226 /** 227 * Adds a mandatory requirement in form of a PHP configuration option. 228 * 229 * @param string $cfgName The configuration name used for ini_get() 230 * @param bool|callable $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 231 * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 232 * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 233 * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 234 * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 235 * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 236 * @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) 237 * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 238 */ 239 public function addPhpConfigRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 240 { 241 $this->add(new PhpConfigRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false)); 242 } 243 244 /** 245 * Adds an optional recommendation in form of a PHP configuration option. 246 * 247 * @param string $cfgName The configuration name used for ini_get() 248 * @param bool|callable $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 249 * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 250 * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 251 * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 252 * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 253 * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 254 * @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) 255 * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 256 */ 257 public function addPhpConfigRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 258 { 259 $this->add(new PhpConfigRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true)); 260 } 261 262 /** 263 * Adds a requirement collection to the current set of requirements. 264 * 265 * @param RequirementCollection $collection A RequirementCollection instance 266 */ 267 public function addCollection(RequirementCollection $collection) 268 { 269 $this->requirements = array_merge($this->requirements, $collection->all()); 270 } 271 272 /** 273 * Returns both requirements and recommendations. 274 * 275 * @return Requirement[] 276 */ 277 public function all() 278 { 279 return $this->requirements; 280 } 281 282 /** 283 * Returns all mandatory requirements. 284 * 285 * @return Requirement[] 286 */ 287 public function getRequirements() 288 { 289 $array = array(); 290 foreach ($this->requirements as $req) { 291 if (!$req->isOptional()) { 292 $array[] = $req; 293 } 294 } 295 296 return $array; 297 } 298 299 /** 300 * Returns the mandatory requirements that were not met. 301 * 302 * @return Requirement[] 303 */ 304 public function getFailedRequirements() 305 { 306 $array = array(); 307 foreach ($this->requirements as $req) { 308 if (!$req->isFulfilled() && !$req->isOptional()) { 309 $array[] = $req; 310 } 311 } 312 313 return $array; 314 } 315 316 /** 317 * Returns all optional recommendations. 318 * 319 * @return Requirement[] 320 */ 321 public function getRecommendations() 322 { 323 $array = array(); 324 foreach ($this->requirements as $req) { 325 if ($req->isOptional()) { 326 $array[] = $req; 327 } 328 } 329 330 return $array; 331 } 332 333 /** 334 * Returns the recommendations that were not met. 335 * 336 * @return Requirement[] 337 */ 338 public function getFailedRecommendations() 339 { 340 $array = array(); 341 foreach ($this->requirements as $req) { 342 if (!$req->isFulfilled() && $req->isOptional()) { 343 $array[] = $req; 344 } 345 } 346 347 return $array; 348 } 349 } 350 351 352 /* 353 * This file is part of the Symfony package. 354 * 355 * (c) Fabien Potencier <fabien@symfony.com> 356 * 357 * For the full copyright and license information, please view the LICENSE 358 * file that was distributed with this source code. 359 */ 360 361 namespace Symfony\Requirements; 362 363 /** 364 * This class specifies all requirements and optional recommendations that 365 * are necessary to run Symfony. 366 * 367 * @author Tobias Schultze <http://tobion.de> 368 * @author Fabien Potencier <fabien@symfony.com> 369 */ 370 class ProjectRequirements extends RequirementCollection 371 { 372 const REQUIRED_PHP_VERSION_3x = '5.5.9'; 373 const REQUIRED_PHP_VERSION_4x = '7.1.3'; 374 const REQUIRED_PHP_VERSION_5x = '7.2.9'; 375 376 public function __construct($rootDir) 377 { 378 $installedPhpVersion = phpversion(); 379 $symfonyVersion = null; 380 if (file_exists($kernel = $rootDir.'/vendor/symfony/http-kernel/Kernel.php')) { 381 $contents = file_get_contents($kernel); 382 preg_match('{const VERSION += +\'([^\']+)\'}', $contents, $matches); 383 $symfonyVersion = $matches[1]; 384 } 385 386 $rootDir = $this->getComposerRootDir($rootDir); 387 $options = $this->readComposer($rootDir); 388 389 $phpVersion = self::REQUIRED_PHP_VERSION_3x; 390 if (null !== $symfonyVersion) { 391 if (version_compare($symfonyVersion, '5.0.0', '>=')) { 392 $phpVersion = self::REQUIRED_PHP_VERSION_5x; 393 } elseif (version_compare($symfonyVersion, '4.0.0', '>=')) { 394 $phpVersion = self::REQUIRED_PHP_VERSION_4x; 395 } 396 } 397 398 $this->addRequirement( 399 version_compare($installedPhpVersion, $phpVersion, '>='), 400 sprintf('PHP version must be at least %s (%s installed)', $phpVersion, $installedPhpVersion), 401 sprintf('You are running PHP version "<strong>%s</strong>", but Symfony needs at least PHP "<strong>%s</strong>" to run. 402 Before using Symfony, upgrade your PHP installation, preferably to the latest version.', 403 $installedPhpVersion, $phpVersion), 404 sprintf('Install PHP %s or newer (installed version is %s)', $phpVersion, $installedPhpVersion) 405 ); 406 407 if (version_compare($installedPhpVersion, $phpVersion, '>=')) { 408 $this->addRequirement( 409 in_array(@date_default_timezone_get(), \DateTimeZone::listIdentifiers(), true), 410 sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()), 411 '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>.' 412 ); 413 } 414 415 $this->addRequirement( 416 is_dir($rootDir.'/'.$options['vendor-dir'].'/composer'), 417 'Vendor libraries must be installed', 418 'Vendor libraries are missing. Install composer following instructions from <a href="http://getcomposer.org/">http://getcomposer.org/</a>. '. 419 'Then run "<strong>php composer.phar install</strong>" to install them.' 420 ); 421 422 if (is_dir($cacheDir = $rootDir.'/'.$options['var-dir'].'/cache')) { 423 $this->addRequirement( 424 is_writable($cacheDir), 425 sprintf('%s/cache/ directory must be writable', $options['var-dir']), 426 sprintf('Change the permissions of "<strong>%s/cache/</strong>" directory so that the web server can write into it.', $options['var-dir']) 427 ); 428 } 429 430 if (is_dir($logsDir = $rootDir.'/'.$options['var-dir'].'/log')) { 431 $this->addRequirement( 432 is_writable($logsDir), 433 sprintf('%s/log/ directory must be writable', $options['var-dir']), 434 sprintf('Change the permissions of "<strong>%s/log/</strong>" directory so that the web server can write into it.', $options['var-dir']) 435 ); 436 } 437 438 if (version_compare($installedPhpVersion, $phpVersion, '>=')) { 439 $this->addRequirement( 440 in_array(@date_default_timezone_get(), \DateTimeZone::listIdentifiers(), true), 441 sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()), 442 '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>.' 443 ); 444 } 445 } 446 447 private function getComposerRootDir($rootDir) 448 { 449 $dir = $rootDir; 450 while (!file_exists($dir.'/composer.json')) { 451 if ($dir === dirname($dir)) { 452 return $rootDir; 453 } 454 455 $dir = dirname($dir); 456 } 457 458 return $dir; 459 } 460 461 private function readComposer($rootDir) 462 { 463 $composer = json_decode(file_get_contents($rootDir.'/composer.json'), true); 464 $options = array( 465 'bin-dir' => 'bin', 466 'conf-dir' => 'conf', 467 'etc-dir' => 'etc', 468 'src-dir' => 'src', 469 'var-dir' => 'var', 470 'public-dir' => 'public', 471 'vendor-dir' => 'vendor', 472 ); 473 474 foreach (array_keys($options) as $key) { 475 if (isset($composer['extra'][$key])) { 476 $options[$key] = $composer['extra'][$key]; 477 } elseif (isset($composer['extra']['symfony-'.$key])) { 478 $options[$key] = $composer['extra']['symfony-'.$key]; 479 } elseif (isset($composer['config'][$key])) { 480 $options[$key] = $composer['config'][$key]; 481 } 482 } 483 484 return $options; 485 } 486 } 487 488 489 /* 490 * This file is part of the Symfony package. 491 * 492 * (c) Fabien Potencier <fabien@symfony.com> 493 * 494 * For the full copyright and license information, please view the LICENSE 495 * file that was distributed with this source code. 496 */ 497 498 namespace Symfony\Requirements; 499 500 /** 501 * This class specifies all requirements and optional recommendations that 502 * are necessary to run Symfony. 503 * 504 * @author Tobias Schultze <http://tobion.de> 505 * @author Fabien Potencier <fabien@symfony.com> 506 */ 507 class SymfonyRequirements extends RequirementCollection 508 { 509 public function __construct() 510 { 511 $installedPhpVersion = phpversion(); 512 513 if (version_compare($installedPhpVersion, '7.0.0', '<')) { 514 $this->addPhpConfigRequirement( 515 'date.timezone', true, false, 516 'date.timezone setting must be set', 517 'Set the "<strong>date.timezone</strong>" setting in php.ini<a href="#phpini">*</a> (like Europe/Paris).' 518 ); 519 } 520 521 $this->addRequirement( 522 function_exists('iconv'), 523 'iconv() must be available', 524 'Install and enable the <strong>iconv</strong> extension.' 525 ); 526 527 $this->addRequirement( 528 function_exists('json_encode'), 529 'json_encode() must be available', 530 'Install and enable the <strong>JSON</strong> extension.' 531 ); 532 533 $this->addRequirement( 534 function_exists('session_start'), 535 'session_start() must be available', 536 'Install and enable the <strong>session</strong> extension.' 537 ); 538 539 $this->addRequirement( 540 function_exists('ctype_alpha'), 541 'ctype_alpha() must be available', 542 'Install and enable the <strong>ctype</strong> extension.' 543 ); 544 545 $this->addRequirement( 546 function_exists('token_get_all'), 547 'token_get_all() must be available', 548 'Install and enable the <strong>Tokenizer</strong> extension.' 549 ); 550 551 $this->addRequirement( 552 function_exists('simplexml_import_dom'), 553 'simplexml_import_dom() must be available', 554 'Install and enable the <strong>SimpleXML</strong> extension.' 555 ); 556 557 if (function_exists('apc_store') && ini_get('apc.enabled')) { 558 if (version_compare($installedPhpVersion, '5.4.0', '>=')) { 559 $this->addRequirement( 560 version_compare(phpversion('apc'), '3.1.13', '>='), 561 'APC version must be at least 3.1.13 when using PHP 5.4', 562 'Upgrade your <strong>APC</strong> extension (3.1.13+).' 563 ); 564 } else { 565 $this->addRequirement( 566 version_compare(phpversion('apc'), '3.0.17', '>='), 567 'APC version must be at least 3.0.17', 568 'Upgrade your <strong>APC</strong> extension (3.0.17+).' 569 ); 570 } 571 } 572 573 $this->addPhpConfigRequirement('detect_unicode', false); 574 575 if (extension_loaded('suhosin')) { 576 $this->addPhpConfigRequirement( 577 'suhosin.executor.include.whitelist', 578 function($cfgValue) { return false !== stripos($cfgValue, 'phar'); }, 579 false, 580 'suhosin.executor.include.whitelist must be configured correctly in php.ini', 581 'Add "<strong>phar</strong>" to <strong>suhosin.executor.include.whitelist</strong> in php.ini<a href="#phpini">*</a>.' 582 ); 583 } 584 585 if (extension_loaded('xdebug')) { 586 $this->addPhpConfigRequirement( 587 'xdebug.show_exception_trace', false, true 588 ); 589 590 $this->addPhpConfigRequirement( 591 'xdebug.scream', false, true 592 ); 593 594 $this->addPhpConfigRecommendation( 595 'xdebug.max_nesting_level', 596 function ($cfgValue) { return $cfgValue > 100; }, 597 true, 598 'xdebug.max_nesting_level should be above 100 in php.ini', 599 '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.' 600 ); 601 } 602 603 $pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null; 604 605 $this->addRequirement( 606 null !== $pcreVersion, 607 'PCRE extension must be available', 608 'Install the <strong>PCRE</strong> extension (version 8.0+).' 609 ); 610 611 if (extension_loaded('mbstring')) { 612 $this->addPhpConfigRequirement( 613 'mbstring.func_overload', 614 function ($cfgValue) { return (int) $cfgValue === 0; }, 615 true, 616 'string functions should not be overloaded', 617 '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.' 618 ); 619 } 620 621 /* optional recommendations follow */ 622 623 if (null !== $pcreVersion) { 624 $this->addRecommendation( 625 $pcreVersion >= 8.0, 626 sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion), 627 '<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.' 628 ); 629 } 630 631 $this->addRecommendation( 632 class_exists('DomDocument'), 633 'PHP-DOM and PHP-XML modules should be installed', 634 'Install and enable the <strong>PHP-DOM</strong> and the <strong>PHP-XML</strong> modules.' 635 ); 636 637 $this->addRecommendation( 638 function_exists('mb_strlen'), 639 'mb_strlen() should be available', 640 'Install and enable the <strong>mbstring</strong> extension.' 641 ); 642 643 $this->addRecommendation( 644 function_exists('utf8_decode'), 645 'utf8_decode() should be available', 646 'Install and enable the <strong>XML</strong> extension.' 647 ); 648 649 $this->addRecommendation( 650 function_exists('filter_var'), 651 'filter_var() should be available', 652 'Install and enable the <strong>filter</strong> extension.' 653 ); 654 655 if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 656 $this->addRecommendation( 657 function_exists('posix_isatty'), 658 'posix_isatty() should be available', 659 'Install and enable the <strong>php_posix</strong> extension (used to colorize the CLI output).' 660 ); 661 } 662 663 $this->addRecommendation( 664 extension_loaded('intl'), 665 'intl extension should be available', 666 'Install and enable the <strong>intl</strong> extension (used for validators).' 667 ); 668 669 if (extension_loaded('intl')) { 670 // in some WAMP server installations, new Collator() returns null 671 $this->addRecommendation( 672 null !== new \Collator('fr_FR'), 673 'intl extension should be correctly configured', 674 'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.' 675 ); 676 677 // check for compatible ICU versions (only done when you have the intl extension) 678 if (defined('INTL_ICU_VERSION')) { 679 $version = INTL_ICU_VERSION; 680 } else { 681 $reflector = new \ReflectionExtension('intl'); 682 683 ob_start(); 684 $reflector->info(); 685 $output = strip_tags(ob_get_clean()); 686 687 preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches); 688 $version = $matches[1]; 689 } 690 691 $this->addRecommendation( 692 version_compare($version, '4.0', '>='), 693 'intl ICU version should be at least 4+', 694 'Upgrade your <strong>intl</strong> extension with a newer ICU version (4+).' 695 ); 696 697 if (class_exists('Symfony\Component\Intl\Intl')) { 698 $this->addRecommendation( 699 \Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion(), 700 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()), 701 'To get the latest internationalization data upgrade the ICU system package and the intl PHP extension.' 702 ); 703 if (\Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion()) { 704 $this->addRecommendation( 705 \Symfony\Component\Intl\Intl::getIcuDataVersion() === \Symfony\Component\Intl\Intl::getIcuVersion(), 706 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()), 707 'To avoid internationalization data inconsistencies upgrade the symfony/intl component.' 708 ); 709 } 710 } 711 712 $this->addPhpConfigRecommendation( 713 'intl.error_level', 714 function ($cfgValue) { return (int) $cfgValue === 0; }, 715 true, 716 'intl.error_level should be 0 in php.ini', 717 '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.' 718 ); 719 } 720 721 $accelerator = 722 (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) 723 || 724 (extension_loaded('apc') && ini_get('apc.enabled')) 725 || 726 (extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable')) 727 || 728 (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) 729 || 730 (extension_loaded('xcache') && ini_get('xcache.cacher')) 731 || 732 (extension_loaded('wincache') && ini_get('wincache.ocenabled')) 733 ; 734 735 $this->addRecommendation( 736 $accelerator, 737 'a PHP accelerator should be installed', 738 'Install and/or enable a <strong>PHP accelerator</strong> (highly recommended).' 739 ); 740 741 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 742 $this->addRecommendation( 743 $this->getRealpathCacheSize() >= 5 * 1024 * 1024, 744 'realpath_cache_size should be at least 5M in php.ini', 745 '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.' 746 ); 747 } 748 749 $this->addPhpConfigRecommendation('short_open_tag', false); 750 751 $this->addPhpConfigRecommendation('magic_quotes_gpc', false, true); 752 753 $this->addPhpConfigRecommendation('register_globals', false, true); 754 755 $this->addPhpConfigRecommendation('session.auto_start', false); 756 757 $this->addPhpConfigRecommendation( 758 'xdebug.max_nesting_level', 759 function ($cfgValue) { return $cfgValue > 100; }, 760 true, 761 'xdebug.max_nesting_level should be above 100 in php.ini', 762 '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.' 763 ); 764 765 $this->addPhpConfigRecommendation( 766 'post_max_size', 767 function () { 768 $memoryLimit = $this->getMemoryLimit(); 769 $postMaxSize = $this->getPostMaxSize(); 770 771 return \INF === $memoryLimit || \INF === $postMaxSize || $memoryLimit > $postMaxSize; 772 }, 773 true, 774 '"memory_limit" should be greater than "post_max_size".', 775 'Set "<strong>memory_limit</strong>" to be greater than "<strong>post_max_size</strong>".' 776 ); 777 778 $this->addPhpConfigRecommendation( 779 'upload_max_filesize', 780 function () { 781 $postMaxSize = $this->getPostMaxSize(); 782 $uploadMaxFilesize = $this->getUploadMaxFilesize(); 783 784 return \INF === $postMaxSize || \INF === $uploadMaxFilesize || $postMaxSize > $uploadMaxFilesize; 785 }, 786 true, 787 '"post_max_size" should be greater than "upload_max_filesize".', 788 'Set "<strong>post_max_size</strong>" to be greater than "<strong>upload_max_filesize</strong>".' 789 ); 790 791 $this->addRecommendation( 792 class_exists('PDO'), 793 'PDO should be installed', 794 'Install <strong>PDO</strong> (mandatory for Doctrine).' 795 ); 796 797 if (class_exists('PDO')) { 798 $drivers = \PDO::getAvailableDrivers(); 799 $this->addRecommendation( 800 count($drivers) > 0, 801 sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'), 802 'Install <strong>PDO drivers</strong> (mandatory for Doctrine).' 803 ); 804 } 805 } 806 807 /** 808 * Convert a given shorthand size in an integer 809 * (e.g. 16k is converted to 16384 int) 810 * 811 * @param string $size Shorthand size 812 * @param string $infiniteValue The infinite value for this setting 813 * 814 * @see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes 815 * 816 * @return float Converted size 817 */ 818 private function convertShorthandSize($size, $infiniteValue = '-1') 819 { 820 // Initialize 821 $size = trim($size); 822 $unit = ''; 823 824 // Check unlimited alias 825 if ($size === $infiniteValue) { 826 return \INF; 827 } 828 829 // Check size 830 if (!ctype_digit($size)) { 831 $unit = strtolower(substr($size, -1, 1)); 832 $size = (int) substr($size, 0, -1); 833 } 834 835 // Return converted size 836 switch ($unit) { 837 case 'g': 838 return $size * 1024 * 1024 * 1024; 839 case 'm': 840 return $size * 1024 * 1024; 841 case 'k': 842 return $size * 1024; 843 default: 844 return (int) $size; 845 } 846 } 847 848 /** 849 * Loads realpath_cache_size from php.ini and converts it to int. 850 * 851 * (e.g. 16k is converted to 16384 int) 852 * 853 * @return float 854 */ 855 private function getRealpathCacheSize() 856 { 857 return $this->convertShorthandSize(ini_get('realpath_cache_size')); 858 } 859 860 /** 861 * Loads post_max_size from php.ini and converts it to int. 862 * 863 * @return float 864 */ 865 private function getPostMaxSize() 866 { 867 return $this->convertShorthandSize(ini_get('post_max_size'), '0'); 868 } 869 870 /** 871 * Loads memory_limit from php.ini and converts it to int. 872 * 873 * @return float 874 */ 875 private function getMemoryLimit() 876 { 877 return $this->convertShorthandSize(ini_get('memory_limit')); 878 } 879 880 /** 881 * Loads upload_max_filesize from php.ini and converts it to int. 882 * 883 * @return float 884 */ 885 private function getUploadMaxFilesize() 886 { 887 return $this->convertShorthandSize(ini_get('upload_max_filesize'), '0'); 888 } 889 } 890 891 892 /* 893 * This file is part of the Symfony package. 894 * 895 * (c) Fabien Potencier <fabien@symfony.com> 896 * 897 * For the full copyright and license information, please view the LICENSE 898 * file that was distributed with this source code. 899 */ 900 901 use Symfony\Requirements\Requirement; 902 use Symfony\Requirements\SymfonyRequirements; 903 use Symfony\Requirements\ProjectRequirements; 904 905 if (file_exists($autoloader = __DIR__.'/../../../autoload.php')) { 906 require_once $autoloader; 907 } elseif (file_exists($autoloader = __DIR__.'/../vendor/autoload.php')) { 908 require_once $autoloader; 909 } elseif (!class_exists('Symfony\Requirements\Requirement', false)) { 910 require_once dirname(__DIR__).'/src/Requirement.php'; 911 require_once dirname(__DIR__).'/src/RequirementCollection.php'; 912 require_once dirname(__DIR__).'/src/PhpConfigRequirement.php'; 913 require_once dirname(__DIR__).'/src/SymfonyRequirements.php'; 914 require_once dirname(__DIR__).'/src/ProjectRequirements.php'; 915 } 916 917 $lineSize = 70; 918 $args = array(); 919 $isVerbose = false; 920 foreach ($argv as $arg) { 921 if ('-v' === $arg || '-vv' === $arg || '-vvv' === $arg) { 922 $isVerbose = true; 923 } else { 924 $args[] = $arg; 925 } 926 } 927 928 $symfonyRequirements = new SymfonyRequirements(); 929 $requirements = $symfonyRequirements->getRequirements(); 930 931 // specific directory to check? 932 $dir = isset($args[1]) ? $args[1] : (file_exists(getcwd().'/composer.json') ? getcwd().'/composer.json' : null); 933 if (null !== $dir) { 934 $projectRequirements = new ProjectRequirements($dir); 935 $requirements = array_merge($requirements, $projectRequirements->getRequirements()); 936 } 937 938 echo_title('Symfony Requirements Checker'); 939 940 echo '> PHP is using the following php.ini file:'.PHP_EOL; 941 if ($iniPath = get_cfg_var('cfg_file_path')) { 942 echo_style('green', $iniPath); 943 } else { 944 echo_style('yellow', 'WARNING: No configuration file (php.ini) used by PHP!'); 945 } 946 947 echo PHP_EOL.PHP_EOL; 948 949 echo '> Checking Symfony requirements:'.PHP_EOL.PHP_EOL; 950 951 $messages = array(); 952 foreach ($requirements as $req) { 953 if ($helpText = get_error_message($req, $lineSize)) { 954 if ($isVerbose) { 955 echo_style('red', '[ERROR] '); 956 echo $req->getTestMessage().PHP_EOL; 957 } else { 958 echo_style('red', 'E'); 959 } 960 961 $messages['error'][] = $helpText; 962 } else { 963 if ($isVerbose) { 964 echo_style('green', '[OK] '); 965 echo $req->getTestMessage().PHP_EOL; 966 } else { 967 echo_style('green', '.'); 968 } 969 } 970 } 971 972 $checkPassed = empty($messages['error']); 973 974 foreach ($symfonyRequirements->getRecommendations() as $req) { 975 if ($helpText = get_error_message($req, $lineSize)) { 976 if ($isVerbose) { 977 echo_style('yellow', '[WARN] '); 978 echo $req->getTestMessage().PHP_EOL; 979 } else { 980 echo_style('yellow', 'W'); 981 } 982 983 $messages['warning'][] = $helpText; 984 } else { 985 if ($isVerbose) { 986 echo_style('green', '[OK] '); 987 echo $req->getTestMessage().PHP_EOL; 988 } else { 989 echo_style('green', '.'); 990 } 991 } 992 } 993 994 if ($checkPassed) { 995 echo_block('success', 'OK', 'Your system is ready to run Symfony projects'); 996 } else { 997 echo_block('error', 'ERROR', 'Your system is not ready to run Symfony projects'); 998 999 echo_title('Fix the following mandatory requirements', 'red'); 1000 1001 foreach ($messages['error'] as $helpText) { 1002 echo ' * '.$helpText.PHP_EOL; 1003 } 1004 } 1005 1006 if (!empty($messages['warning'])) { 1007 echo_title('Optional recommendations to improve your setup', 'yellow'); 1008 1009 foreach ($messages['warning'] as $helpText) { 1010 echo ' * '.$helpText.PHP_EOL; 1011 } 1012 } 1013 1014 echo PHP_EOL; 1015 echo_style('title', 'Note'); 1016 echo ' The command console can use a different php.ini file'.PHP_EOL; 1017 echo_style('title', '~~~~'); 1018 echo ' than the one used by your web server.'.PHP_EOL; 1019 echo ' Please check that both the console and the web server'.PHP_EOL; 1020 echo ' are using the same PHP version and configuration.'.PHP_EOL; 1021 echo PHP_EOL; 1022 1023 exit($checkPassed ? 0 : 1); 1024 1025 function get_error_message(Requirement $requirement, $lineSize) 1026 { 1027 if ($requirement->isFulfilled()) { 1028 return; 1029 } 1030 1031 $errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL; 1032 $errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL; 1033 1034 return $errorMessage; 1035 } 1036 1037 function echo_title($title, $style = null) 1038 { 1039 $style = $style ?: 'title'; 1040 1041 echo PHP_EOL; 1042 echo_style($style, $title.PHP_EOL); 1043 echo_style($style, str_repeat('~', strlen($title)).PHP_EOL); 1044 echo PHP_EOL; 1045 } 1046 1047 function echo_style($style, $message) 1048 { 1049 // ANSI color codes 1050 $styles = array( 1051 'reset' => "\033[0m", 1052 'red' => "\033[31m", 1053 'green' => "\033[32m", 1054 'yellow' => "\033[33m", 1055 'error' => "\033[37;41m", 1056 'success' => "\033[37;42m", 1057 'title' => "\033[34m", 1058 ); 1059 $supports = has_color_support(); 1060 1061 echo($supports ? $styles[$style] : '').$message.($supports ? $styles['reset'] : ''); 1062 } 1063 1064 function echo_block($style, $title, $message) 1065 { 1066 $message = ' '.trim($message).' '; 1067 $width = strlen($message); 1068 1069 echo PHP_EOL.PHP_EOL; 1070 1071 echo_style($style, str_repeat(' ', $width)); 1072 echo PHP_EOL; 1073 echo_style($style, str_pad(' ['.$title.']', $width, ' ', STR_PAD_RIGHT)); 1074 echo PHP_EOL; 1075 echo_style($style, $message); 1076 echo PHP_EOL; 1077 echo_style($style, str_repeat(' ', $width)); 1078 echo PHP_EOL; 1079 } 1080 1081 function has_color_support() 1082 { 1083 static $support; 1084 1085 if (null === $support) { 1086 1087 if ('Hyper' === getenv('TERM_PROGRAM')) { 1088 return $support = true; 1089 } 1090 1091 if (DIRECTORY_SEPARATOR === '\\') { 1092 return $support = (function_exists('sapi_windows_vt100_support') 1093 && @sapi_windows_vt100_support(STDOUT)) 1094 || false !== getenv('ANSICON') 1095 || 'ON' === getenv('ConEmuANSI') 1096 || 'xterm' === getenv('TERM'); 1097 } 1098 1099 if (function_exists('stream_isatty')) { 1100 return $support = @stream_isatty(STDOUT); 1101 } 1102 1103 if (function_exists('posix_isatty')) { 1104 return $support = @posix_isatty(STDOUT); 1105 } 1106 1107 $stat = @fstat(STDOUT); 1108 // Check if formatted mode is S_IFCHR 1109 return $support = ( $stat ? 0020000 === ($stat['mode'] & 0170000) : false ); 1110 } 1111 1112 return $support; 1113 }