ValidationRuleParser.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <?php
  2. namespace Illuminate\Validation;
  3. use Closure;
  4. use Illuminate\Contracts\Validation\CompilableRules;
  5. use Illuminate\Contracts\Validation\InvokableRule;
  6. use Illuminate\Contracts\Validation\Rule as RuleContract;
  7. use Illuminate\Contracts\Validation\ValidationRule;
  8. use Illuminate\Support\Arr;
  9. use Illuminate\Support\Collection;
  10. use Illuminate\Support\Str;
  11. use Illuminate\Validation\Rules\Date;
  12. use Illuminate\Validation\Rules\Exists;
  13. use Illuminate\Validation\Rules\Numeric;
  14. use Illuminate\Validation\Rules\StringRule;
  15. use Illuminate\Validation\Rules\Unique;
  16. class ValidationRuleParser
  17. {
  18. /**
  19. * The data being validated.
  20. *
  21. * @var array
  22. */
  23. public $data;
  24. /**
  25. * The implicit attributes.
  26. *
  27. * @var array
  28. */
  29. public $implicitAttributes = [];
  30. /**
  31. * Create a new validation rule parser.
  32. *
  33. * @param array $data
  34. */
  35. public function __construct(array $data)
  36. {
  37. $this->data = $data;
  38. }
  39. /**
  40. * Parse the human-friendly rules into a full rules array for the validator.
  41. *
  42. * @param array $rules
  43. * @return \stdClass
  44. */
  45. public function explode($rules)
  46. {
  47. $this->implicitAttributes = [];
  48. $rules = $this->explodeRules($rules);
  49. return (object) [
  50. 'rules' => $rules,
  51. 'implicitAttributes' => $this->implicitAttributes,
  52. ];
  53. }
  54. /**
  55. * Explode the rules into an array of explicit rules.
  56. *
  57. * @param array $rules
  58. * @return array
  59. */
  60. protected function explodeRules($rules)
  61. {
  62. foreach ($rules as $key => $rule) {
  63. if (str_contains($key, '*')) {
  64. $rules = $this->explodeWildcardRules($rules, $key, [$rule]);
  65. unset($rules[$key]);
  66. } else {
  67. $rules[$key] = $this->explodeExplicitRule($rule, $key);
  68. }
  69. }
  70. return $rules;
  71. }
  72. /**
  73. * Explode the explicit rule into an array if necessary.
  74. *
  75. * @param mixed $rule
  76. * @param string $attribute
  77. * @return array
  78. */
  79. protected function explodeExplicitRule($rule, $attribute)
  80. {
  81. if (is_string($rule)) {
  82. return explode('|', $rule);
  83. }
  84. if (is_object($rule)) {
  85. if ($rule instanceof Date || $rule instanceof Numeric || $rule instanceof StringRule) {
  86. return explode('|', (string) $rule);
  87. }
  88. return Arr::wrap($this->prepareRule($rule, $attribute));
  89. }
  90. $rules = [];
  91. foreach ($rule as $value) {
  92. if ($value instanceof Date || $value instanceof Numeric || $value instanceof StringRule) {
  93. $rules = array_merge($rules, explode('|', (string) $value));
  94. } else {
  95. $rules[] = $this->prepareRule($value, $attribute);
  96. }
  97. }
  98. return $rules;
  99. }
  100. /**
  101. * Prepare the given rule for the Validator.
  102. *
  103. * @param mixed $rule
  104. * @param string $attribute
  105. * @return mixed
  106. */
  107. protected function prepareRule($rule, $attribute)
  108. {
  109. if ($rule instanceof Closure) {
  110. $rule = new ClosureValidationRule($rule);
  111. }
  112. if ($rule instanceof InvokableRule || $rule instanceof ValidationRule) {
  113. $rule = InvokableValidationRule::make($rule);
  114. }
  115. if (! is_object($rule) ||
  116. $rule instanceof RuleContract ||
  117. ($rule instanceof Exists && $rule->queryCallbacks()) ||
  118. ($rule instanceof Unique && $rule->queryCallbacks())) {
  119. return $rule;
  120. }
  121. if ($rule instanceof CompilableRules) {
  122. return $rule->compile(
  123. $attribute, $this->data[$attribute] ?? null, Arr::dot($this->data), $this->data
  124. )->rules[$attribute];
  125. }
  126. return (string) $rule;
  127. }
  128. /**
  129. * Define a set of rules that apply to each element in an array attribute.
  130. *
  131. * @param array $results
  132. * @param string $attribute
  133. * @param string|array $rules
  134. * @return array
  135. */
  136. protected function explodeWildcardRules($results, $attribute, $rules)
  137. {
  138. $pattern = str_replace('\*', '[^\.]*', preg_quote($attribute, '/'));
  139. $data = ValidationData::initializeAndGatherData($attribute, $this->data);
  140. foreach ($data as $key => $value) {
  141. if (Str::startsWith($key, $attribute) || (bool) preg_match('/^'.$pattern.'\z/', $key)) {
  142. foreach ((array) $rules as $rule) {
  143. if ($rule instanceof CompilableRules) {
  144. $context = Arr::get($this->data, Str::beforeLast($key, '.'));
  145. $compiled = $rule->compile($key, $value, $data, $context);
  146. $this->implicitAttributes = array_merge_recursive(
  147. $compiled->implicitAttributes,
  148. $this->implicitAttributes,
  149. [$attribute => [$key]]
  150. );
  151. $results = $this->mergeRules($results, $compiled->rules);
  152. } else {
  153. $this->implicitAttributes[$attribute][] = $key;
  154. $results = $this->mergeRules($results, $key, $rule);
  155. }
  156. }
  157. }
  158. }
  159. return $results;
  160. }
  161. /**
  162. * Merge additional rules into a given attribute(s).
  163. *
  164. * @param array $results
  165. * @param string|array $attribute
  166. * @param string|array $rules
  167. * @return array
  168. */
  169. public function mergeRules($results, $attribute, $rules = [])
  170. {
  171. if (is_array($attribute)) {
  172. foreach ((array) $attribute as $innerAttribute => $innerRules) {
  173. $results = $this->mergeRulesForAttribute($results, $innerAttribute, $innerRules);
  174. }
  175. return $results;
  176. }
  177. return $this->mergeRulesForAttribute(
  178. $results, $attribute, $rules
  179. );
  180. }
  181. /**
  182. * Merge additional rules into a given attribute.
  183. *
  184. * @param array $results
  185. * @param string $attribute
  186. * @param string|array $rules
  187. * @return array
  188. */
  189. protected function mergeRulesForAttribute($results, $attribute, $rules)
  190. {
  191. $merge = head($this->explodeRules([$rules]));
  192. $results[$attribute] = array_merge(
  193. isset($results[$attribute]) ? $this->explodeExplicitRule($results[$attribute], $attribute) : [], $merge
  194. );
  195. return $results;
  196. }
  197. /**
  198. * Extract the rule name and parameters from a rule.
  199. *
  200. * @param array|string $rule
  201. * @return array
  202. */
  203. public static function parse($rule)
  204. {
  205. if ($rule instanceof RuleContract || $rule instanceof CompilableRules) {
  206. return [$rule, []];
  207. }
  208. if (is_array($rule)) {
  209. $rule = static::parseArrayRule($rule);
  210. } else {
  211. $rule = static::parseStringRule($rule);
  212. }
  213. $rule[0] = static::normalizeRule($rule[0]);
  214. return $rule;
  215. }
  216. /**
  217. * Parse an array based rule.
  218. *
  219. * @param array $rule
  220. * @return array
  221. */
  222. protected static function parseArrayRule(array $rule)
  223. {
  224. return [Str::studly(trim(Arr::get($rule, 0, ''))), array_slice($rule, 1)];
  225. }
  226. /**
  227. * Parse a string based rule.
  228. *
  229. * @param string $rule
  230. * @return array
  231. */
  232. protected static function parseStringRule($rule)
  233. {
  234. $parameters = [];
  235. // The format for specifying validation rules and parameters follows an
  236. // easy {rule}:{parameters} formatting convention. For instance the
  237. // rule "Max:3" states that the value may only be three letters.
  238. if (str_contains($rule, ':')) {
  239. [$rule, $parameter] = explode(':', $rule, 2);
  240. $parameters = static::parseParameters($rule, $parameter);
  241. }
  242. return [Str::studly(trim($rule)), $parameters];
  243. }
  244. /**
  245. * Parse a parameter list.
  246. *
  247. * @param string $rule
  248. * @param string $parameter
  249. * @return array
  250. */
  251. protected static function parseParameters($rule, $parameter)
  252. {
  253. return static::ruleIsRegex($rule) ? [$parameter] : str_getcsv($parameter, escape: '\\');
  254. }
  255. /**
  256. * Determine if the rule is a regular expression.
  257. *
  258. * @param string $rule
  259. * @return bool
  260. */
  261. protected static function ruleIsRegex($rule)
  262. {
  263. return in_array(strtolower($rule), ['regex', 'not_regex', 'notregex'], true);
  264. }
  265. /**
  266. * Normalizes a rule so that we can accept short types.
  267. *
  268. * @param string $rule
  269. * @return string
  270. */
  271. protected static function normalizeRule($rule)
  272. {
  273. return match ($rule) {
  274. 'Int' => 'Integer',
  275. 'Bool' => 'Boolean',
  276. default => $rule,
  277. };
  278. }
  279. /**
  280. * Expand the conditional rules in the given array of rules.
  281. *
  282. * @param array $rules
  283. * @param array $data
  284. * @return array
  285. */
  286. public static function filterConditionalRules($rules, array $data = [])
  287. {
  288. return (new Collection($rules))->mapWithKeys(function ($attributeRules, $attribute) use ($data) {
  289. if (! is_array($attributeRules) &&
  290. ! $attributeRules instanceof ConditionalRules) {
  291. return [$attribute => $attributeRules];
  292. }
  293. if ($attributeRules instanceof ConditionalRules) {
  294. return [$attribute => $attributeRules->passes($data)
  295. ? array_filter($attributeRules->rules($data))
  296. : array_filter($attributeRules->defaultRules($data)), ];
  297. }
  298. return [$attribute => (new Collection($attributeRules))->map(function ($rule) use ($data) {
  299. if (! $rule instanceof ConditionalRules) {
  300. return [$rule];
  301. }
  302. return $rule->passes($data) ? $rule->rules($data) : $rule->defaultRules($data);
  303. })->filter()->flatten(1)->values()->all()];
  304. })->all();
  305. }
  306. }