Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00%
0 / 1
66.67%
4 / 6
CRAP
94.51%
172 / 182
PhpSettingCheck
0.00%
0 / 1
66.67%
4 / 6
58.56
94.51%
172 / 182
 getDefaultGroupName()
100.00%
1 / 1
1
100.00%
1 / 1
 run()
0.00%
0 / 1
30.54
91.59%
98 / 107
 getSupportedComparisons()
100.00%
1 / 1
1
100.00%
8 / 8
 compareIntegers($value, $setting_value)
0.00%
0 / 1
7
96.00%
24 / 25
 getOperator($value)
100.00%
1 / 1
13
100.00%
20 / 20
 getIntegerValue($value)
100.00%
1 / 1
6
100.00%
21 / 21
<?php
namespace Environaut\Checks;
use Environaut\Checks\Check;
/**
 * This check compares PHP runtime configuration options (from php.ini etc.) against given values.
 * The following comparison operations are supported:
 * - "equals": the default comparison; checks whether the setting is exactly as given (e.g. "foo")
 * - "null": checks whether the setting is NULL
 * - "notempty": checks whether the setting has a value set (that is, not null or empty string)
 * - "version": checks how versions compare (e.g. "=>2.6.30")
 * - "regex": checks whether the PHP setting's value matches the given regular expression (e.g. "/file\.ini/"
 * - "integer": checks whether the integer or byte value of the PHP setting is correct (e.g. ">30" or ">=256M")
 *
 * By default the name of the check will be used as the PHP runtime setting name to check. For integer
 * or byte values that may be set to infinite values (like "-1" or "0") you can specify the setting's
 * infite value via the "infinite" parameter. The comparison operation is "equals" by default and
 * may be specified via the "comparison" parameter.
 *
 * All supported parameters are:
 * - "setting": name of setting 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 setting name
 * - "value": the value to compare the setting's value against
 * - "infinite": the infinite value of the setting to check (e.g. "-1" or "0" for integer or byte values)
 * - "comparison": the comparison operation to use (see above)
 * - "help": message to display when the setting's value is not correct
 */
