Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00%
0 / 1
50.00%
1 / 2
CRAP
84.67%
116 / 137
PhpExtensionCheck
0.00%
0 / 1
50.00%
1 / 2
35.69
84.67%
116 / 137
 getDefaultGroupName()
100.00%
1 / 1
1
100.00%
1 / 1
 run()
0.00%
0 / 1
34.54
84.56%
115 / 136
<?php
namespace Environaut\Checks;
use Environaut\Checks\Check;
use Environaut\Checks\PhpSettingCheck;
/**
 * This check compares PHP extensions and their version and settings against given values.
 * By default the name of the check will be used as the PHP extension name that is checked.
 *
 * All supported parameters are:
 * - "extension": name of extension to check (defaults to the "name" of the check)
 * - "custom_name": name to be used for messages if the check's name is used as extension name
 * - "loaded": boolean parameter to determine if the extension should be loaded or not
 * - "version": the version string the extension should match (e.g. ">=2.6.30" or ">1.0.2")
 * - "regex": regular expression(s) that should match on the extension's info (see phpinfo)
 * - "help": message to display when the extension does not fulfil the "version", "regex" and/or "loaded" parameters
 * - "debug": var_dump's extension name, version and info string for regex analysis
 *
 * As the version of a PHP extension may be empty or some weird value you can use a version comparison of a phpinfo()
 * string by using a nested "version" parameter like this:
 *
 * <parameter name="version">
 *     <parameter name="regex"><![CDATA[#libXML (Compiled )?Version => (?P<version>\d+.+?)\n#]]></parameter>
 *     <parameter name="value"><![CDATA[>=2.6.30]]></parameter>
 * </parameter>
 *
 * Notice, that you need a NAMED CAPTURING GROUP "version" in you regular expression. The "value" then specifies the
 * version comparison operation that should be done with that matching group.
 */
