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  }