class PhpSettingCheck 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 Settings';
    /**
     * 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();
        $setting = $params->get('setting', $this->getName());
        if (empty($setting)) {
            throw new \InvalidArgumentException(
                'Parameter "setting" must be a (valid) php.ini setting to check on class "' . get_class($this) . '".'
            );
        }
        $custom_name = $params->get('custom_name', $this->getName());
        $help = $params->get('help');
        $setting_value = $params->get('value');
        $infinite_value = $params->get('infinite');
        $comparison = strtolower($params->get('comparison', 'equals'));
        if (!in_array($comparison, self::getSupportedComparisons())) {
            throw new \InvalidArgumentException(
                'Unsupported comparison name: "' . $comparison .
                '". Supported are: ' . implode(', ', self::getSupportedComparisons())
            );
        }
        /**
         * The value of the configuration option as
         * - a string on success or
         * - an empty string for null values or
         * - false if the setting doesn't exist
         */
        $value = ini_get($setting);
        $okay = true;
        if (false === $value) {
            $this->addError('There is no setting with the name "' . $setting . '".', $custom_name);
            $okay = false;
        } else {
            switch ($comparison) {
                case 'integer':
                    // bail out here directly to not compare float values like "2.75M" or "1e3"
                    if ($value !== trim($value) || strpos($value, '.') !== false || stripos($value, 'e') !== false) {
                        $this->addError(
                            'Value of "' . $setting . '" should be "' . $setting_value .
                            '", but is: "' . $value . '". Leading whitespace or ' .
                            'decimal values will fail in practice as PHP treats e.g. "2.75M" as "2M"!',
                            $custom_name
                        );
                        $okay = false;
                        break;
                    }
                    if ((null !== $infinite_value && $value != $infinite_value) || null === $infinite_value) {
                        $okay = self::compareIntegers($value, $setting_value);
                        if (!$okay) {
                            $this->addError(
                                'Value of "' . $setting . '" should be "' . $setting_value .
                                '", but is: "' . $value . '"',
                                $custom_name
                            );
                        }
                    }
                    break;
                case 'regex':
                    if (!preg_match($setting_value, $value)) {
                        $this->addError(
                            'Value of "' . $setting . '" does not match "' . $setting_value .
                            '": ' . $value . '"',
                            $custom_name
                        );
                        $okay = false;
                    }
                    break;
                case 'version':
                    $operator = self::getOperator($setting_value);
                    $setting_value_without_operator = ltrim($setting_value, '<>!=');
                    if (!version_compare($value, $setting_value_without_operator, $operator)) {
                        $this->addError(
                            'Version of "' . $setting . '" should be "' . $setting_value .
                            '", but is: "' . $value . '"',
                            $custom_name
                        );
                        $okay = false;
                    }
                    break;
                case 'notempty':
                    if ($value === null || $value === "") {
                        $this->addError(
                            'Value of "' . $setting . '" should not be NULL or empty string, but is: "' . $value .
                            '"',
                            $custom_name
                        );
                        $okay = false;
                    }
                    break;
                case 'null':
                    if ($value !== "") {
                        $this->addError(
                            'Value of "' . $setting . '" must be NULL, but is: "' . $value . '"',
                            $custom_name
                        );
                        $okay = false;
                    }
                    break;
                case 'notequals':
                    if ($value === $setting_value) {
                        $this->addError(
                            'Value of "' . $setting . '" should not be "' . $setting_value . '", but is: "' . $value .
                            '"',
                            $custom_name
                        );
                        $okay = false;
                    }
                    break;
                case 'equals':
                default:
                    if ($value === "" && $setting_value === "0") {
                        break; // boolean value should be off (and can be reported as "" or "0" according to php docs)
                    }
                    if ($value !== $setting_value) {
                        $this->addError(
                            'Value of "' . $setting . '" should be "' . $setting_value . '", but is: "' . $value .
                            '"',
                            $custom_name
                        );
                        $okay = false;
                    }
                    break;
            }
        }
        if (!$okay && $help !== null) {
            // TODO depending on local/global value and access level we could generate
            //  a small help here where to change the ini setting
            $this->addNotice($help, $custom_name);
        }
        if ($okay) {
            // TODO give better info depending on comparison type?
            $this->addInfo('Value of "' . $setting . '" is okay: "' . $value . '"', $custom_name);
        }
        return $okay;
    }
    /**
     * Returns the supported comparison names that may be used in comparison operations.
     *
     * @return array of strings (supported comparison names)
     */
    public static function getSupportedComparisons()
    {
        return array(
            'equals',
            'notequals',
            'regex',
            'notempty',
            'null',
            'version',
            'integer',
        );
    }
    /**
     * Compares the given values according to the comparison operator the second
     * value might have and returns whether the evaluation is true.
     *
     * @param string $value integer value that is set in php.ini (e.g. "8M")
     * @param string $setting_value comparison value as string (e.g. ">=8M")
     *
     * @return bool true if comparison is okay; false otherwise.
     */
    public static function compareIntegers($value, $setting_value)
    {
        $okay = false;
        $actual = self::getIntegerValue($value);
        $operator = self::getOperator($setting_value);
        $expected = self::getIntegerValue($setting_value);
        switch ($operator) {
            case '>':
                $okay = $actual > $expected;
                break;
            case '>=':
                $okay = $actual >= $expected;
                break;
            case '<':
                $okay = $actual < $expected;
                break;
            case '<=':
                $okay = $actual <= $expected;
                break;
            case '!=':
                $okay = $actual != $expected;
                break;
            case '=':
            default:
                $okay = $actual == $expected;
                break;
        }
        return $okay;
    }
    /**
     * Returns the comparison operator part of the given value. Valid known
     * comparison operators are: "<", ">", "<=", ">=", "!=" and "=".
     * By default a ">=" will be returned (if no comparison operator is found).
     *
     * @param string $value string that might contain a comparison operator (e.g. "<=2M")
     *
     * @return string operator string found
     */
    public static function getOperator($value)
    {
        // TODO make this smarter and perhaps support "eq", "ne", "le", "ge" etc.
        $operator = strpos($value, '>=') === 0 ? '>=' : null;
        if (null === $operator) {
            $operator = strpos($value, '>') === 0 ? '>' : null;
        }
        if (null === $operator) {
            $operator = strpos($value, '=') === 0 ? '=' : null;
        }
        if (null === $operator) {
            $operator = strpos($value, '<=') === 0 ? '<=' : null;
        }
        if (null === $operator) {
            $operator = strpos($value, '<') === 0 ? '<' : null;
        }
        if (null === $operator) {
            $operator = strpos($value, '!=') === 0 ? '!=' : null;
        }
        if (null === $operator) {
            $operator = '>=';
        }
        return $operator;
    }
    /**
     * Returns the integer value of the given value.
     *
     * @param mixed $value string to interpret (e.g. ">=2.75M")
     *
     * @return int integer value as PHP interprets it on ini settings
     */
    public static function getIntegerValue($value)
    {
        if (!is_numeric($value)) {
            // strip excessive whitespace
            $value = trim($value);
            // strip comparison operator
            $value = ltrim($value, '<>!=');
            // remove the K|M|G suffix if necessary
            $len = strlen($value);
            if (!is_numeric($value)) {
                $quantity = substr($value, 0, $len - 1);
                // we need this, as PHP sets a memory_limit of 0 if the ini value is 0.25M
                // another example: 2.75M will be 2M for PHP (e.g. on post_max_size)
                $quantity = intval($quantity);
            } else {
                $quantity = $value;
            }
            // get unit if this string represents a byte value
            $unit = strtolower(substr($value, $len - 1));
            switch ($unit) {
                case 'k':
                    $quantity *= 1024;
                    break;
                case 'm':
                    $quantity *= 1048576;
                    break;
                case 'g':
                    $quantity *= 1073741824;
                    break;
            }
            return $quantity;
        }
        return intval($value);
    }
}