<?php
namespace Illuminate\Validation;
use Closure;
use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Exists;
use Illuminate\Validation\Rules\Unique;
class ValidationRuleParser
{
/**
* The data being validated.
*
* @var array
*/
public $data;
/**
* The implicit attributes.
*
* @var array
*/
public $implicitAttributes = [];
/**
* Create a new validation rule parser.
*
* @param array $data
* @return void
*/
public function __construct(array $data)
{
$this->data = $data;
}
/**
* Parse the human-friendly rules into a full rules array for the validator.
*
* @param array $rules
* @return \stdClass
*/
public function explode($rules)
{
$this->implicitAttributes = [];
$rules = $this->explodeRules($rules);
return (object) [
'rules' => $rules,
'implicitAttributes' => $this->implicitAttributes,
];
}
/**
* Explode the rules into an array of explicit rules.
*
* @param array $rules
* @return array
*/
protected function explodeRules($rules)
{
foreach ($rules as $key => $rule) {
if (Str::contains($key, '*')) {
$rules = $this->explodeWildcardRules($rules, $key, [$rule]);
unset($rules[$key]);
} else {
$rules[$key] = $this->explodeExplicitRule($rule);
}
}
return $rules;
}
/**
* Explode the explicit rule into an array if necessary.
*
* @param mixed $rule
* @return array
*/
protected function explodeExplicitRule($rule)
{
if (is_string($rule)) {
return explode('|', $rule);
} elseif (is_object($rule)) {
return [$this->prepareRule($rule)];
}
return array_map([$this, 'prepareRule'], $rule);
}
/**
* Prepare the given rule for the Validator.
*
* @param mixed $rule
* @return mixed
*/
protected function prepareRule($rule)
{
if ($rule instanceof Closure) {
$rule = new ClosureValidationRule($rule);
}
if (! is_object($rule) ||
$rule instanceof RuleContract ||
($rule instanceof Exists && $rule->queryCallbacks()) ||
($rule instanceof Unique && $rule->queryCallbacks())) {
return $rule;
}
return (string) $rule;
}
/**
* Define a set of rules that apply to each element in an array attribute.
*
* @param array $results
* @param string $attribute
* @param string|array $rules
* @return array
*/
protected function explodeWildcardRules($results, $attribute, $rules)
{
$pattern = str_replace('\*', '[^\.]*', preg_quote($attribute));
$data = ValidationData::initializeAndGatherData($attribute, $this->data);
foreach ($data as $key => $value) {
if (Str::startsWith($key, $attribute) || (bool) preg_match('/^'.$pattern.'\z/', $key)) {
foreach ((array) $rules as $rule) {
$this->implicitAttributes[$attribute][] = $key;
$results = $this->mergeRules($results, $key, $rule);
}
}
}
return $results;
}
/**
* Merge additional rules into a given attribute(s).
*
* @param array $results
* @param string|array $attribute
* @param string|array $rules
* @return array
*/
public function mergeRules($results, $attribute, $rules = [])
{
if (is_array($attribute)) {
foreach ((array) $attribute as $innerAttribute => $innerRules) {
$results = $this->mergeRulesForAttribute($results, $innerAttribute, $innerRules);
}
return $results;
}
return $this->mergeRulesForAttribute(
$results, $attribute, $rules
);
}
/**
* Merge additional rules into a given attribute.
*
* @param array $results
* @param string $attribute
* @param string|array $rules
* @return array
*/
protected function mergeRulesForAttribute($results, $attribute, $rules)
{
$merge = head($this->explodeRules([$rules]));
$results[$attribute] = array_merge(
isset($results[$attribute]) ? $this->explodeExplicitRule($results[$attribute]) : [], $merge
);
return $results;
}
/**
* Extract the rule name and parameters from a rule.
*
* @param array|string $rule
* @return array
*/
public static function parse($rule)
{
if ($rule instanceof RuleContract) {
return [$rule, []];
}
if (is_array($rule)) {
$rule = static::parseArrayRule($rule);
} else {
$rule = static::parseStringRule($rule);
}
$rule[0] = static::normalizeRule($rule[0]);
return $rule;
}
/**
* Parse an array based rule.
*
* @param array $rule
* @return array
*/
protected static function parseArrayRule(array $rule)
{
return [Str::studly(trim(Arr::get($rule, 0, ''))), array_slice($rule, 1)];
}
/**
* Parse a string based rule.
*
* @param string $rule
* @return array
*/
protected static function parseStringRule($rule)
{
$parameters = [];
// The format for specifying validation rules and parameters follows an
// easy {rule}:{parameters} formatting convention. For instance the
// rule "Max:3" states that the value may only be three letters.
if (strpos($rule, ':') !== false) {
[$rule, $parameter] = explode(':', $rule, 2);
$parameters = static::parseParameters($rule, $parameter);
}
return [Str::studly(trim($rule)), $parameters];
}
/**
* Parse a parameter list.
*
* @param string $rule
* @param string $parameter
* @return array
*/
protected static function parseParameters($rule, $parameter)
{
$rule = strtolower($rule);
if (in_array($rule, ['regex', 'not_regex', 'notregex'], true)) {
return [$parameter];
}
return str_getcsv($parameter);
}
/**
* Normalizes a rule so that we can accept short types.
*
* @param string $rule
* @return string
*/
protected static function normalizeRule($rule)
{
switch ($rule) {
case 'Int':
return 'Integer';
case 'Bool':
return 'Boolean';
default:
return $rule;
}
}
}