PHPColibri/framework

View on GitHub
Validation/Validation.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php
namespace Colibri\Validation;

use Colibri\Util\Arr;
use Colibri\Util\Str;

/**
 * Organize Validation of your data.
 */
class Validation
{
    /** @var string */
    public static $requiredMessage = 'поле \'%s\' является обязательным для заполнения.';
    /** @var string */
    public static $minLengthMessage = 'поле \'%s\' должно быть не меньше %d символов.';
    /** @var string */
    public static $maxLengthMessage = 'поле \'%s\' не должно быть больше %d символов.';
    /** @var string */
    public static $regexMessage = 'поле \'%s\' не удовлетворяет условию.';
    /** @var string */
    public static $isIntGt0Message = 'поле \'%s\' должно быть целым числом больше 0.';
    /** @var string */
    public static $isJSONMessage = 'поле \'%s\' должно быть строкой в формате JSON.';
    /** @var string */
    public static $isEmailMessage = 'поле \'%s\' должно содержать существующий почтовый ящик.';
    /** @var string */
    public static $isEqualMessage = 'поля \'%s\' должны быть одинаковыми.';

    /**
     * @var array occurred validation errors
     */
    protected $errors = [];
    /**
     * @var array scope of data to validate
     */
    protected $scope = [];

    /**
     * Validation constructor.
     *
     * @param array $scope initial scope of data to validate
     */
    public function __construct(array $scope = null)
    {
        if ($scope !== null) {
            $this->scope = $scope;
        }
    }

    /**
     * Adds additional data to existing scope.
     *
     * @param array $scope
     *
     * @return $this
     */
    public function extendScope(array $scope)
    {
        $this->scope = array_merge($this->scope, $scope);

        return $this;
    }

    /**
     * Resets scope with new one & resets the errors.
     *
     * @param array $scope
     *
     * @return $this
     */
    public function setScope(array $scope)
    {
        $this->scope  = $scope;
        $this->errors = [];

        return $this;
    }

    /**
     * @return array
     */
    public function errors(): array
    {
        return $this->errors;
    }

    /**
     * Adds specified error $message for $key.
     *
     * @param $key
     * @param $message
     */
    public function addError($key, $message)
    {
        $this->errors[$key] = $message;
    }

    /**
     * @param string       $method
     * @param string|array $key
     * @param string       $message
     * @param callable     $check
     *
     * @return $this
     */
    private function check(string $method, $key, string $message = null, callable $check)
    {
        if (is_array($key)) {
            foreach ($key as $name) {
                $this->$method($name, $message);
            }
        } else {
            if (isset($this->scope[$key]) && ! $check($key)) {
                $this->errors[$key] = sprintf($message !== null ? $message : self::${$method . 'Message'}, $key);
            }
        }

        return $this;
    }

    /**
     * 'Required' validation rule. Checks if specified by $key data exists in scope.
     *
     * @param string|array $key
     * @param string       $message
     *
     * @return $this
     */
    public function required($key, $message = null)
    {
        if (is_array($key)) {
            foreach ($key as $name) {
                $this->required($name, $message);
            }
        } else {
            if ( ! (isset($this->scope[$key]) && ! empty($this->scope[$key]))) {
                $this->errors[$key] = sprintf($message !== null ? $message : self::$requiredMessage, $key);
            }
        }

        return $this;
    }

    /**
     * 'MinLength' validation rule. Checks that specified by $key data not shorter than $minLength.
     *
     * @param string|array $key
     * @param int|array    $minLength
     * @param string       $message
     *
     * @return $this
     */
    public function minLength($key, $minLength, $message = null)
    {
        if (is_array($key)) {
            foreach ($key as $k => $name) {
                $this->minLength($name, is_array($minLength) ? $minLength[$k] : $minLength, $message);
            }
        } else {
            if (isset($this->scope[$key]) && mb_strlen($this->scope[$key]) < $minLength) {
                $this->errors[$key] = sprintf($message !== null ? $message : self::$minLengthMessage, $key, $minLength);
            }
        }

        return $this;
    }

    /**
     * 'MaxLength' validation rule. Checks that specified by $key data not longer than $maxLength.
     *
     * @param string|array $key
     * @param int|array    $maxLength
     * @param string       $message
     *
     * @return $this
     */
    public function maxLength($key, $maxLength, $message = null)
    {
        if (is_array($key)) {
            foreach ($key as $k => $name) {
                $this->maxLength($name, is_array($maxLength) ? $maxLength[$k] : $maxLength, $message);
            }
        } else {
            if (isset($this->scope[$key]) && mb_strlen($this->scope[$key]) > $maxLength) {
                $this->errors[$key] = sprintf($message !== null ? $message : self::$maxLengthMessage, $key, $maxLength);
            }
        }

        return $this;
    }