class PhpExtensionCheck extends Check
{
    /**
     * Default group name used in messages of the report.
     * By default also used as default setting group name if not customized.
     */
    const DEFAULT_CUSTOM_GROUP_NAME = 'PHP Extensions';
    /**
     * Returns the default group name this check uses when none is specified.
     *
     * @return string default group name of the check
     */
    public function getDefaultGroupName()
    {
        return self::DEFAULT_CUSTOM_GROUP_NAME;
    }
    public function run()
    {
        $params = $this->getParameters();
        $extension = $params->get('extension', $this->getName());
        if (empty($extension)) {
            throw new \InvalidArgumentException(
                'Parameter "extension" must be a php extension name to check on class "' . get_class($this) . '".'
            );
        }
        $custom_name = $params->get('custom_name', $this->getName());
        $help = $params->get('help');
        $wanted_version = $params->get('version');
        $loaded = $params->get('loaded');
        $regex = $params->get('regex');
        $debug_mode = $params->get('debug', false);
        $okay = true;
        try {
            // GATHER DATA
            $extension_class = new \ReflectionExtension($extension);
            $extension_version = $extension_class->getVersion();
            ob_start();
            $extension_class->info();
            $info = ob_get_clean();
            if ($debug_mode) {
                var_dump($extension, $extension_version, $info, '============================================');
            }
            // OPTIONS CHECK
            if (null !== $regex) {
                if (is_array($regex)) { // multiple regular expressions
                    foreach ($regex as $key => $test) {
                        if (!is_string($test)) {
                            throw new \InvalidArgumentException(
                                'The extension requirements must be strings that are valid regular expressions.'
                            );
                        }
                        if (strpos($test, '(?P<contains>') !== false && !is_numeric($key)) {
                            // explode "name" attribute & match each item in the named capturing group "contains" w/ it
                            $values = explode(',', $key);
                            $values = array_map('trim', $values);
                            $regex_matches = preg_match($test, $info, $matches);
                            if ($regex_matches) {
                                $pool = $matches['contains'];
                                foreach ($values as $value) {
                                    if (strpos($pool, $value) === false) {
                                        $this->addError(
                                            'The extension "' . $extension . '" does not have "' .
                                            $value . '" support.',
                                            $custom_name
                                        );
                                        $okay = false;
                                    } else {
                                        $this->addInfo(
                                            'The extension "' . $extension . '" does have "' . $value . '" support.',
                                            $custom_name
                                        );
                                    }
                                }
                            }
                        } else { // just preg_match the given regex string
                            if (!preg_match($test, $info)) {
                                $this->addError(
                                    'The extension "' . $extension . '" does not match the requirement: ' . $test,
                                    $custom_name
                                );
                                $okay = false;
                            } else {
                                $this->addInfo(
                                    'The extension "' . $extension . '" does match the requirement: ' . $test,
                                    $custom_name
                                );
                            }
                        }
                    }
                } else { // single regex to test
                    if (!preg_match($regex, $info)) {
                        $this->addError(
                            'The extension "' . $extension . '" does not match the requirement: ' . $regex,
                            $custom_name
                        );
                        $okay = false;
                    } else {
                        $this->addInfo(
                            'The extension "' . $extension . '" does match the requirement: ' . $regex,
                            $custom_name
                        );
                    }
                }
            }
            // VERSION COMPARISON
            if (null !== $wanted_version) {
                if (is_array($wanted_version) && array_key_exists('regex', $wanted_version) &&
                    array_key_exists('value', $wanted_version)) {
                    $regex_matches = preg_match($wanted_version['regex'], $info, $matches);
                    if (!$regex_matches || !array_key_exists('version', $matches)) {
                        $this->addError(
                            'Version information of "' . $extension . '" could not be determined, as ' .
                            'the given regular expression did not match: "' . $wanted_version['regex'] . PHP_EOL .
                            'Remember that you need a valid named capturing group "version" in the ' .
                            'regexp, e.g.: #libXML (Compiled )?Version => (?P<version>\d+.+?)\n#' . PHP_EOL .
                            'Set the parameter "debug" to true to work on that matching regex.',
                            $custom_name
                        );
                        $okay = false;
                    } elseif ($regex_matches) {
                        $operator = PhpSettingCheck::getOperator($wanted_version['value']);
                        $wanted_version_without_operator = ltrim($wanted_version['value'], '<>!=');
                        if (!version_compare($matches['version'], $wanted_version_without_operator, $operator)) {
                            $this->addError(
                                'Version of "' . $extension . '" should be "' . $wanted_version['value'] .
                                '", but is: "' . $matches['version'] . '"',
                                $custom_name
                            );
                            $okay = false;
                        } else {
                            $this->addInfo(
                                'Version of extension "' . $extension . '" is "' . $matches['version'] .
                                '" ("' . $wanted_version['value'] . '").',
                                $custom_name
                            );
                        }
                    }
                } elseif (is_string($wanted_version)) {
                    $operator = PhpSettingCheck::getOperator($wanted_version);
                    $wanted_version_without_operator = ltrim($wanted_version, '<>!=');
                    if (!version_compare($extension_version, $wanted_version_without_operator, $operator)) {
                        $this->addError(
                            'Version of "' . $extension . '" should be "' . $wanted_version .
                            '", but is: "' . $extension_version . '"',
                            $custom_name
                        );
                        $okay = false;
                    } else {
                        $this->addInfo(
                            'Version of extension "' . $extension . '" is "' . $extension_version . '"' .
                            ' ("' . $wanted_version. '").',
                            $custom_name
                        );
                    }
                } else {
                    throw new \InvalidArgumentException(
                        'A nested version parameter needs exactly two keys: ' . PHP_EOL .
                        '- "regex" with one matching named capturing group "version" (e.g. ' .
                        '"#libXML (Compiled )?Version => (?P<version>\d+.+?)\n#") and ' . PHP_EOL .
                        '- "value" to compare the named capturing group content against' .
                        '(version comparison, e.g. ">=2.6.26").'
                    );
                }
            }
            // LOADED CHECK
            if (null !== $loaded) {
                if ($loaded != extension_loaded($extension)) {
                    $loaded_string = $loaded ? 'not loaded, but should be.' : 'loaded, but should not be.';
                    $this->addError('The extension "' . $extension . '" is ' . $loaded_string, $custom_name);
                    $okay = false;
                } else {
                    $this->addInfo('The extension "' . $extension . '" is loaded.', $custom_name);
                }
            }
        } catch (\ReflectionException $e) {
            $this->addError('There is no extension with the name "' . $extension . '".', $custom_name);
            $okay = false;
        }
        if (!$okay && $help !== null) {
            $this->addNotice($help, $custom_name);
        }
        if ($okay) {
            $this->addInfo('Extension "' . $extension . '" is available and correct.', $custom_name);
        }
        return $okay;
    }
}