mirror of
https://github.com/OpenXE-org/OpenXE.git
synced 2024-12-26 22:50:29 +01:00
498 lines
14 KiB
PHP
498 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Rakit\Validation;
|
|
|
|
use Rakit\Validation\Rules\Required;
|
|
use Closure;
|
|
use Rakit\Validation\Rules\Defaults;
|
|
|
|
class Validation
|
|
{
|
|
|
|
protected $validator;
|
|
|
|
protected $inputs = [];
|
|
|
|
protected $attributes = [];
|
|
|
|
protected $messages = [];
|
|
|
|
protected $aliases = [];
|
|
|
|
protected $messageSeparator = ':';
|
|
|
|
protected $validData = [];
|
|
protected $invalidData = [];
|
|
|
|
public function __construct(Validator $validator, array $inputs, array $rules, array $messages = array())
|
|
{
|
|
$this->validator = $validator;
|
|
$this->inputs = $this->resolveInputAttributes($inputs);
|
|
$this->messages = $messages;
|
|
$this->errors = new ErrorBag;
|
|
foreach($rules as $attributeKey => $rules) {
|
|
$this->addAttribute($attributeKey, $rules);
|
|
}
|
|
}
|
|
|
|
public function addAttribute($attributeKey, $rules)
|
|
{
|
|
$resolved_rules = $this->resolveRules($rules);
|
|
$attribute = new Attribute($this, $attributeKey, $this->getAlias($attributeKey), $resolved_rules);
|
|
$this->attributes[$attributeKey] = $attribute;
|
|
}
|
|
|
|
public function getAttribute($attributeKey)
|
|
{
|
|
return isset($this->attributes[$attributeKey])? $this->attributes[$attributeKey] : null;
|
|
}
|
|
|
|
public function validate(array $inputs = array())
|
|
{
|
|
$this->errors = new ErrorBag; // reset error bag
|
|
$this->inputs = array_merge($this->inputs, $this->resolveInputAttributes($inputs));
|
|
foreach($this->attributes as $attributeKey => $attribute) {
|
|
$this->validateAttribute($attribute);
|
|
}
|
|
}
|
|
|
|
public function errors()
|
|
{
|
|
return $this->errors;
|
|
}
|
|
|
|
protected function validateAttribute(Attribute $attribute)
|
|
{
|
|
if ($this->isArrayAttribute($attribute)) {
|
|
$attributes = $this->parseArrayAttribute($attribute);
|
|
foreach($attributes as $i => $attr) {
|
|
$this->validateAttribute($attr);
|
|
}
|
|
return;
|
|
}
|
|
|
|
$attributeKey = $attribute->getKey();
|
|
$rules = $attribute->getRules();
|
|
|
|
$value = $this->getValue($attributeKey);
|
|
$isEmptyValue = $this->isEmptyValue($value);
|
|
|
|
$isValid = true;
|
|
foreach($rules as $ruleValidator) {
|
|
$ruleValidator->setAttribute($attribute);
|
|
|
|
if ($isEmptyValue && $ruleValidator instanceof Defaults) {
|
|
$value = $ruleValidator->check(null);
|
|
$isEmptyValue = $this->isEmptyValue($value);
|
|
continue;
|
|
}
|
|
|
|
$valid = $ruleValidator->check($value);
|
|
|
|
if ($isEmptyValue AND $this->ruleIsOptional($attribute, $ruleValidator)) {
|
|
continue;
|
|
}
|
|
|
|
if (!$valid) {
|
|
$isValid = false;
|
|
$this->addError($attribute, $value, $ruleValidator);
|
|
if ($ruleValidator->isImplicit()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($isValid) {
|
|
$this->setValidData($attribute, $value);
|
|
} else {
|
|
$this->setInvalidData($attribute, $value);
|
|
}
|
|
}
|
|
|
|
protected function isArrayAttribute(Attribute $attribute)
|
|
{
|
|
$key = $attribute->getKey();
|
|
return strpos($key, '*') !== false;
|
|
}
|
|
|
|
protected function parseArrayAttribute(Attribute $attribute)
|
|
{
|
|
$attributeKey = $attribute->getKey();
|
|
$data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey));
|
|
|
|
$pattern = str_replace('\*', '([^\.]+)', preg_quote($attributeKey));
|
|
|
|
$data = array_merge($data, $this->extractValuesForWildcards(
|
|
$data, $attributeKey
|
|
));
|
|
|
|
$attributes = [];
|
|
|
|
foreach ($data as $key => $value) {
|
|
if ((bool) preg_match('/^'.$pattern.'\z/', $key, $match)) {
|
|
$attr = new Attribute($this, $key, null, $attribute->getRules());
|
|
$attr->setPrimaryAttribute($attribute);
|
|
$attr->setKeyIndexes(array_slice($match, 1));
|
|
$attributes[] = $attr;
|
|
}
|
|
}
|
|
|
|
// set other attributes to each attributes
|
|
foreach ($attributes as $i => $attr) {
|
|
$otherAttributes = $attributes;
|
|
unset($otherAttributes[$i]);
|
|
$attr->setOtherAttributes($otherAttributes);
|
|
}
|
|
|
|
return $attributes;
|
|
}
|
|
|
|
/**
|
|
* Gather a copy of the attribute data filled with any missing attributes.
|
|
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L334
|
|
*
|
|
* @param string $attribute
|
|
* @return array
|
|
*/
|
|
protected function initializeAttributeOnData($attributeKey)
|
|
{
|
|
$explicitPath = $this->getLeadingExplicitAttributePath($attributeKey);
|
|
|
|
$data = $this->extractDataFromPath($explicitPath);
|
|
|
|
$asteriskPos = strpos($attributeKey, '*');
|
|
|
|
if (false === $asteriskPos || $asteriskPos === (mb_strlen($attributeKey, 'UTF-8') - 1)) {
|
|
return $data;
|
|
}
|
|
|
|
return Helper::arraySet($data, $attributeKey, null, true);
|
|
}
|
|
|
|
/**
|
|
* Get all of the exact attribute values for a given wildcard attribute.
|
|
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L354
|
|
*
|
|
* @param array $data
|
|
* @param string $attributeKey
|
|
* @return array
|
|
*/
|
|
public function extractValuesForWildcards($data, $attributeKey)
|
|
{
|
|
$keys = [];
|
|
|
|
$pattern = str_replace('\*', '[^\.]+', preg_quote($attributeKey));
|
|
|
|
foreach ($data as $key => $value) {
|
|
if ((bool) preg_match('/^'.$pattern.'/', $key, $matches)) {
|
|
$keys[] = $matches[0];
|
|
}
|
|
}
|
|
|
|
$keys = array_unique($keys);
|
|
|
|
$data = [];
|
|
|
|
foreach ($keys as $key) {
|
|
$data[$key] = Helper::arrayGet($this->inputs, $key);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Get the explicit part of the attribute name.
|
|
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L2817
|
|
*
|
|
* E.g. 'foo.bar.*.baz' -> 'foo.bar'
|
|
*
|
|
* Allows us to not spin through all of the flattened data for some operations.
|
|
*
|
|
* @param string $attributeKey
|
|
* @return string
|
|
*/
|
|
protected function getLeadingExplicitAttributePath($attributeKey)
|
|
{
|
|
return rtrim(explode('*', $attributeKey)[0], '.') ?: null;
|
|
}
|
|
|
|
/**
|
|
* Extract data based on the given dot-notated path.
|
|
* Adapted from: https://github.com/illuminate/validation/blob/v5.3.23/Validator.php#L2830
|
|
*
|
|
* Used to extract a sub-section of the data for faster iteration.
|
|
*
|
|
* @param string $attributeKey
|
|
* @return array
|
|
*/
|
|
protected function extractDataFromPath($attributeKey)
|
|
{
|
|
$results = [];
|
|
|
|
$value = Helper::arrayGet($this->inputs, $attributeKey, '__missing__');
|
|
|
|
if ($value != '__missing__') {
|
|
Helper::arraySet($results, $attributeKey, $value);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function addError(Attribute $attribute, $value, Rule $ruleValidator)
|
|
{
|
|
$ruleName = $ruleValidator->getKey();
|
|
$message = $this->resolveMessage($attribute, $value, $ruleValidator);
|
|
|
|
$this->errors->add($attribute->getKey(), $ruleName, $message);
|
|
}
|
|
|
|
protected function isEmptyValue($value)
|
|
{
|
|
$requiredValidator = new Required;
|
|
return false === $requiredValidator->check($value, []);
|
|
}
|
|
|
|
protected function ruleIsOptional(Attribute $attribute, Rule $rule)
|
|
{
|
|
return false === $attribute->isRequired() AND
|
|
false === $rule->isImplicit() AND
|
|
false === $rule instanceof Required;
|
|
}
|
|
|
|
protected function resolveAttributeName(Attribute $attribute)
|
|
{
|
|
$primaryAttribute = $attribute->getPrimaryAttribute();
|
|
if (isset($this->aliases[$attribute->getKey()])) {
|
|
return $this->aliases[$attribute->getKey()];
|
|
} elseif($primaryAttribute AND isset($this->aliases[$primaryAttribute->getKey()])) {
|
|
return $this->aliases[$primaryAttribute->getKey()];
|
|
} elseif ($this->validator->getUseHumanizedKeys()) {
|
|
return $attribute->getHumanizedKey();
|
|
} else {
|
|
return $attribute->getKey();
|
|
}
|
|
}
|
|
|
|
protected function resolveMessage(Attribute $attribute, $value, Rule $validator)
|
|
{
|
|
$primaryAttribute = $attribute->getPrimaryAttribute();
|
|
$params = $validator->getParameters();
|
|
$attributeKey = $attribute->getKey();
|
|
$ruleKey = $validator->getKey();
|
|
$alias = $attribute->getAlias() ?: $this->resolveAttributeName($attribute);
|
|
$message = $validator->getMessage(); // default rule message
|
|
$message_keys = [
|
|
$attributeKey.$this->messageSeparator.$ruleKey,
|
|
$attributeKey,
|
|
$ruleKey
|
|
];
|
|
|
|
if ($primaryAttribute) {
|
|
// insert primaryAttribute keys
|
|
// $message_keys = [
|
|
// $attributeKey.$this->messageSeparator.$ruleKey,
|
|
// >> here [1] <<
|
|
// $attributeKey,
|
|
// >> and here [3] <<
|
|
// $ruleKey
|
|
// ];
|
|
$primaryAttributeKey = $primaryAttribute->getKey();
|
|
array_splice($message_keys, 1, 0, $primaryAttributeKey.$this->messageSeparator.$ruleKey);
|
|
array_splice($message_keys, 3, 0, $primaryAttributeKey);
|
|
}
|
|
|
|
foreach($message_keys as $key) {
|
|
if (isset($this->messages[$key])) {
|
|
$message = $this->messages[$key];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Replace message params
|
|
$vars = array_merge($params, [
|
|
'attribute' => $alias,
|
|
'value' => $value,
|
|
]);
|
|
|
|
foreach($vars as $key => $value) {
|
|
$value = $this->stringify($value);
|
|
$message = str_replace(':'.$key, $value, $message);
|
|
}
|
|
|
|
// Replace key indexes
|
|
$keyIndexes = $attribute->getKeyIndexes();
|
|
foreach ($keyIndexes as $pathIndex => $index) {
|
|
$replacers = [
|
|
"[{$pathIndex}]" => $index,
|
|
];
|
|
|
|
if (is_numeric($index)) {
|
|
$replacers["{{$pathIndex}}"] = $index + 1;
|
|
}
|
|
|
|
$message = str_replace(array_keys($replacers), array_values($replacers), $message);
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
protected function stringify($value)
|
|
{
|
|
if (is_string($value) || is_numeric($value)) {
|
|
return $value;
|
|
} elseif(is_array($value) || is_object($value)) {
|
|
return json_encode($value);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
protected function resolveRules($rules)
|
|
{
|
|
if (is_string($rules)) {
|
|
$rules = explode('|', $rules);
|
|
}
|
|
|
|
$resolved_rules = [];
|
|
$validatorFactory = $this->getValidator();
|
|
|
|
foreach($rules as $i => $rule) {
|
|
if (empty($rule)) continue;
|
|
$params = [];
|
|
|
|
if (is_string($rule)) {
|
|
list($rulename, $params) = $this->parseRule($rule);
|
|
$validator = call_user_func_array($validatorFactory, array_merge([$rulename], $params));
|
|
} elseif($rule instanceof Rule) {
|
|
$validator = $rule;
|
|
} elseif($rule instanceof Closure) {
|
|
$validator = call_user_func_array($validatorFactory, ['callback', $rule]);
|
|
} else {
|
|
$ruleName = is_object($rule) ? get_class($rule) : gettype($rule);
|
|
throw new \Exception("Rule must be a string, closure or Rakit\Validation\Rule instance. ".$ruleName." given");
|
|
}
|
|
|
|
$resolved_rules[] = $validator;
|
|
}
|
|
|
|
return $resolved_rules;
|
|
}
|
|
|
|
protected function parseRule($rule)
|
|
{
|
|
$exp = explode(':', $rule, 2);
|
|
$rulename = $exp[0];
|
|
if ($rulename !== 'regex') {
|
|
$params = isset($exp[1])? explode(',', $exp[1]) : [];
|
|
} else {
|
|
$params = [$exp[1]];
|
|
}
|
|
|
|
return [$rulename, $params];
|
|
}
|
|
|
|
public function setMessage($key, $message)
|
|
{
|
|
$this->messages[$key] = $message;
|
|
}
|
|
|
|
public function setMessages(array $messages)
|
|
{
|
|
$this->messages = array_merge($this->messages, $messages);
|
|
}
|
|
|
|
public function setAlias($attributeKey, $alias)
|
|
{
|
|
$this->aliases[$attributeKey] = $alias;
|
|
}
|
|
|
|
public function getAlias($attributeKey)
|
|
{
|
|
return isset($this->aliases[$attributeKey])? $this->aliases[$attributeKey] : null;
|
|
}
|
|
|
|
public function setAliases($aliases)
|
|
{
|
|
$this->aliases = array_merge($this->aliases, $aliases);
|
|
}
|
|
|
|
public function passes()
|
|
{
|
|
return $this->errors->count() == 0;
|
|
}
|
|
|
|
public function fails()
|
|
{
|
|
return !$this->passes();
|
|
}
|
|
|
|
public function getValue($key)
|
|
{
|
|
return Helper::arrayGet($this->inputs, $key);
|
|
}
|
|
|
|
public function hasValue($key)
|
|
{
|
|
return Helper::arrayHas($this->inputs, $key);
|
|
}
|
|
|
|
public function getValidator()
|
|
{
|
|
return $this->validator;
|
|
}
|
|
|
|
protected function resolveInputAttributes(array $inputs)
|
|
{
|
|
$resolvedInputs = [];
|
|
foreach($inputs as $key => $rules) {
|
|
$exp = explode(':', $key);
|
|
|
|
if (count($exp) > 1) {
|
|
// set attribute alias
|
|
$this->aliases[$exp[0]] = $exp[1];
|
|
}
|
|
|
|
$resolvedInputs[$exp[0]] = $rules;
|
|
}
|
|
|
|
return $resolvedInputs;
|
|
}
|
|
|
|
public function getValidatedData() {
|
|
return array_merge($this->validData, $this->invalidData);
|
|
}
|
|
|
|
protected function setValidData(Attribute $attribute, $value)
|
|
{
|
|
$key = $attribute->getKey();
|
|
if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) {
|
|
Helper::arraySet($this->validData, $key, $value);
|
|
Helper::arrayUnset($this->invalidData, $key);
|
|
} else {
|
|
$this->validData[$key] = $value;
|
|
}
|
|
}
|
|
|
|
public function getValidData()
|
|
{
|
|
return $this->validData;
|
|
}
|
|
|
|
protected function setInvalidData(Attribute $attribute, $value)
|
|
{
|
|
$key = $attribute->getKey();
|
|
if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) {
|
|
Helper::arraySet($this->invalidData, $key, $value);
|
|
Helper::arrayUnset($this->validData, $key);
|
|
} else {
|
|
$this->invalidData[$key] = $value;
|
|
}
|
|
}
|
|
|
|
public function getInvalidData()
|
|
{
|
|
return $this->invalidData;
|
|
}
|
|
|
|
}
|