├── .gitignore ├── bin ├── requirements-checker ├── release.php └── requirements-checker.php ├── README.rst ├── composer.json ├── LICENSE └── src ├── Requirement.php ├── PhpConfigRequirement.php ├── ProjectRequirements.php ├── RequirementCollection.php └── SymfonyRequirements.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /bin/requirements-checker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =5.3.9" 15 | }, 16 | "bin": [ 17 | "bin/requirements-checker" 18 | ], 19 | "autoload": { 20 | "psr-4": { "Symfony\\Requirements\\": "src/" } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Requirement.php: -------------------------------------------------------------------------------- 1 | 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 | namespace Symfony\Requirements; 13 | 14 | /** 15 | * Represents a single PHP requirement, e.g. an installed extension. 16 | * It can be a mandatory requirement or an optional recommendation. 17 | * There is a special subclass, named PhpConfigRequirement, to check a PHP 18 | * configuration option. 19 | * 20 | * @author Tobias Schultze 21 | */ 22 | class Requirement 23 | { 24 | private $fulfilled; 25 | private $testMessage; 26 | private $helpText; 27 | private $helpHtml; 28 | private $optional; 29 | 30 | /** 31 | * Constructor that initializes the requirement. 32 | * 33 | * @param bool $fulfilled Whether the requirement is fulfilled 34 | * @param string $testMessage The message for testing the requirement 35 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 36 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 37 | * @param bool $optional Whether this is only an optional recommendation not a mandatory requirement 38 | */ 39 | public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false) 40 | { 41 | $this->fulfilled = (bool) $fulfilled; 42 | $this->testMessage = (string) $testMessage; 43 | $this->helpHtml = (string) $helpHtml; 44 | $this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText; 45 | $this->optional = (bool) $optional; 46 | } 47 | 48 | /** 49 | * Returns whether the requirement is fulfilled. 50 | * 51 | * @return bool true if fulfilled, otherwise false 52 | */ 53 | public function isFulfilled() 54 | { 55 | return $this->fulfilled; 56 | } 57 | 58 | /** 59 | * Returns the message for testing the requirement. 60 | * 61 | * @return string The test message 62 | */ 63 | public function getTestMessage() 64 | { 65 | return $this->testMessage; 66 | } 67 | 68 | /** 69 | * Returns the help text for resolving the problem. 70 | * 71 | * @return string The help text 72 | */ 73 | public function getHelpText() 74 | { 75 | return $this->helpText; 76 | } 77 | 78 | /** 79 | * Returns the help text formatted in HTML. 80 | * 81 | * @return string The HTML help 82 | */ 83 | public function getHelpHtml() 84 | { 85 | return $this->helpHtml; 86 | } 87 | 88 | /** 89 | * Returns whether this is only an optional recommendation and not a mandatory requirement. 90 | * 91 | * @return bool true if optional, false if mandatory 92 | */ 93 | public function isOptional() 94 | { 95 | return $this->optional; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/PhpConfigRequirement.php: -------------------------------------------------------------------------------- 1 | 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 | namespace Symfony\Requirements; 13 | 14 | /** 15 | * Represents a requirement in form of a PHP configuration option. 16 | * 17 | * @author Tobias Schultze 18 | */ 19 | class PhpConfigRequirement extends Requirement 20 | { 21 | /** 22 | * Constructor that initializes the requirement. 23 | * 24 | * @param string $cfgName The configuration name used for ini_get() 25 | * @param bool|callable $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 26 | * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 27 | * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 28 | * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 29 | * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 30 | * @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 31 | * @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) 32 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 33 | * @param bool $optional Whether this is only an optional recommendation not a mandatory requirement 34 | */ 35 | public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false) 36 | { 37 | $cfgValue = ini_get($cfgName); 38 | 39 | if (is_callable($evaluation)) { 40 | if (null === $testMessage || null === $helpHtml) { 41 | throw new \InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.'); 42 | } 43 | 44 | $fulfilled = call_user_func($evaluation, $cfgValue); 45 | } else { 46 | if (null === $testMessage) { 47 | $testMessage = sprintf('%s %s be %s in php.ini', 48 | $cfgName, 49 | $optional ? 'should' : 'must', 50 | $evaluation ? 'enabled' : 'disabled' 51 | ); 52 | } 53 | 54 | if (null === $helpHtml) { 55 | $helpHtml = sprintf('Set %s to %s in php.ini*.', 56 | $cfgName, 57 | $evaluation ? 'on' : 'off' 58 | ); 59 | } 60 | 61 | $fulfilled = $evaluation == $cfgValue; 62 | } 63 | 64 | parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ProjectRequirements.php: -------------------------------------------------------------------------------- 1 | 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 | namespace Symfony\Requirements; 13 | 14 | /** 15 | * This class specifies all requirements and optional recommendations that 16 | * are necessary to run Symfony. 17 | * 18 | * @author Tobias Schultze 19 | * @author Fabien Potencier 20 | */ 21 | class ProjectRequirements extends RequirementCollection 22 | { 23 | const REQUIRED_PHP_VERSION_3x = '5.5.9'; 24 | const REQUIRED_PHP_VERSION_4x = '7.1.3'; 25 | const REQUIRED_PHP_VERSION_5x = '7.2.9'; 26 | const REQUIRED_PHP_VERSION_6x = '8.1.0'; 27 | const REQUIRED_PHP_VERSION_7x = '8.2.0'; 28 | 29 | public function __construct($rootDir) 30 | { 31 | $installedPhpVersion = phpversion(); 32 | $symfonyVersion = null; 33 | if (file_exists($kernel = $rootDir.'/vendor/symfony/http-kernel/Kernel.php')) { 34 | $contents = file_get_contents($kernel); 35 | preg_match('{const VERSION += +\'([^\']+)\'}', $contents, $matches); 36 | $symfonyVersion = $matches[1]; 37 | } 38 | 39 | $rootDir = $this->getComposerRootDir($rootDir); 40 | $options = $this->readComposer($rootDir); 41 | 42 | $phpVersion = self::REQUIRED_PHP_VERSION_7x; 43 | if (null !== $symfonyVersion) { 44 | if (version_compare($symfonyVersion, '6.0.0', '>=')) { 45 | $phpVersion = self::REQUIRED_PHP_VERSION_6x; 46 | } elseif (version_compare($symfonyVersion, '5.0.0', '>=')) { 47 | $phpVersion = self::REQUIRED_PHP_VERSION_5x; 48 | } elseif (version_compare($symfonyVersion, '4.0.0', '>=')) { 49 | $phpVersion = self::REQUIRED_PHP_VERSION_4x; 50 | } elseif (version_compare($symfonyVersion, '3.0.0', '>=')) { 51 | $phpVersion = self::REQUIRED_PHP_VERSION_3x; 52 | } 53 | } 54 | 55 | $this->addRequirement( 56 | version_compare($installedPhpVersion, $phpVersion, '>='), 57 | sprintf('PHP version must be at least %s (%s installed)', $phpVersion, $installedPhpVersion), 58 | sprintf('You are running PHP version "%s", but Symfony needs at least PHP "%s" to run. 59 | Before using Symfony, upgrade your PHP installation, preferably to the latest version.', 60 | $installedPhpVersion, $phpVersion), 61 | sprintf('Install PHP %s or newer (installed version is %s)', $phpVersion, $installedPhpVersion) 62 | ); 63 | 64 | if (version_compare($installedPhpVersion, $phpVersion, '>=')) { 65 | $this->addRequirement( 66 | in_array(@date_default_timezone_get(), \DateTimeZone::listIdentifiers(), true), 67 | sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()), 68 | 'Your default timezone is not supported by PHP. Check for typos in your php.ini file and have a look at the list of deprecated timezones at http://php.net/manual/en/timezones.others.php.' 69 | ); 70 | } 71 | 72 | $this->addRequirement( 73 | is_dir($rootDir.'/'.$options['vendor-dir'].'/composer'), 74 | 'Vendor libraries must be installed', 75 | 'Vendor libraries are missing. Install composer following instructions from http://getcomposer.org/. '. 76 | 'Then run "php composer.phar install" to install them.' 77 | ); 78 | 79 | if (is_dir($cacheDir = $rootDir.'/'.$options['var-dir'].'/cache')) { 80 | $this->addRequirement( 81 | is_writable($cacheDir), 82 | sprintf('%s/cache/ directory must be writable', $options['var-dir']), 83 | sprintf('Change the permissions of "%s/cache/" directory so that the web server can write into it.', $options['var-dir']) 84 | ); 85 | } 86 | 87 | if (is_dir($logsDir = $rootDir.'/'.$options['var-dir'].'/log')) { 88 | $this->addRequirement( 89 | is_writable($logsDir), 90 | sprintf('%s/log/ directory must be writable', $options['var-dir']), 91 | sprintf('Change the permissions of "%s/log/" directory so that the web server can write into it.', $options['var-dir']) 92 | ); 93 | } 94 | 95 | if (version_compare($installedPhpVersion, $phpVersion, '>=')) { 96 | $this->addRequirement( 97 | in_array(@date_default_timezone_get(), \DateTimeZone::listIdentifiers(), true), 98 | sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()), 99 | 'Your default timezone is not supported by PHP. Check for typos in your php.ini file and have a look at the list of deprecated timezones at http://php.net/manual/en/timezones.others.php.' 100 | ); 101 | } 102 | } 103 | 104 | private function getComposerRootDir($rootDir) 105 | { 106 | $dir = $rootDir; 107 | while (!file_exists($dir.'/composer.json')) { 108 | if ($dir === dirname($dir)) { 109 | return $rootDir; 110 | } 111 | 112 | $dir = dirname($dir); 113 | } 114 | 115 | return $dir; 116 | } 117 | 118 | private function readComposer($rootDir) 119 | { 120 | $composer = json_decode(file_get_contents($rootDir.'/composer.json'), true); 121 | $options = array( 122 | 'bin-dir' => 'bin', 123 | 'conf-dir' => 'conf', 124 | 'etc-dir' => 'etc', 125 | 'src-dir' => 'src', 126 | 'var-dir' => 'var', 127 | 'public-dir' => 'public', 128 | 'vendor-dir' => 'vendor', 129 | ); 130 | 131 | foreach (array_keys($options) as $key) { 132 | if (isset($composer['extra'][$key])) { 133 | $options[$key] = $composer['extra'][$key]; 134 | } elseif (isset($composer['extra']['symfony-'.$key])) { 135 | $options[$key] = $composer['extra']['symfony-'.$key]; 136 | } elseif (isset($composer['config'][$key])) { 137 | $options[$key] = $composer['config'][$key]; 138 | } 139 | } 140 | 141 | return $options; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /bin/requirements-checker.php: -------------------------------------------------------------------------------- 1 | 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 | use Symfony\Requirements\Requirement; 13 | use Symfony\Requirements\SymfonyRequirements; 14 | use Symfony\Requirements\ProjectRequirements; 15 | 16 | if (file_exists($autoloader = __DIR__.'/../../../autoload.php')) { 17 | require_once $autoloader; 18 | } elseif (file_exists($autoloader = __DIR__.'/../vendor/autoload.php')) { 19 | require_once $autoloader; 20 | } elseif (!class_exists('Symfony\Requirements\Requirement', false)) { 21 | require_once dirname(__DIR__).'/src/Requirement.php'; 22 | require_once dirname(__DIR__).'/src/RequirementCollection.php'; 23 | require_once dirname(__DIR__).'/src/PhpConfigRequirement.php'; 24 | require_once dirname(__DIR__).'/src/SymfonyRequirements.php'; 25 | require_once dirname(__DIR__).'/src/ProjectRequirements.php'; 26 | } 27 | 28 | $lineSize = 70; 29 | $args = array(); 30 | $isVerbose = false; 31 | foreach ($argv as $arg) { 32 | if ('-v' === $arg || '-vv' === $arg || '-vvv' === $arg) { 33 | $isVerbose = true; 34 | } else { 35 | $args[] = $arg; 36 | } 37 | } 38 | 39 | $symfonyRequirements = new SymfonyRequirements(); 40 | $requirements = $symfonyRequirements->getRequirements(); 41 | 42 | // specific directory to check? 43 | $dir = isset($args[1]) ? $args[1] : (file_exists(getcwd().'/composer.json') ? getcwd() : null); 44 | if (null !== $dir) { 45 | $projectRequirements = new ProjectRequirements($dir); 46 | $requirements = array_merge($requirements, $projectRequirements->getRequirements()); 47 | } 48 | 49 | echo_title('Symfony Requirements Checker'); 50 | 51 | echo '> PHP is using the following php.ini file:'.PHP_EOL; 52 | if ($iniPath = get_cfg_var('cfg_file_path')) { 53 | echo_style('green', $iniPath); 54 | } else { 55 | echo_style('yellow', 'WARNING: No configuration file (php.ini) used by PHP!'); 56 | } 57 | 58 | echo PHP_EOL.PHP_EOL; 59 | 60 | echo '> Checking Symfony requirements:'.PHP_EOL.PHP_EOL; 61 | 62 | $messages = array(); 63 | foreach ($requirements as $req) { 64 | if ($helpText = get_error_message($req, $lineSize)) { 65 | if ($isVerbose) { 66 | echo_style('red', '[ERROR] '); 67 | echo $req->getTestMessage().PHP_EOL; 68 | } else { 69 | echo_style('red', 'E'); 70 | } 71 | 72 | $messages['error'][] = $helpText; 73 | } else { 74 | if ($isVerbose) { 75 | echo_style('green', '[OK] '); 76 | echo $req->getTestMessage().PHP_EOL; 77 | } else { 78 | echo_style('green', '.'); 79 | } 80 | } 81 | } 82 | 83 | $checkPassed = empty($messages['error']); 84 | 85 | foreach ($symfonyRequirements->getRecommendations() as $req) { 86 | if ($helpText = get_error_message($req, $lineSize)) { 87 | if ($isVerbose) { 88 | echo_style('yellow', '[WARN] '); 89 | echo $req->getTestMessage().PHP_EOL; 90 | } else { 91 | echo_style('yellow', 'W'); 92 | } 93 | 94 | $messages['warning'][] = $helpText; 95 | } else { 96 | if ($isVerbose) { 97 | echo_style('green', '[OK] '); 98 | echo $req->getTestMessage().PHP_EOL; 99 | } else { 100 | echo_style('green', '.'); 101 | } 102 | } 103 | } 104 | 105 | if ($checkPassed) { 106 | echo_block('success', 'OK', 'Your system is ready to run Symfony projects'); 107 | } else { 108 | echo_block('error', 'ERROR', 'Your system is not ready to run Symfony projects'); 109 | 110 | echo_title('Fix the following mandatory requirements', 'red'); 111 | 112 | foreach ($messages['error'] as $helpText) { 113 | echo ' * '.$helpText.PHP_EOL; 114 | } 115 | } 116 | 117 | if (!empty($messages['warning'])) { 118 | echo_title('Optional recommendations to improve your setup', 'yellow'); 119 | 120 | foreach ($messages['warning'] as $helpText) { 121 | echo ' * '.$helpText.PHP_EOL; 122 | } 123 | } 124 | 125 | echo PHP_EOL; 126 | echo_style('title', 'Note'); 127 | echo ' The command console can use a different php.ini file'.PHP_EOL; 128 | echo_style('title', '~~~~'); 129 | echo ' than the one used by your web server.'.PHP_EOL; 130 | echo ' Please check that both the console and the web server'.PHP_EOL; 131 | echo ' are using the same PHP version and configuration.'.PHP_EOL; 132 | echo PHP_EOL; 133 | 134 | exit($checkPassed ? 0 : 1); 135 | 136 | function get_error_message(Requirement $requirement, $lineSize) 137 | { 138 | if ($requirement->isFulfilled()) { 139 | return; 140 | } 141 | 142 | $errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL; 143 | $errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL; 144 | 145 | return $errorMessage; 146 | } 147 | 148 | function echo_title($title, $style = null) 149 | { 150 | $style = $style ?: 'title'; 151 | 152 | echo PHP_EOL; 153 | echo_style($style, $title.PHP_EOL); 154 | echo_style($style, str_repeat('~', strlen($title)).PHP_EOL); 155 | echo PHP_EOL; 156 | } 157 | 158 | function echo_style($style, $message) 159 | { 160 | // ANSI color codes 161 | $styles = array( 162 | 'reset' => "\033[0m", 163 | 'red' => "\033[31m", 164 | 'green' => "\033[32m", 165 | 'yellow' => "\033[33m", 166 | 'error' => "\033[37;41m", 167 | 'success' => "\033[37;42m", 168 | 'title' => "\033[34m", 169 | ); 170 | $supports = has_color_support(); 171 | 172 | echo($supports ? $styles[$style] : '').$message.($supports ? $styles['reset'] : ''); 173 | } 174 | 175 | function echo_block($style, $title, $message) 176 | { 177 | $message = ' '.trim($message).' '; 178 | $width = strlen($message); 179 | 180 | echo PHP_EOL.PHP_EOL; 181 | 182 | echo_style($style, str_repeat(' ', $width)); 183 | echo PHP_EOL; 184 | echo_style($style, str_pad(' ['.$title.']', $width, ' ', STR_PAD_RIGHT)); 185 | echo PHP_EOL; 186 | echo_style($style, $message); 187 | echo PHP_EOL; 188 | echo_style($style, str_repeat(' ', $width)); 189 | echo PHP_EOL; 190 | } 191 | 192 | function has_color_support() 193 | { 194 | static $support; 195 | 196 | if (null === $support) { 197 | 198 | if ('Hyper' === getenv('TERM_PROGRAM')) { 199 | return $support = true; 200 | } 201 | 202 | if (DIRECTORY_SEPARATOR === '\\') { 203 | return $support = (function_exists('sapi_windows_vt100_support') 204 | && @sapi_windows_vt100_support(STDOUT)) 205 | || false !== getenv('ANSICON') 206 | || 'ON' === getenv('ConEmuANSI') 207 | || 'xterm' === getenv('TERM'); 208 | } 209 | 210 | if (function_exists('stream_isatty')) { 211 | return $support = @stream_isatty(STDOUT); 212 | } 213 | 214 | if (function_exists('posix_isatty')) { 215 | return $support = @posix_isatty(STDOUT); 216 | } 217 | 218 | $stat = @fstat(STDOUT); 219 | // Check if formatted mode is S_IFCHR 220 | return $support = ( $stat ? 0020000 === ($stat['mode'] & 0170000) : false ); 221 | } 222 | 223 | return $support; 224 | } 225 | -------------------------------------------------------------------------------- /src/RequirementCollection.php: -------------------------------------------------------------------------------- 1 | 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 | namespace Symfony\Requirements; 13 | 14 | /** 15 | * A RequirementCollection represents a set of Requirement instances. 16 | * 17 | * @author Tobias Schultze 18 | */ 19 | class RequirementCollection 20 | { 21 | /** 22 | * @var Requirement[] 23 | */ 24 | private $requirements = array(); 25 | 26 | /** 27 | * Adds a Requirement. 28 | * 29 | * @param Requirement $requirement A Requirement instance 30 | */ 31 | public function add(Requirement $requirement) 32 | { 33 | $this->requirements[] = $requirement; 34 | } 35 | 36 | /** 37 | * Adds a mandatory requirement. 38 | * 39 | * @param bool $fulfilled Whether the requirement is fulfilled 40 | * @param string $testMessage The message for testing the requirement 41 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 42 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 43 | */ 44 | public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null) 45 | { 46 | $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false)); 47 | } 48 | 49 | /** 50 | * Adds an optional recommendation. 51 | * 52 | * @param bool $fulfilled Whether the recommendation is fulfilled 53 | * @param string $testMessage The message for testing the recommendation 54 | * @param string $helpHtml The help text formatted in HTML for resolving the problem 55 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 56 | */ 57 | public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null) 58 | { 59 | $this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true)); 60 | } 61 | 62 | /** 63 | * Adds a mandatory requirement in form of a PHP configuration option. 64 | * 65 | * @param string $cfgName The configuration name used for ini_get() 66 | * @param bool|callable $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 67 | * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 68 | * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 69 | * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 70 | * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 71 | * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 72 | * @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) 73 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 74 | */ 75 | public function addPhpConfigRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 76 | { 77 | $this->add(new PhpConfigRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false)); 78 | } 79 | 80 | /** 81 | * Adds an optional recommendation in form of a PHP configuration option. 82 | * 83 | * @param string $cfgName The configuration name used for ini_get() 84 | * @param bool|callable $evaluation Either a boolean indicating whether the configuration should evaluate to true or false, 85 | * or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement 86 | * @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false. 87 | * This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin. 88 | * Example: You require a config to be true but PHP later removes this config and defaults it to true internally. 89 | * @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived) 90 | * @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) 91 | * @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags) 92 | */ 93 | public function addPhpConfigRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null) 94 | { 95 | $this->add(new PhpConfigRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true)); 96 | } 97 | 98 | /** 99 | * Adds a requirement collection to the current set of requirements. 100 | * 101 | * @param RequirementCollection $collection A RequirementCollection instance 102 | */ 103 | public function addCollection(RequirementCollection $collection) 104 | { 105 | $this->requirements = array_merge($this->requirements, $collection->all()); 106 | } 107 | 108 | /** 109 | * Returns both requirements and recommendations. 110 | * 111 | * @return Requirement[] 112 | */ 113 | public function all() 114 | { 115 | return $this->requirements; 116 | } 117 | 118 | /** 119 | * Returns all mandatory requirements. 120 | * 121 | * @return Requirement[] 122 | */ 123 | public function getRequirements() 124 | { 125 | $array = array(); 126 | foreach ($this->requirements as $req) { 127 | if (!$req->isOptional()) { 128 | $array[] = $req; 129 | } 130 | } 131 | 132 | return $array; 133 | } 134 | 135 | /** 136 | * Returns the mandatory requirements that were not met. 137 | * 138 | * @return Requirement[] 139 | */ 140 | public function getFailedRequirements() 141 | { 142 | $array = array(); 143 | foreach ($this->requirements as $req) { 144 | if (!$req->isFulfilled() && !$req->isOptional()) { 145 | $array[] = $req; 146 | } 147 | } 148 | 149 | return $array; 150 | } 151 | 152 | /** 153 | * Returns all optional recommendations. 154 | * 155 | * @return Requirement[] 156 | */ 157 | public function getRecommendations() 158 | { 159 | $array = array(); 160 | foreach ($this->requirements as $req) { 161 | if ($req->isOptional()) { 162 | $array[] = $req; 163 | } 164 | } 165 | 166 | return $array; 167 | } 168 | 169 | /** 170 | * Returns the recommendations that were not met. 171 | * 172 | * @return Requirement[] 173 | */ 174 | public function getFailedRecommendations() 175 | { 176 | $array = array(); 177 | foreach ($this->requirements as $req) { 178 | if (!$req->isFulfilled() && $req->isOptional()) { 179 | $array[] = $req; 180 | } 181 | } 182 | 183 | return $array; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/SymfonyRequirements.php: -------------------------------------------------------------------------------- 1 | 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 | namespace Symfony\Requirements; 13 | 14 | /** 15 | * This class specifies all requirements and optional recommendations that 16 | * are necessary to run Symfony. 17 | * 18 | * @author Tobias Schultze 19 | * @author Fabien Potencier 20 | */ 21 | class SymfonyRequirements extends RequirementCollection 22 | { 23 | public function __construct() 24 | { 25 | $installedPhpVersion = phpversion(); 26 | 27 | if (version_compare($installedPhpVersion, '7.0.0', '<')) { 28 | $this->addPhpConfigRequirement( 29 | 'date.timezone', true, false, 30 | 'date.timezone setting must be set', 31 | 'Set the "date.timezone" setting in php.ini* (like Europe/Paris).' 32 | ); 33 | } 34 | 35 | $this->addRequirement( 36 | function_exists('iconv'), 37 | 'iconv() must be available', 38 | 'Install and enable the iconv extension.' 39 | ); 40 | 41 | $this->addRequirement( 42 | function_exists('json_encode'), 43 | 'json_encode() must be available', 44 | 'Install and enable the JSON extension.' 45 | ); 46 | 47 | $this->addRequirement( 48 | function_exists('session_start'), 49 | 'session_start() must be available', 50 | 'Install and enable the session extension.' 51 | ); 52 | 53 | $this->addRequirement( 54 | function_exists('ctype_alpha'), 55 | 'ctype_alpha() must be available', 56 | 'Install and enable the ctype extension.' 57 | ); 58 | 59 | $this->addRequirement( 60 | function_exists('token_get_all'), 61 | 'token_get_all() must be available', 62 | 'Install and enable the Tokenizer extension.' 63 | ); 64 | 65 | $this->addRequirement( 66 | function_exists('simplexml_import_dom'), 67 | 'simplexml_import_dom() must be available', 68 | 'Install and enable the SimpleXML extension.' 69 | ); 70 | 71 | if (function_exists('apc_store') && ini_get('apc.enabled')) { 72 | if (version_compare($installedPhpVersion, '5.4.0', '>=')) { 73 | $this->addRequirement( 74 | version_compare(phpversion('apc'), '3.1.13', '>='), 75 | 'APC version must be at least 3.1.13 when using PHP 5.4', 76 | 'Upgrade your APC extension (3.1.13+).' 77 | ); 78 | } else { 79 | $this->addRequirement( 80 | version_compare(phpversion('apc'), '3.0.17', '>='), 81 | 'APC version must be at least 3.0.17', 82 | 'Upgrade your APC extension (3.0.17+).' 83 | ); 84 | } 85 | } 86 | 87 | $this->addPhpConfigRequirement('detect_unicode', false); 88 | 89 | if (extension_loaded('suhosin')) { 90 | $this->addPhpConfigRequirement( 91 | 'suhosin.executor.include.whitelist', 92 | function($cfgValue) { return false !== stripos($cfgValue, 'phar'); }, 93 | false, 94 | 'suhosin.executor.include.whitelist must be configured correctly in php.ini', 95 | 'Add "phar" to suhosin.executor.include.whitelist in php.ini*.' 96 | ); 97 | } 98 | 99 | if (extension_loaded('xdebug')) { 100 | $this->addPhpConfigRequirement( 101 | 'xdebug.show_exception_trace', false, true 102 | ); 103 | 104 | $this->addPhpConfigRequirement( 105 | 'xdebug.scream', false, true 106 | ); 107 | 108 | $this->addPhpConfigRecommendation( 109 | 'xdebug.max_nesting_level', 110 | function ($cfgValue) { return $cfgValue > 100; }, 111 | true, 112 | 'xdebug.max_nesting_level should be above 100 in php.ini', 113 | 'Set "xdebug.max_nesting_level" to e.g. "250" in php.ini* to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.' 114 | ); 115 | } 116 | 117 | $pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null; 118 | 119 | $this->addRequirement( 120 | null !== $pcreVersion, 121 | 'PCRE extension must be available', 122 | 'Install the PCRE extension (version 8.0+).' 123 | ); 124 | 125 | if (extension_loaded('mbstring')) { 126 | $this->addPhpConfigRequirement( 127 | 'mbstring.func_overload', 128 | function ($cfgValue) { return (int) $cfgValue === 0; }, 129 | true, 130 | 'string functions should not be overloaded', 131 | 'Set "mbstring.func_overload" to 0 in php.ini* to disable function overloading by the mbstring extension.' 132 | ); 133 | } 134 | 135 | /* optional recommendations follow */ 136 | 137 | if (null !== $pcreVersion) { 138 | $this->addRecommendation( 139 | $pcreVersion >= 8.0, 140 | sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion), 141 | 'PCRE 8.0+ 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.' 142 | ); 143 | } 144 | 145 | $this->addRecommendation( 146 | class_exists('DomDocument'), 147 | 'PHP-DOM and PHP-XML modules should be installed', 148 | 'Install and enable the PHP-DOM and the PHP-XML modules.' 149 | ); 150 | 151 | $this->addRecommendation( 152 | function_exists('mb_strlen'), 153 | 'mb_strlen() should be available', 154 | 'Install and enable the mbstring extension.' 155 | ); 156 | 157 | $this->addRecommendation( 158 | function_exists('utf8_decode'), 159 | 'utf8_decode() should be available', 160 | 'Install and enable the XML extension.' 161 | ); 162 | 163 | $this->addRecommendation( 164 | function_exists('filter_var'), 165 | 'filter_var() should be available', 166 | 'Install and enable the filter extension.' 167 | ); 168 | 169 | if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 170 | $this->addRecommendation( 171 | function_exists('posix_isatty'), 172 | 'posix_isatty() should be available', 173 | 'Install and enable the php_posix extension (used to colorize the CLI output).' 174 | ); 175 | } 176 | 177 | $this->addRecommendation( 178 | extension_loaded('intl'), 179 | 'intl extension should be available', 180 | 'Install and enable the intl extension (used for validators).' 181 | ); 182 | 183 | if (extension_loaded('intl')) { 184 | // in some WAMP server installations, new Collator() returns null 185 | $this->addRecommendation( 186 | null !== new \Collator('fr_FR'), 187 | 'intl extension should be correctly configured', 188 | 'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.' 189 | ); 190 | 191 | // check for compatible ICU versions (only done when you have the intl extension) 192 | if (defined('INTL_ICU_VERSION')) { 193 | $version = INTL_ICU_VERSION; 194 | } else { 195 | $reflector = new \ReflectionExtension('intl'); 196 | 197 | ob_start(); 198 | $reflector->info(); 199 | $output = strip_tags(ob_get_clean()); 200 | 201 | preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches); 202 | $version = $matches[1]; 203 | } 204 | 205 | $this->addRecommendation( 206 | version_compare($version, '4.0', '>='), 207 | 'intl ICU version should be at least 4+', 208 | 'Upgrade your intl extension with a newer ICU version (4+).' 209 | ); 210 | 211 | if (class_exists('Symfony\Component\Intl\Intl')) { 212 | $this->addRecommendation( 213 | \Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion(), 214 | 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()), 215 | 'To get the latest internationalization data upgrade the ICU system package and the intl PHP extension.' 216 | ); 217 | if (\Symfony\Component\Intl\Intl::getIcuDataVersion() <= \Symfony\Component\Intl\Intl::getIcuVersion()) { 218 | $this->addRecommendation( 219 | \Symfony\Component\Intl\Intl::getIcuDataVersion() === \Symfony\Component\Intl\Intl::getIcuVersion(), 220 | 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()), 221 | 'To avoid internationalization data inconsistencies upgrade the symfony/intl component.' 222 | ); 223 | } 224 | } 225 | 226 | $this->addPhpConfigRecommendation( 227 | 'intl.error_level', 228 | function ($cfgValue) { return (int) $cfgValue === 0; }, 229 | true, 230 | 'intl.error_level should be 0 in php.ini', 231 | 'Set "intl.error_level" to "0" in php.ini* to inhibit the messages when an error occurs in ICU functions.' 232 | ); 233 | } 234 | 235 | $accelerator = 236 | (extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) 237 | || 238 | (extension_loaded('apc') && ini_get('apc.enabled')) 239 | || 240 | (extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable')) 241 | || 242 | (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) 243 | || 244 | (extension_loaded('xcache') && ini_get('xcache.cacher')) 245 | || 246 | (extension_loaded('wincache') && ini_get('wincache.ocenabled')) 247 | ; 248 | 249 | $this->addRecommendation( 250 | $accelerator, 251 | 'a PHP accelerator should be installed', 252 | 'Install and/or enable a PHP accelerator (highly recommended).' 253 | ); 254 | 255 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 256 | $this->addRecommendation( 257 | $this->getRealpathCacheSize() >= 5 * 1024 * 1024, 258 | 'realpath_cache_size should be at least 5M in php.ini', 259 | 'Setting "realpath_cache_size" to e.g. "5242880" or "5M" in php.ini* may improve performance on Windows significantly in some cases.' 260 | ); 261 | } 262 | 263 | $this->addPhpConfigRecommendation('short_open_tag', false); 264 | 265 | $this->addPhpConfigRecommendation('magic_quotes_gpc', false, true); 266 | 267 | $this->addPhpConfigRecommendation('register_globals', false, true); 268 | 269 | $this->addPhpConfigRecommendation('session.auto_start', false); 270 | 271 | $this->addPhpConfigRecommendation( 272 | 'xdebug.max_nesting_level', 273 | function ($cfgValue) { return $cfgValue > 100; }, 274 | true, 275 | 'xdebug.max_nesting_level should be above 100 in php.ini', 276 | 'Set "xdebug.max_nesting_level" to e.g. "250" in php.ini* to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.' 277 | ); 278 | 279 | $this->addPhpConfigRecommendation( 280 | 'post_max_size', 281 | function () { 282 | $memoryLimit = $this->getMemoryLimit(); 283 | $postMaxSize = $this->getPostMaxSize(); 284 | 285 | return \INF === $memoryLimit || \INF === $postMaxSize || $memoryLimit > $postMaxSize; 286 | }, 287 | true, 288 | '"memory_limit" should be greater than "post_max_size".', 289 | 'Set "memory_limit" to be greater than "post_max_size".' 290 | ); 291 | 292 | $this->addPhpConfigRecommendation( 293 | 'upload_max_filesize', 294 | function () { 295 | $postMaxSize = $this->getPostMaxSize(); 296 | $uploadMaxFilesize = $this->getUploadMaxFilesize(); 297 | 298 | return \INF === $postMaxSize || \INF === $uploadMaxFilesize || $postMaxSize > $uploadMaxFilesize; 299 | }, 300 | true, 301 | '"post_max_size" should be greater than "upload_max_filesize".', 302 | 'Set "post_max_size" to be greater than "upload_max_filesize".' 303 | ); 304 | 305 | $this->addRecommendation( 306 | class_exists('PDO'), 307 | 'PDO should be installed', 308 | 'Install PDO (mandatory for Doctrine).' 309 | ); 310 | 311 | if (class_exists('PDO')) { 312 | $drivers = \PDO::getAvailableDrivers(); 313 | $this->addRecommendation( 314 | count($drivers) > 0, 315 | sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'), 316 | 'Install PDO drivers (mandatory for Doctrine).' 317 | ); 318 | } 319 | } 320 | 321 | /** 322 | * Convert a given shorthand size in an integer 323 | * (e.g. 16k is converted to 16384 int) 324 | * 325 | * @param string $size Shorthand size 326 | * @param string $infiniteValue The infinite value for this setting 327 | * 328 | * @see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes 329 | * 330 | * @return float Converted size 331 | */ 332 | private function convertShorthandSize($size, $infiniteValue = '-1') 333 | { 334 | // Initialize 335 | $size = trim($size); 336 | $unit = ''; 337 | 338 | // Check unlimited alias 339 | if ($size === $infiniteValue) { 340 | return \INF; 341 | } 342 | 343 | // Check size 344 | if (!ctype_digit($size)) { 345 | $unit = strtolower(substr($size, -1, 1)); 346 | $size = (int) substr($size, 0, -1); 347 | } 348 | 349 | // Return converted size 350 | switch ($unit) { 351 | case 'g': 352 | return $size * 1024 * 1024 * 1024; 353 | case 'm': 354 | return $size * 1024 * 1024; 355 | case 'k': 356 | return $size * 1024; 357 | default: 358 | return (int) $size; 359 | } 360 | } 361 | 362 | /** 363 | * Loads realpath_cache_size from php.ini and converts it to int. 364 | * 365 | * (e.g. 16k is converted to 16384 int) 366 | * 367 | * @return float 368 | */ 369 | private function getRealpathCacheSize() 370 | { 371 | return $this->convertShorthandSize(ini_get('realpath_cache_size')); 372 | } 373 | 374 | /** 375 | * Loads post_max_size from php.ini and converts it to int. 376 | * 377 | * @return float 378 | */ 379 | private function getPostMaxSize() 380 | { 381 | return $this->convertShorthandSize(ini_get('post_max_size'), '0'); 382 | } 383 | 384 | /** 385 | * Loads memory_limit from php.ini and converts it to int. 386 | * 387 | * @return float 388 | */ 389 | private function getMemoryLimit() 390 | { 391 | return $this->convertShorthandSize(ini_get('memory_limit')); 392 | } 393 | 394 | /** 395 | * Loads upload_max_filesize from php.ini and converts it to int. 396 | * 397 | * @return float 398 | */ 399 | private function getUploadMaxFilesize() 400 | { 401 | return $this->convertShorthandSize(ini_get('upload_max_filesize'), '0'); 402 | } 403 | } 404 | --------------------------------------------------------------------------------