    /**
     * 'Regex' validation rule. Checks that specified by $key data matches to $pattern.
     *
     * @param string|array $key
     * @param string       $pattern
     * @param string       $message
     *
     * @return $this
     */
    public function regex($key, $pattern, $message = null)
    {
        if (is_array($key)) {
            foreach ($key as $k => $name) {
                $this->regex($name, is_array($pattern) ? $pattern[$k] : $pattern, $message);
            }
        } else {
            if (isset($this->scope[$key]) && ! (bool)preg_match($pattern, $this->scope[$key])) {
                $this->errors[$key] = sprintf($message !== null ? $message : self::$regexMessage, $key);
            }
        }

        return $this;
    }

    /**
     * 'IsIntGt0' validation rule. Checks that specified by $key data is integer and greater than zero.
     *
     * @param string|array $key
     * @param string       $message
     *
     * @return $this
     */
    public function isIntGt0($key, $message = null)
    {
        return $this->check(__FUNCTION__, $key, $message, function ($key) {
            return Str::isInt($this->scope[$key]) && ((int)$this->scope[$key]) > 0;
        });
    }

    /**
     * 'IsJSON' validation rule. Checks that specified by $key data stores a JSON string.
     *
     * @param string|array $key
     * @param string       $message
     *
     * @return $this
     */
    public function isJSON($key, $message = null)
    {
        return $this->check(__FUNCTION__, $key, $message, function ($key) {
            return Str::isJSON($this->scope[$key]);
        });
    }

    /**
     * 'IsEmail' validation rule. Checks that specified by $key data stores a string with email.
     *
     * @param string|array $key
     * @param string       $message
     *
     * @return $this
     */
    public function isEmail($key, $message = null)
    {
        return $this->check(__FUNCTION__, $key, $message, function ($key) {
            return Str::isEmail($this->scope[$key]);
        });
    }

    /**
     * 'IsEqual' validation rule. Checks that specified by $keys values are equal.
     *
     * @param array  $keys
     * @param string $message
     *
     * @return $this
     */
    public function isEqual(array $keys, $message = null)
    {
        $existingKey = null;
        foreach ($keys as $key) {
            if (isset($this->scope[$key])) {
                $existingKey = $key;
                break;
            }
        }
        if ($existingKey === null) {
            return $this;
        }

        foreach ($keys as $key) {
            if (isset($this->scope[$key]) && $this->scope[$key] != $this->scope[$existingKey]) {
                $keysList           = implode("', '", $keys);
                $this->errors[$key] = sprintf($message !== null ? $message : self::$isEqualMessage, $keysList);
            }
        }

        return $this;
    }

    /**
     * 'Is' validation rule. Custom rule specified by $checkFunc().
     *
     * @param callable     $checkFunc
     * @param string|array $key
     * @param string       $message
     *
     * @return static
     */
    public function is($checkFunc, $key, $message)
    {
        if (is_array($key)) {
            foreach ($key as $name) {
                $this->is($checkFunc, $name, $message);
            }
        } else {
            if (isset($this->scope[$key]) && ! call_user_func($checkFunc, $this->scope[$key])) {
                $this->errors[$key] = sprintf($message, $key);
            }
        }

        return $this;
    }

    /**
     * 'Is' validation rule. Custom rule specified by $checkFunc(). Checks that data NOT satisfies to rule.
     *
     * @param callable     $checkFunc
     * @param string|array $key
     * @param string       $message
     *
     * @return $this
     */
    public function isNot($checkFunc, $key, $message)
    {
        if (is_array($key)) {
            foreach ($key as $name) {
                $this->isNot($checkFunc, $name, $message);
            }
        } else {
            if (isset($this->scope[$key]) && call_user_func($checkFunc, $this->scope[$key])) {
                $this->errors[$key] = sprintf($message, $key);
            }
        }

        return $this;
    }

    /**
     * Checks if scope data is valid or not.
     *
     * @return bool
     */
    public function valid()
    {
        return ! (bool)count($this->errors);
    }

    /**
     * Calls $callback if scope is valid.
     *
     * @param \Closure $callback
     *
     * @return $this
     */
    public function ifIsValid(\Closure $callback)
    {
        if ($this->valid()) {
            $callback($this->scope);
        }

        return $this;
    }

    /**
     * Calls $callback if scope is NOT valid.
     *
     * @param \Closure $callback
     *
     * @return $this
     */
    public function ifNotValid(\Closure $callback)
    {
        if ( ! $this->valid()) {
            $callback($this->errors);
        }

        return $this;
    }

    /** @noinspection PhpDocRedundantThrowsInspection */

    /**
     * Validates the data scope.
     *
     * @return $this
     *
     * @throws ValidationException
     */
    public function validate()
    {
        $this->ifNotValid(function (array $errors) {
            /* @noinspection PhpUnhandledExceptionInspection */
            throw new ValidationException($errors);
        });

        return $this;
    }

    /**
     * Creates new Validation instance with $scope injected.
     *
     * @param array $scope
     *
     * @return static
     */
    public static function forScope(array $scope)
    {
        return new static($scope);
    }

    /**
     * Gets from $_POST only needed keys and use new as scope.
     * Creates new Validation instance with this scope.
     *
     * @param array $keys list of keys in $_POST array that must be validates
     *
     * @return Validation
     */
    public static function forPostOnly(array $keys)
    {
        return static::forScope(Arr::only($_POST, $keys));
    }
}