├── .gitignore ├── .php_cs ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Factory.php ├── MessageBag.php ├── PresenceVerifierInterface.php ├── Translator.php ├── TranslatorInterface.php ├── Validator.php └── helpers.php └── tests └── ValidatorTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | /vendor 3 | sftp-config.json 4 | /*.php 5 | /.idea 6 | /coverage 7 | /.split 8 | /composer.lock 9 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | 7 | 8 | This source file is subject to the MIT license that is bundled 9 | with this source code in the file LICENSE. 10 | EOF; 11 | 12 | Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($header); 13 | 14 | return Symfony\CS\Config\Config::create() 15 | // use default SYMFONY_LEVEL and extra fixers: 16 | ->fixers(array( 17 | 'header_comment', 18 | 'short_array_syntax', 19 | 'ordered_use', 20 | 'php_unit_construct', 21 | 'strict_param', 22 | )) 23 | ->finder( 24 | Symfony\CS\Finder\DefaultFinder::create() 25 | ->in(__DIR__.'/src') 26 | ) 27 | ; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Original work Copyright (c) 2016 Taylor Otwell 4 | Modified work Copyright (c) 2016 overtrue 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Validation 2 | ========== 3 | 4 | Validation 是从Laravel的验证模块提取简化而来,旨在让你更方便的在非laravel项目中便捷的完成数据验证。 5 | 6 | 更多验证规则请阅读:http://laravel.com/docs/4.2/validation#available-validation-rules 7 | 8 | # Usage 9 | 10 | ```php 11 | 'required|min:5', 23 | 'password' => 'confirmed', 24 | ///... 25 | ]; 26 | 27 | $validator = $factory->make($input, $rules); 28 | 29 | //判断验证是否通过 30 | if ($validator->passes()) { 31 | //通过 32 | } else { 33 | //未通过 34 | //输出错误消息 35 | print_r($validator->messages()->all()); // 或者 $validator->errors(); 36 | } 37 | 38 | ``` 39 | 40 | ## 自定义消息语言: 41 | 42 | > 语言列表可以从这里拿:https://github.com/caouecs/Laravel-lang 43 | 44 | 以中文为例: 45 | 46 | ```php 47 | $messages = [ 48 | 'accepted' => ':attribute 必须接受。', 49 | 'active_url' => ':attribute 不是一个有效的网址。', 50 | 'after' => ':attribute 必须是一个在 :date 之后的日期。', 51 | 'alpha' => ':attribute 只能由字母组成。', 52 | 'alpha_dash' => ':attribute 只能由字母、数字和斜杠组成。', 53 | 'alpha_num' => ':attribute 只能由字母和数字组成。', 54 | // ... 55 | ]; 56 | 57 | //初始化工厂对象 58 | $factory = new ValidatorFactory(new Translator($messages)); 59 | 60 | ``` 61 | 62 | ## 设置属性名称 63 | 64 | ```php 65 | $attributes = [ 66 | 'username' => '用户名', 67 | 'password' => '密码', 68 | ]; 69 | 70 | $rules = [ 71 | 'username' => 'required|min:5', 72 | 'password' => 'confirmed', 73 | ///... 74 | ]; 75 | 76 | $messages = [...]; // 自定义消息,如果你在初始化 factory 的时候已经设置了消息,则留空即可 77 | 78 | $validator = $factory->make($input, $rules, $messages, $attributes); 79 | ``` 80 | 81 | ## PHP 扩展包开发 82 | 83 | > 想知道如何从零开始构建 PHP 扩展包? 84 | > 85 | > 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) 86 | 87 | # License 88 | 89 | MIT 90 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overtrue/validation", 3 | "description": "Laralve Validation 简化无依赖版", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Carlos", 8 | "email": "anzhengchao@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.4.0" 13 | }, 14 | 15 | "autoload": { 16 | "psr-4": { 17 | "Overtrue\\Validation\\": "src/" 18 | }, 19 | "files":["src/helpers.php"] 20 | }, 21 | 22 | "minimum-stability": "dev", 23 | "require-dev": { 24 | "phpunit/phpunit": "^5.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Validation; 13 | 14 | use Closure; 15 | 16 | class Factory 17 | { 18 | /** 19 | * The Translator implementation. 20 | * 21 | * @var \Overtrue\Validation\TranslatorInterface 22 | */ 23 | protected $translator; 24 | 25 | /** 26 | * The Presence Verifier implementation. 27 | * 28 | * @var \Overtrue\Validation\PresenceVerifierInterface 29 | */ 30 | protected $verifier; 31 | 32 | /** 33 | * All of the custom validator extensions. 34 | * 35 | * @var array 36 | */ 37 | protected $extensions = []; 38 | 39 | /** 40 | * All of the custom implicit validator extensions. 41 | * 42 | * @var array 43 | */ 44 | protected $implicitExtensions = []; 45 | 46 | /** 47 | * All of the custom validator message replacers. 48 | * 49 | * @var array 50 | */ 51 | protected $replacers = []; 52 | 53 | /** 54 | * All of the fallback messages for custom rules. 55 | * 56 | * @var array 57 | */ 58 | protected $fallbackMessages = []; 59 | 60 | /** 61 | * The Validator resolver instance. 62 | * 63 | * @var Closure 64 | */ 65 | protected $resolver; 66 | 67 | /** 68 | * Create a new Validator factory instance. 69 | * 70 | * @param \Overtrue\Validation\TranslatorInterface $translator 71 | * 72 | * @return \Overtrue\Validation\Factory 73 | */ 74 | public function __construct(TranslatorInterface $translator = null) 75 | { 76 | $this->translator = $translator ?: new Translator(); 77 | } 78 | 79 | /** 80 | * Create a new Validator instance. 81 | * 82 | * @param array $data 83 | * @param array $rules 84 | * @param array $messages 85 | * @param array $customAttributes 86 | * 87 | * @return \Overtrue\Validation\Validator 88 | */ 89 | public function make(array $data, array $rules, array $messages = [], array $customAttributes = []) 90 | { 91 | // The presence verifier is responsible for checking the unique and exists data 92 | // for the validator. It is behind an interface so that multiple versions of 93 | // it may be written besides database. We'll inject it into the validator. 94 | $validator = $this->resolve($data, $rules, $messages, $customAttributes); 95 | 96 | if (!is_null($this->verifier)) { 97 | $validator->setPresenceVerifier($this->verifier); 98 | } 99 | 100 | $this->addExtensions($validator); 101 | 102 | return $validator; 103 | } 104 | 105 | /** 106 | * Add the extensions to a validator instance. 107 | * 108 | * @param \Overtrue\Validation\Validator $validator 109 | */ 110 | protected function addExtensions(Validator $validator) 111 | { 112 | $validator->addExtensions($this->extensions); 113 | 114 | // Next, we will add the implicit extensions, which are similar to the required 115 | // and accepted rule in that they are run even if the attributes is not in a 116 | // array of data that is given to a validator instances via instantiation. 117 | $implicit = $this->implicitExtensions; 118 | 119 | $validator->addImplicitExtensions($implicit); 120 | 121 | $validator->addReplacers($this->replacers); 122 | 123 | $validator->setFallbackMessages($this->fallbackMessages); 124 | } 125 | 126 | /** 127 | * Resolve a new Validator instance. 128 | * 129 | * @param array $data 130 | * @param array $rules 131 | * @param array $messages 132 | * @param array $customAttributes 133 | * 134 | * @return \Overtrue\Validation\Validator 135 | */ 136 | protected function resolve(array $data, array $rules, array $messages, array $customAttributes) 137 | { 138 | if (is_null($this->resolver)) { 139 | return new Validator($this->translator, $data, $rules, $messages, $customAttributes); 140 | } else { 141 | return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes); 142 | } 143 | } 144 | 145 | /** 146 | * Register a custom validator extension. 147 | * 148 | * @param string $rule 149 | * @param \Closure|string $extension 150 | * @param string $message 151 | */ 152 | public function extend($rule, $extension, $message = null) 153 | { 154 | $this->extensions[$rule] = $extension; 155 | 156 | if ($message) { 157 | $this->fallbackMessages[snake_case($rule)] = $message; 158 | } 159 | } 160 | 161 | /** 162 | * Register a custom implicit validator extension. 163 | * 164 | * @param string $rule 165 | * @param \Closure|string $extension 166 | * @param string $message 167 | */ 168 | public function extendImplicit($rule, $extension, $message = null) 169 | { 170 | $this->implicitExtensions[$rule] = $extension; 171 | 172 | if ($message) { 173 | $this->fallbackMessages[snake_case($rule)] = $message; 174 | } 175 | } 176 | 177 | /** 178 | * Register a custom implicit validator message replacer. 179 | * 180 | * @param string $rule 181 | * @param \Closure|string $replacer 182 | */ 183 | public function replacer($rule, $replacer) 184 | { 185 | $this->replacers[$rule] = $replacer; 186 | } 187 | 188 | /** 189 | * Set the Validator instance resolver. 190 | * 191 | * @param \Closure $resolver 192 | */ 193 | public function resolver(Closure $resolver) 194 | { 195 | $this->resolver = $resolver; 196 | } 197 | 198 | /** 199 | * Get the Translator implementation. 200 | * 201 | * @return \Overtrue\Validation\TranslatorInterface 202 | */ 203 | public function getTranslator() 204 | { 205 | return $this->translator; 206 | } 207 | 208 | /** 209 | * Get the Presence Verifier implementation. 210 | * 211 | * @return \Overtrue\Validation\PresenceVerifierInterface 212 | */ 213 | public function getPresenceVerifier() 214 | { 215 | return $this->verifier; 216 | } 217 | 218 | /** 219 | * Set the Presence Verifier implementation. 220 | * 221 | * @param \Overtrue\Validation\PresenceVerifierInterface $presenceVerifier 222 | */ 223 | public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier) 224 | { 225 | $this->verifier = $presenceVerifier; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/MessageBag.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Validation; 13 | 14 | use Countable; 15 | use JsonSerializable; 16 | 17 | class MessageBag implements Countable, JsonSerializable 18 | { 19 | /** 20 | * All of the registered messages. 21 | * 22 | * @var array 23 | */ 24 | protected $messages = []; 25 | 26 | /** 27 | * Default format for message output. 28 | * 29 | * @var string 30 | */ 31 | protected $format = ':message'; 32 | 33 | /** 34 | * Create a new message bag instance. 35 | * 36 | * @param array $messages 37 | * 38 | * @return \Overtrue\Validation\MessageBag 39 | */ 40 | public function __construct(array $messages = []) 41 | { 42 | foreach ($messages as $key => $value) { 43 | $this->messages[$key] = (array) $value; 44 | } 45 | } 46 | 47 | /** 48 | * Add a message to the bag. 49 | * 50 | * @param string $key 51 | * @param string $message 52 | * 53 | * @return $this 54 | */ 55 | public function add($key, $message) 56 | { 57 | if ($this->isUnique($key, $message)) { 58 | $this->messages[$key][] = $message; 59 | } 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Merge a new array of messages into the bag. 66 | * 67 | * @param array $messages 68 | * 69 | * @return $this 70 | */ 71 | public function merge($messages) 72 | { 73 | $this->messages = array_merge_recursive($this->messages, $messages); 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * Determine if a key and message combination already exists. 80 | * 81 | * @param string $key 82 | * @param string $message 83 | * 84 | * @return bool 85 | */ 86 | protected function isUnique($key, $message) 87 | { 88 | $messages = (array) $this->messages; 89 | 90 | return !isset($messages[$key]) || !in_array($message, $messages[$key], true); 91 | } 92 | 93 | /** 94 | * Determine if messages exist for a given key. 95 | * 96 | * @param string $key 97 | * 98 | * @return bool 99 | */ 100 | public function has($key = null) 101 | { 102 | return $this->first($key) !== ''; 103 | } 104 | 105 | /** 106 | * Get the first message from the bag for a given key. 107 | * 108 | * @param string $key 109 | * @param string $format 110 | * 111 | * @return string 112 | */ 113 | public function first($key = null, $format = null) 114 | { 115 | $messages = is_null($key) ? $this->all($format) : $this->get($key, $format); 116 | 117 | return (count($messages) > 0) ? $messages[0] : ''; 118 | } 119 | 120 | /** 121 | * Get all of the messages from the bag for a given key. 122 | * 123 | * @param string $key 124 | * @param string $format 125 | * 126 | * @return array 127 | */ 128 | public function get($key, $format = null) 129 | { 130 | $format = $this->checkFormat($format); 131 | 132 | // If the message exists in the container, we will transform it and return 133 | // the message. Otherwise, we'll return an empty array since the entire 134 | // methods is to return back an array of messages in the first place. 135 | if (array_key_exists($key, $this->messages)) { 136 | return $this->transform($this->messages[$key], $format, $key); 137 | } 138 | 139 | return []; 140 | } 141 | 142 | /** 143 | * Get all of the messages for every key in the bag. 144 | * 145 | * @param string $format 146 | * 147 | * @return array 148 | */ 149 | public function all($format = null) 150 | { 151 | $format = $this->checkFormat($format); 152 | 153 | $all = []; 154 | 155 | foreach ($this->messages as $key => $messages) { 156 | $all = array_merge($all, $this->transform($messages, $format, $key)); 157 | } 158 | 159 | return $all; 160 | } 161 | 162 | /** 163 | * Format an array of messages. 164 | * 165 | * @param array $messages 166 | * @param string $format 167 | * @param string $messageKey 168 | * 169 | * @return array 170 | */ 171 | protected function transform($messages, $format, $messageKey) 172 | { 173 | $messages = (array) $messages; 174 | 175 | // We will simply spin through the given messages and transform each one 176 | // replacing the :message place holder with the real message allowing 177 | // the messages to be easily formatted to each developer's desires. 178 | foreach ($messages as $key => &$message) { 179 | $replace = [':message', ':key']; 180 | 181 | $message = str_replace($replace, [$message, $messageKey], $format); 182 | } 183 | 184 | return $messages; 185 | } 186 | 187 | /** 188 | * Get the appropriate format based on the given format. 189 | * 190 | * @param string $format 191 | * 192 | * @return string 193 | */ 194 | protected function checkFormat($format) 195 | { 196 | return ($format === null) ? $this->format : $format; 197 | } 198 | 199 | /** 200 | * Get the raw messages in the container. 201 | * 202 | * @return array 203 | */ 204 | public function getMessages() 205 | { 206 | return $this->messages; 207 | } 208 | 209 | /** 210 | * Get the messages for the instance. 211 | * 212 | * @return \Overtrue\Validation\MessageBag 213 | */ 214 | public function getMessageBag() 215 | { 216 | return $this; 217 | } 218 | 219 | /** 220 | * Get the default message format. 221 | * 222 | * @return string 223 | */ 224 | public function getFormat() 225 | { 226 | return $this->format; 227 | } 228 | 229 | /** 230 | * Set the default message format. 231 | * 232 | * @param string $format 233 | * 234 | * @return \Overtrue\Validation\MessageBag 235 | */ 236 | public function setFormat($format = ':message') 237 | { 238 | $this->format = $format; 239 | 240 | return $this; 241 | } 242 | 243 | /** 244 | * Determine if the message bag has any messages. 245 | * 246 | * @return bool 247 | */ 248 | public function isEmpty() 249 | { 250 | return !$this->any(); 251 | } 252 | 253 | /** 254 | * Determine if the message bag has any messages. 255 | * 256 | * @return bool 257 | */ 258 | public function any() 259 | { 260 | return $this->count() > 0; 261 | } 262 | 263 | /** 264 | * Get the number of messages in the container. 265 | * 266 | * @return int 267 | */ 268 | public function count() 269 | { 270 | return count($this->messages, COUNT_RECURSIVE) - count($this->messages); 271 | } 272 | 273 | /** 274 | * Get the instance as an array. 275 | * 276 | * @return array 277 | */ 278 | public function toArray() 279 | { 280 | return $this->getMessages(); 281 | } 282 | 283 | /** 284 | * Convert the object into something JSON serializable. 285 | * 286 | * @return array 287 | */ 288 | public function jsonSerialize() 289 | { 290 | return $this->toArray(); 291 | } 292 | 293 | /** 294 | * Convert the object to its JSON representation. 295 | * 296 | * @param int $options 297 | * 298 | * @return string 299 | */ 300 | public function toJson($options = 0) 301 | { 302 | return json_encode($this->toArray(), $options); 303 | } 304 | 305 | /** 306 | * Convert the message bag to its string representation. 307 | * 308 | * @return string 309 | */ 310 | public function __toString() 311 | { 312 | return $this->toJson(); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/PresenceVerifierInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Validation; 13 | 14 | interface PresenceVerifierInterface 15 | { 16 | /** 17 | * Count the number of objects in a collection having the given value. 18 | * 19 | * @param string $collection 20 | * @param string $column 21 | * @param string $value 22 | * @param int $excludeId 23 | * @param string $idColumn 24 | * @param array $extra 25 | * 26 | * @return int 27 | */ 28 | public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = []); 29 | 30 | /** 31 | * Count the number of objects in a collection with the given values. 32 | * 33 | * @param string $collection 34 | * @param string $column 35 | * @param array $values 36 | * @param array $extra 37 | * 38 | * @return int 39 | */ 40 | public function getMultiCount($collection, $column, array $values, array $extra = []); 41 | } 42 | -------------------------------------------------------------------------------- /src/Translator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Validation; 13 | 14 | /** 15 | * 验证类使用的默认消息翻译工具. 16 | */ 17 | class Translator implements TranslatorInterface 18 | { 19 | protected $messages = []; 20 | 21 | /** 22 | * 默认消息. 23 | * 24 | * @var array 25 | */ 26 | protected $defaultMessages = [ 27 | 'accepted' => 'The :attribute must be accepted.', 28 | 'active_url' => 'The :attribute is not a valid URL.', 29 | 'after' => 'The :attribute must be a date after :date.', 30 | 'alpha' => 'The :attribute may only contain letters.', 31 | 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', 32 | 'alpha_num' => 'The :attribute may only contain letters and numbers.', 33 | 'array' => 'The :attribute must be an array.', 34 | 'before' => 'The :attribute must be a date before :date.', 35 | 'between' => [ 36 | 'numeric' => 'The :attribute must be between :min and :max.', 37 | 'file' => 'The :attribute must be between :min and :max kilobytes.', 38 | 'string' => 'The :attribute must be between :min and :max characters.', 39 | 'array' => 'The :attribute must have between :min and :max items.', 40 | ], 41 | 'boolean' => 'The :attribute field must be true or false', 42 | 'confirmed' => 'The :attribute confirmation does not match.', 43 | 'date' => 'The :attribute is not a valid date.', 44 | 'date_format' => 'The :attribute does not match the format :format.', 45 | 'different' => 'The :attribute and :other must be different.', 46 | 'digits' => 'The :attribute must be :digits digits.', 47 | 'digits_between' => 'The :attribute must be between :min and :max digits.', 48 | 'email' => 'The :attribute must be a valid email address.', 49 | 'filled' => 'The :attribute field is required.', 50 | 'exists' => 'The selected :attribute is invalid.', 51 | 'image' => 'The :attribute must be an image.', 52 | 'in' => 'The selected :attribute is invalid.', 53 | 'integer' => 'The :attribute must be an integer.', 54 | 'ip' => 'The :attribute must be a valid IP address.', 55 | 'max' => [ 56 | 'numeric' => 'The :attribute may not be greater than :max.', 57 | 'file' => 'The :attribute may not be greater than :max kilobytes.', 58 | 'string' => 'The :attribute may not be greater than :max characters.', 59 | 'array' => 'The :attribute may not have more than :max items.', 60 | ], 61 | 'mimes' => 'The :attribute must be a file of type: :values.', 62 | 'min' => [ 63 | 'numeric' => 'The :attribute must be at least :min.', 64 | 'file' => 'The :attribute must be at least :min kilobytes.', 65 | 'string' => 'The :attribute must be at least :min characters.', 66 | 'array' => 'The :attribute must have at least :min items.', 67 | ], 68 | 'not_in' => 'The selected :attribute is invalid.', 69 | 'numeric' => 'The :attribute must be a number.', 70 | 'regex' => 'The :attribute format is invalid.', 71 | 'required' => 'The :attribute field is required.', 72 | 'required_if' => 'The :attribute field is required when :other is :value.', 73 | 'required_with' => 'The :attribute field is required when :values is present.', 74 | 'required_with_all' => 'The :attribute field is required when :values is present.', 75 | 'required_without' => 'The :attribute field is required when :values is not present.', 76 | 'required_without_all' => 'The :attribute field is required when none of :values are present.', 77 | 'same' => 'The :attribute and :other must match.', 78 | 'size' => [ 79 | 'numeric' => 'The :attribute must be :size.', 80 | 'file' => 'The :attribute must be :size kilobytes.', 81 | 'string' => 'The :attribute must be :size characters.', 82 | 'array' => 'The :attribute must contain :size items.', 83 | ], 84 | 'unique' => 'The :attribute has already been taken.', 85 | 'url' => 'The :attribute format is invalid.', 86 | 'timezone' => 'The :attribute must be a valid zone.', 87 | ]; 88 | 89 | /** 90 | * 设置验证消息列表. 91 | * 92 | * @param $messages 93 | */ 94 | public function __construct(array $messages = []) 95 | { 96 | $this->messages = ['validation' => array_merge($this->defaultMessages, $messages)]; 97 | } 98 | 99 | /** 100 | * 翻译. 101 | * 102 | * @param string $key 点拼接的key 103 | * 104 | * @return string 105 | */ 106 | public function trans($key) 107 | { 108 | return $this->arrayGet($this->messages, $key, $key); 109 | } 110 | 111 | /** 112 | * 使用点字符串获取. 113 | * 114 | * @param array $array 115 | * @param string $key 116 | * @param mixed $default 117 | * 118 | * @return mixed 119 | */ 120 | protected function arrayGet($array, $key, $default = null) 121 | { 122 | if (is_null($key)) { 123 | return $array; 124 | } 125 | 126 | if (isset($array[$key])) { 127 | return $array[$key]; 128 | } 129 | 130 | foreach (explode('.', $key) as $segment) { 131 | if (!is_array($array) || !array_key_exists($segment, $array)) { 132 | return value($default); 133 | } 134 | 135 | $array = $array[$segment]; 136 | } 137 | 138 | return $array; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/TranslatorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Validation; 13 | 14 | interface TranslatorInterface 15 | { 16 | /** 17 | * translator. 18 | * 19 | * @param $key message key. 20 | * 21 | * @return string 22 | */ 23 | public function trans($key); 24 | } 25 | -------------------------------------------------------------------------------- /src/Validator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Validation; 13 | 14 | use Closure; 15 | use DateTime; 16 | use DateTimeZone; 17 | 18 | class Validator 19 | { 20 | /** 21 | * The Translator implementation. 22 | * 23 | * @var \Overtrue\Validation\TranslatorInterface 24 | */ 25 | protected $translator; 26 | 27 | /** 28 | * The Presence Verifier implementation. 29 | * 30 | * @var \Overtrue\Validation\PresenceVerifierInterface 31 | */ 32 | protected $presenceVerifier; 33 | 34 | /** 35 | * The failed validation rules. 36 | * 37 | * @var array 38 | */ 39 | protected $failedRules = []; 40 | 41 | /** 42 | * The messages. 43 | * 44 | * @var \Overtrue\Validation\MessageBag 45 | */ 46 | protected $messages; 47 | 48 | /** 49 | * The data under validation. 50 | * 51 | * @var array 52 | */ 53 | protected $data; 54 | 55 | /** 56 | * The files under validation. 57 | * 58 | * @var array 59 | */ 60 | protected $files = []; 61 | 62 | /** 63 | * The rules to be applied to the data. 64 | * 65 | * @var array 66 | */ 67 | protected $rules; 68 | 69 | /** 70 | * All of the registered "after" callbacks. 71 | * 72 | * @var array 73 | */ 74 | protected $after = []; 75 | 76 | /** 77 | * The array of custom error messages. 78 | * 79 | * @var array 80 | */ 81 | protected $customMessages = []; 82 | 83 | /** 84 | * The array of fallback error messages. 85 | * 86 | * @var array 87 | */ 88 | protected $fallbackMessages = []; 89 | 90 | /** 91 | * The array of custom attribute names. 92 | * 93 | * @var array 94 | */ 95 | protected $customAttributes = []; 96 | 97 | /** 98 | * The array of custom displayabled values. 99 | * 100 | * @var array 101 | */ 102 | protected $customValues = []; 103 | 104 | /** 105 | * All of the custom validator extensions. 106 | * 107 | * @var array 108 | */ 109 | protected $extensions = []; 110 | 111 | /** 112 | * All of the custom replacer extensions. 113 | * 114 | * @var array 115 | */ 116 | protected $replacers = []; 117 | 118 | /** 119 | * The size related validation rules. 120 | * 121 | * @var array 122 | */ 123 | protected $sizeRules = ['Size', 'Between', 'Min', 'Max']; 124 | 125 | /** 126 | * The numeric related validation rules. 127 | * 128 | * @var array 129 | */ 130 | protected $numericRules = ['Numeric', 'Integer']; 131 | 132 | /** 133 | * The validation rules that imply the field is required. 134 | * 135 | * @var array 136 | */ 137 | protected $implicitRules = [ 138 | 'Required', 'Filled', 'RequiredWith', 'RequiredWithAll', 'RequiredWithout', 'RequiredWithoutAll', 'RequiredIf', 'Accepted', 139 | ]; 140 | 141 | /** 142 | * Create a new Validator instance. 143 | * 144 | * @param \Overtrue\Validation\TranslatorInterface $translator 145 | * @param array $data 146 | * @param array $rules 147 | * @param array $messages 148 | * @param array $customAttributes 149 | * 150 | * @return \Overtrue\Validation\Validator 151 | */ 152 | public function __construct(TranslatorInterface $translator, array $data, array $rules, array $messages = [], array $customAttributes = []) 153 | { 154 | $this->translator = $translator; 155 | $this->customMessages = $messages; 156 | $this->data = $this->parseData($data); 157 | $this->rules = $this->explodeRules($rules); 158 | $this->customAttributes = $customAttributes; 159 | } 160 | 161 | /** 162 | * Parse the data and hydrate the files array. 163 | * 164 | * @param array $data 165 | * 166 | * @return array 167 | */ 168 | protected function parseData(array $data) 169 | { 170 | $this->files = []; 171 | 172 | foreach ($data as $key => $value) { 173 | // If this value is an instance of the HttpFoundation File class we will 174 | // remove it from the data array and add it to the files array, which 175 | // we use to conveniently separate out these files from other data. 176 | if (in_array($value, $_FILES, true)) { 177 | $this->files[$key] = $value; 178 | 179 | unset($data[$key]); 180 | } 181 | } 182 | 183 | return $data; 184 | } 185 | 186 | /** 187 | * Explode the rules into an array of rules. 188 | * 189 | * @param string|array $rules 190 | * 191 | * @return array 192 | */ 193 | protected function explodeRules($rules) 194 | { 195 | foreach ($rules as $key => &$rule) { 196 | $rule = (is_string($rule)) ? explode('|', $rule) : $rule; 197 | } 198 | 199 | return $rules; 200 | } 201 | 202 | /** 203 | * After an after validation callback. 204 | * 205 | * @param callable|string $callback 206 | * 207 | * @return $this 208 | */ 209 | public function after($callback) 210 | { 211 | $this->after[] = function () use ($callback) { 212 | return call_user_func($callback, [], 'validate'); 213 | }; 214 | 215 | return $this; 216 | } 217 | 218 | /** 219 | * Add conditions to a given field based on a Closure. 220 | * 221 | * @param string $attribute 222 | * @param string|array $rules 223 | * @param callable $callback 224 | */ 225 | public function sometimes($attribute, $rules, callable $callback) 226 | { 227 | $payload = array_merge($this->data, $this->files); 228 | 229 | if (call_user_func($callback, $payload)) { 230 | foreach ((array) $attribute as $key) { 231 | $this->mergeRules($key, $rules); 232 | } 233 | } 234 | } 235 | 236 | /** 237 | * Define a set of rules that apply to each element in an array attribute. 238 | * 239 | * @param string $attribute 240 | * @param string|array $rules 241 | * 242 | * @throws \InvalidArgumentException 243 | */ 244 | public function each($attribute, $rules) 245 | { 246 | $data = array_get($this->data, $attribute); 247 | 248 | if (!is_array($data)) { 249 | if ($this->hasRule($attribute, 'Array')) { 250 | return; 251 | } 252 | 253 | throw new \InvalidArgumentException('Attribute for each() must be an array.'); 254 | } 255 | 256 | foreach ($data as $dataKey => $dataValue) { 257 | foreach ($rules as $ruleValue) { 258 | $this->mergeRules("$attribute.$dataKey", $ruleValue); 259 | } 260 | } 261 | } 262 | 263 | /** 264 | * Merge additional rules into a given attribute. 265 | * 266 | * @param string $attribute 267 | * @param string|array $rules 268 | */ 269 | public function mergeRules($attribute, $rules) 270 | { 271 | $current = isset($this->rules[$attribute]) ? $this->rules[$attribute] : []; 272 | 273 | $merge = head($this->explodeRules([$rules])); 274 | 275 | $this->rules[$attribute] = array_merge($current, $merge); 276 | } 277 | 278 | /** 279 | * Determine if the data passes the validation rules. 280 | * 281 | * 282 | * @return bool 283 | */ 284 | public function passes() 285 | { 286 | $this->messages = new MessageBag(); 287 | 288 | // We'll spin through each rule, validating the attributes attached to that 289 | // rule. Any error messages will be added to the containers with each of 290 | // the other error messages, returning true if we don't have messages. 291 | foreach ($this->rules as $attribute => $rules) { 292 | foreach ($rules as $rule) { 293 | $this->validate($attribute, $rule); 294 | } 295 | } 296 | 297 | // Here we will spin through all of the "after" hooks on this validator and 298 | // fire them off. This gives the callbacks a chance to perform all kinds 299 | // of other validation that needs to get wrapped up in this operation. 300 | foreach ($this->after as $after) { 301 | call_user_func($after); 302 | } 303 | 304 | return count($this->messages->all()) === 0; 305 | } 306 | 307 | /** 308 | * Determine if the data fails the validation rules. 309 | * 310 | * 311 | * @return bool 312 | */ 313 | public function fails() 314 | { 315 | return !$this->passes(); 316 | } 317 | 318 | /** 319 | * Validate a given attribute against a rule. 320 | * 321 | * @param string $attribute 322 | * @param string $rule 323 | */ 324 | protected function validate($attribute, $rule) 325 | { 326 | list($rule, $parameters) = $this->parseRule($rule); 327 | 328 | if ($rule === '') { 329 | return; 330 | } 331 | 332 | // We will get the value for the given attribute from the array of data and then 333 | // verify that the attribute is indeed validatable. Unless the rule implies 334 | // that the attribute is required, rules are not run for missing values. 335 | $value = $this->getValue($attribute); 336 | 337 | $validatable = $this->isValidatable($rule, $attribute, $value); 338 | 339 | $method = "validate{$rule}"; 340 | 341 | if ($validatable && !$this->$method($attribute, $value, $parameters, $this)) { 342 | $this->addFailure($attribute, $rule, $parameters); 343 | } 344 | } 345 | 346 | /** 347 | * Get the value of a given attribute. 348 | * 349 | * @param string $attribute 350 | * 351 | * @return mixed 352 | */ 353 | protected function getValue($attribute) 354 | { 355 | if (!is_null($value = array_get($this->data, $attribute))) { 356 | return $value; 357 | } elseif (!is_null($value = array_get($this->files, $attribute))) { 358 | return $value; 359 | } 360 | } 361 | 362 | /** 363 | * Determine if the attribute is validatable. 364 | * 365 | * @param string $rule 366 | * @param string $attribute 367 | * @param mixed $value 368 | * 369 | * @return bool 370 | */ 371 | protected function isValidatable($rule, $attribute, $value) 372 | { 373 | return $this->presentOrRuleIsImplicit($rule, $attribute, $value) && 374 | $this->passesOptionalCheck($attribute); 375 | } 376 | 377 | /** 378 | * Determine if the field is present, or the rule implies required. 379 | * 380 | * @param string $rule 381 | * @param string $attribute 382 | * @param mixed $value 383 | * 384 | * @return bool 385 | */ 386 | protected function presentOrRuleIsImplicit($rule, $attribute, $value) 387 | { 388 | return $this->validateRequired($attribute, $value) || $this->isImplicit($rule); 389 | } 390 | 391 | /** 392 | * Determine if the attribute passes any optional check. 393 | * 394 | * @param string $attribute 395 | * 396 | * @return bool 397 | */ 398 | protected function passesOptionalCheck($attribute) 399 | { 400 | if ($this->hasRule($attribute, ['Sometimes'])) { 401 | return array_key_exists($attribute, array_dot($this->data)) 402 | || array_key_exists($attribute, $this->files); 403 | } 404 | 405 | return true; 406 | } 407 | 408 | /** 409 | * Determine if a given rule implies the attribute is required. 410 | * 411 | * @param string $rule 412 | * 413 | * @return bool 414 | */ 415 | protected function isImplicit($rule) 416 | { 417 | return in_array($rule, $this->implicitRules, true); 418 | } 419 | 420 | /** 421 | * Add a failed rule and error message to the collection. 422 | * 423 | * @param string $attribute 424 | * @param string $rule 425 | * @param array $parameters 426 | */ 427 | protected function addFailure($attribute, $rule, $parameters) 428 | { 429 | $this->addError($attribute, $rule, $parameters); 430 | 431 | $this->failedRules[$attribute][snake_case($rule)] = $parameters; 432 | } 433 | 434 | /** 435 | * Add an error message to the validator's collection of messages. 436 | * 437 | * @param string $attribute 438 | * @param string $rule 439 | * @param array $parameters 440 | */ 441 | protected function addError($attribute, $rule, $parameters) 442 | { 443 | $message = $this->getMessage($attribute, $rule); 444 | 445 | $message = $this->doReplacements($message, $attribute, $rule, $parameters); 446 | 447 | $this->messages->add($attribute, $message); 448 | } 449 | 450 | /** 451 | * "Validate" optional attributes. 452 | * 453 | * Always returns true, just lets us put sometimes in rules. 454 | * 455 | * 456 | * @return bool 457 | */ 458 | protected function validateSometimes() 459 | { 460 | return true; 461 | } 462 | 463 | /** 464 | * Validate that a required attribute exists. 465 | * 466 | * @param string $attribute 467 | * @param mixed $value 468 | * 469 | * @return bool 470 | */ 471 | protected function validateRequired($attribute, $value) 472 | { 473 | if (is_null($value)) { 474 | return false; 475 | } elseif (is_string($value) && trim($value) === '') { 476 | return false; 477 | } elseif (is_array($value) && count($value) < 1) { 478 | return false; 479 | } elseif (in_array($value, $_FILES, true)) { 480 | return (string) $value['tmp_name'] !== ''; 481 | } 482 | 483 | return true; 484 | } 485 | 486 | /** 487 | * Validate the given attribute is filled if it is present. 488 | * 489 | * @param string $attribute 490 | * @param mixed $value 491 | * 492 | * @return bool 493 | */ 494 | protected function validateFilled($attribute, $value) 495 | { 496 | if (array_key_exists($attribute, $this->data) || array_key_exists($attribute, $this->files)) { 497 | return $this->validateRequired($attribute, $value); 498 | } else { 499 | return true; 500 | } 501 | } 502 | 503 | /** 504 | * Determine if any of the given attributes fail the required test. 505 | * 506 | * @param array $attributes 507 | * 508 | * @return bool 509 | */ 510 | protected function anyFailingRequired(array $attributes) 511 | { 512 | foreach ($attributes as $key) { 513 | if (!$this->validateRequired($key, $this->getValue($key))) { 514 | return true; 515 | } 516 | } 517 | 518 | return false; 519 | } 520 | 521 | /** 522 | * Determine if all of the given attributes fail the required test. 523 | * 524 | * @param array $attributes 525 | * 526 | * @return bool 527 | */ 528 | protected function allFailingRequired(array $attributes) 529 | { 530 | foreach ($attributes as $key) { 531 | if ($this->validateRequired($key, $this->getValue($key))) { 532 | return false; 533 | } 534 | } 535 | 536 | return true; 537 | } 538 | 539 | /** 540 | * Validate that an attribute exists when any other attribute exists. 541 | * 542 | * @param string $attribute 543 | * @param mixed $value 544 | * @param mixed $parameters 545 | * 546 | * @return bool 547 | */ 548 | protected function validateRequiredWith($attribute, $value, $parameters) 549 | { 550 | if (!$this->allFailingRequired($parameters)) { 551 | return $this->validateRequired($attribute, $value); 552 | } 553 | 554 | return true; 555 | } 556 | 557 | /** 558 | * Validate that an attribute exists when all other attributes exists. 559 | * 560 | * @param string $attribute 561 | * @param mixed $value 562 | * @param mixed $parameters 563 | * 564 | * @return bool 565 | */ 566 | protected function validateRequiredWithAll($attribute, $value, $parameters) 567 | { 568 | if (!$this->anyFailingRequired($parameters)) { 569 | return $this->validateRequired($attribute, $value); 570 | } 571 | 572 | return true; 573 | } 574 | 575 | /** 576 | * Validate that an attribute exists when another attribute does not. 577 | * 578 | * @param string $attribute 579 | * @param mixed $value 580 | * @param mixed $parameters 581 | * 582 | * @return bool 583 | */ 584 | protected function validateRequiredWithout($attribute, $value, $parameters) 585 | { 586 | if ($this->anyFailingRequired($parameters)) { 587 | return $this->validateRequired($attribute, $value); 588 | } 589 | 590 | return true; 591 | } 592 | 593 | /** 594 | * Validate that an attribute exists when all other attributes do not. 595 | * 596 | * @param string $attribute 597 | * @param mixed $value 598 | * @param mixed $parameters 599 | * 600 | * @return bool 601 | */ 602 | protected function validateRequiredWithoutAll($attribute, $value, $parameters) 603 | { 604 | if ($this->allFailingRequired($parameters)) { 605 | return $this->validateRequired($attribute, $value); 606 | } 607 | 608 | return true; 609 | } 610 | 611 | /** 612 | * Validate that an attribute exists when another attribute has a given value. 613 | * 614 | * @param string $attribute 615 | * @param mixed $value 616 | * @param mixed $parameters 617 | * 618 | * @return bool 619 | */ 620 | protected function validateRequiredIf($attribute, $value, $parameters) 621 | { 622 | $this->requireParameterCount(2, $parameters, 'required_if'); 623 | 624 | $data = array_get($this->data, $parameters[0]); 625 | 626 | $values = array_slice($parameters, 1); 627 | 628 | if (in_array($data, $values)) { 629 | return $this->validateRequired($attribute, $value); 630 | } 631 | 632 | return true; 633 | } 634 | 635 | /** 636 | * Get the number of attributes in a list that are present. 637 | * 638 | * @param array $attributes 639 | * 640 | * @return int 641 | */ 642 | protected function getPresentCount($attributes) 643 | { 644 | $count = 0; 645 | 646 | foreach ($attributes as $key) { 647 | if (array_get($this->data, $key) || array_get($this->files, $key)) { 648 | ++$count; 649 | } 650 | } 651 | 652 | return $count; 653 | } 654 | 655 | /** 656 | * Validate that an attribute has a matching confirmation. 657 | * 658 | * @param string $attribute 659 | * @param mixed $value 660 | * 661 | * @return bool 662 | */ 663 | protected function validateConfirmed($attribute, $value) 664 | { 665 | return $this->validateSame($attribute, $value, [$attribute.'_confirmation']); 666 | } 667 | 668 | /** 669 | * Validate that two attributes match. 670 | * 671 | * @param string $attribute 672 | * @param mixed $value 673 | * @param array $parameters 674 | * 675 | * @return bool 676 | */ 677 | protected function validateSame($attribute, $value, $parameters) 678 | { 679 | $this->requireParameterCount(1, $parameters, 'same'); 680 | 681 | $other = array_get($this->data, $parameters[0]); 682 | 683 | return isset($other) && $value === $other; 684 | } 685 | 686 | /** 687 | * Validate that an attribute is different from another attribute. 688 | * 689 | * @param string $attribute 690 | * @param mixed $value 691 | * @param array $parameters 692 | * 693 | * @return bool 694 | */ 695 | protected function validateDifferent($attribute, $value, $parameters) 696 | { 697 | $this->requireParameterCount(1, $parameters, 'different'); 698 | 699 | $other = array_get($this->data, $parameters[0]); 700 | 701 | return isset($other) && $value !== $other; 702 | } 703 | 704 | /** 705 | * Validate that an attribute was "accepted". 706 | * 707 | * This validation rule implies the attribute is "required". 708 | * 709 | * @param string $attribute 710 | * @param mixed $value 711 | * 712 | * @return bool 713 | */ 714 | protected function validateAccepted($attribute, $value) 715 | { 716 | $acceptable = ['yes', 'on', '1', 1, true, 'true']; 717 | 718 | return $this->validateRequired($attribute, $value) && in_array($value, $acceptable, true); 719 | } 720 | 721 | /** 722 | * Validate that an attribute is a boolean. 723 | * 724 | * @param string $attribute 725 | * @param mixed $value 726 | * 727 | * @return bool 728 | */ 729 | protected function validateBoolean($attribute, $value) 730 | { 731 | $acceptable = [true, false, 0, 1, '0', '1']; 732 | 733 | return in_array($value, $acceptable, true); 734 | } 735 | 736 | /** 737 | * Validate that an attribute is an array. 738 | * 739 | * @param string $attribute 740 | * @param mixed $value 741 | * 742 | * @return bool 743 | */ 744 | protected function validateArray($attribute, $value) 745 | { 746 | return is_array($value); 747 | } 748 | 749 | /** 750 | * Validate that an attribute is numeric. 751 | * 752 | * @param string $attribute 753 | * @param mixed $value 754 | * 755 | * @return bool 756 | */ 757 | protected function validateNumeric($attribute, $value) 758 | { 759 | return is_numeric($value); 760 | } 761 | 762 | /** 763 | * Validate that an attribute is an integer. 764 | * 765 | * @param string $attribute 766 | * @param mixed $value 767 | * 768 | * @return bool 769 | */ 770 | protected function validateInteger($attribute, $value) 771 | { 772 | return filter_var($value, FILTER_VALIDATE_INT) !== false; 773 | } 774 | 775 | /** 776 | * Validate that an attribute has a given number of digits. 777 | * 778 | * @param string $attribute 779 | * @param mixed $value 780 | * @param array $parameters 781 | * 782 | * @return bool 783 | */ 784 | protected function validateDigits($attribute, $value, $parameters) 785 | { 786 | $this->requireParameterCount(1, $parameters, 'digits'); 787 | 788 | return $this->validateNumeric($attribute, $value) 789 | && strlen((string) $value) == $parameters[0]; 790 | } 791 | 792 | /** 793 | * Validate that an attribute is between a given number of digits. 794 | * 795 | * @param string $attribute 796 | * @param mixed $value 797 | * @param array $parameters 798 | * 799 | * @return bool 800 | */ 801 | protected function validateDigitsBetween($attribute, $value, $parameters) 802 | { 803 | $this->requireParameterCount(2, $parameters, 'digits_between'); 804 | 805 | $length = strlen((string) $value); 806 | 807 | return $length >= $parameters[0] && $length <= $parameters[1]; 808 | } 809 | 810 | /** 811 | * Validate the size of an attribute. 812 | * 813 | * @param string $attribute 814 | * @param mixed $value 815 | * @param array $parameters 816 | * 817 | * @return bool 818 | */ 819 | protected function validateSize($attribute, $value, $parameters) 820 | { 821 | $this->requireParameterCount(1, $parameters, 'size'); 822 | 823 | return $this->getSize($attribute, $value) == $parameters[0]; 824 | } 825 | 826 | /** 827 | * Validate the size of an attribute is between a set of values. 828 | * 829 | * @param string $attribute 830 | * @param mixed $value 831 | * @param array $parameters 832 | * 833 | * @return bool 834 | */ 835 | protected function validateBetween($attribute, $value, $parameters) 836 | { 837 | $this->requireParameterCount(2, $parameters, 'between'); 838 | 839 | $size = $this->getSize($attribute, $value); 840 | 841 | return $size >= $parameters[0] && $size <= $parameters[1]; 842 | } 843 | 844 | /** 845 | * Validate the size of an attribute is greater than a minimum value. 846 | * 847 | * @param string $attribute 848 | * @param mixed $value 849 | * @param array $parameters 850 | * 851 | * @return bool 852 | */ 853 | protected function validateMin($attribute, $value, $parameters) 854 | { 855 | $this->requireParameterCount(1, $parameters, 'min'); 856 | 857 | return $this->getSize($attribute, $value) >= $parameters[0]; 858 | } 859 | 860 | /** 861 | * Validate the size of an attribute is less than a maximum value. 862 | * 863 | * @param string $attribute 864 | * @param mixed $value 865 | * @param array $parameters 866 | * 867 | * @return bool 868 | */ 869 | protected function validateMax($attribute, $value, $parameters) 870 | { 871 | $this->requireParameterCount(1, $parameters, 'max'); 872 | 873 | if (!empty($value['tmp_name']) && is_uploaded_file($value['tmp_name']) && $value['error']) { 874 | return false; 875 | } 876 | 877 | return $this->getSize($attribute, $value) <= $parameters[0]; 878 | } 879 | 880 | /** 881 | * Get the size of an attribute. 882 | * 883 | * @param string $attribute 884 | * @param mixed $value 885 | * 886 | * @return mixed 887 | */ 888 | protected function getSize($attribute, $value) 889 | { 890 | $hasNumeric = $this->hasRule($attribute, $this->numericRules); 891 | 892 | // This method will determine if the attribute is a number, string, or file and 893 | // return the proper size accordingly. If it is a number, then number itself 894 | // is the size. If it is a file, we take kilobytes, and for a string the 895 | // entire length of the string will be considered the attribute size. 896 | if (is_numeric($value) && $hasNumeric) { 897 | return array_get($this->data, $attribute); 898 | } elseif (is_array($value)) { 899 | return count($value); 900 | } elseif (in_array($value, $_FILES, true)) { 901 | return $value->getSize() / 1024; 902 | } else { 903 | return $this->getStringSize($value); 904 | } 905 | } 906 | 907 | /** 908 | * Get the size of a string. 909 | * 910 | * @param string $value 911 | * 912 | * @return int 913 | */ 914 | protected function getStringSize($value) 915 | { 916 | if (function_exists('mb_strlen')) { 917 | return mb_strlen($value); 918 | } 919 | 920 | return strlen($value); 921 | } 922 | 923 | /** 924 | * Validate an attribute is contained within a list of values. 925 | * 926 | * @param string $attribute 927 | * @param mixed $value 928 | * @param array $parameters 929 | * 930 | * @return bool 931 | */ 932 | protected function validateIn($attribute, $value, $parameters) 933 | { 934 | return in_array((string) $value, $parameters, true); 935 | } 936 | 937 | /** 938 | * Validate an attribute is not contained within a list of values. 939 | * 940 | * @param string $attribute 941 | * @param mixed $value 942 | * @param array $parameters 943 | * 944 | * @return bool 945 | */ 946 | protected function validateNotIn($attribute, $value, $parameters) 947 | { 948 | return !$this->validateIn($attribute, $value, $parameters); 949 | } 950 | 951 | /** 952 | * Validate the uniqueness of an attribute value on a given database table. 953 | * 954 | * If a database column is not specified, the attribute will be used. 955 | * 956 | * @param string $attribute 957 | * @param mixed $value 958 | * @param array $parameters 959 | * 960 | * @return bool 961 | */ 962 | protected function validateUnique($attribute, $value, $parameters) 963 | { 964 | $this->requireParameterCount(1, $parameters, 'unique'); 965 | 966 | $table = $parameters[0]; 967 | 968 | // The second parameter position holds the name of the column that needs to 969 | // be verified as unique. If this parameter isn't specified we will just 970 | // assume that this column to be verified shares the attribute's name. 971 | $column = isset($parameters[1]) ? $parameters[1] : $attribute; 972 | 973 | list($idColumn, $id) = [null, null]; 974 | 975 | if (isset($parameters[2])) { 976 | list($idColumn, $id) = $this->getUniqueIds($parameters); 977 | 978 | if (strtolower($id) === 'null') { 979 | $id = null; 980 | } 981 | } 982 | 983 | // The presence verifier is responsible for counting rows within this store 984 | // mechanism which might be a relational database or any other permanent 985 | // data store like Redis, etc. We will use it to determine uniqueness. 986 | $verifier = $this->getPresenceVerifier(); 987 | 988 | $extra = $this->getUniqueExtra($parameters); 989 | 990 | return $verifier->getCount( 991 | 992 | $table, $column, $value, $id, $idColumn, $extra 993 | 994 | ) === 0; 995 | } 996 | 997 | /** 998 | * Get the excluded ID column and value for the unique rule. 999 | * 1000 | * @param array $parameters 1001 | * 1002 | * @return array 1003 | */ 1004 | protected function getUniqueIds($parameters) 1005 | { 1006 | $idColumn = isset($parameters[3]) ? $parameters[3] : 'id'; 1007 | 1008 | return [$idColumn, $parameters[2]]; 1009 | } 1010 | 1011 | /** 1012 | * Get the extra conditions for a unique rule. 1013 | * 1014 | * @param array $parameters 1015 | * 1016 | * @return array 1017 | */ 1018 | protected function getUniqueExtra($parameters) 1019 | { 1020 | if (isset($parameters[4])) { 1021 | return $this->getExtraConditions(array_slice($parameters, 4)); 1022 | } else { 1023 | return []; 1024 | } 1025 | } 1026 | 1027 | /** 1028 | * Validate the existence of an attribute value in a database table. 1029 | * 1030 | * @param string $attribute 1031 | * @param mixed $value 1032 | * @param array $parameters 1033 | * 1034 | * @return bool 1035 | */ 1036 | protected function validateExists($attribute, $value, $parameters) 1037 | { 1038 | $this->requireParameterCount(1, $parameters, 'exists'); 1039 | 1040 | $table = $parameters[0]; 1041 | 1042 | // The second parameter position holds the name of the column that should be 1043 | // verified as existing. If this parameter is not specified we will guess 1044 | // that the columns being "verified" shares the given attribute's name. 1045 | $column = isset($parameters[1]) ? $parameters[1] : $attribute; 1046 | 1047 | $expected = (is_array($value)) ? count($value) : 1; 1048 | 1049 | return $this->getExistCount($table, $column, $value, $parameters) >= $expected; 1050 | } 1051 | 1052 | /** 1053 | * Get the number of records that exist in storage. 1054 | * 1055 | * @param string $table 1056 | * @param string $column 1057 | * @param mixed $value 1058 | * @param array $parameters 1059 | * 1060 | * @return int 1061 | */ 1062 | protected function getExistCount($table, $column, $value, $parameters) 1063 | { 1064 | $verifier = $this->getPresenceVerifier(); 1065 | 1066 | $extra = $this->getExtraExistConditions($parameters); 1067 | 1068 | if (is_array($value)) { 1069 | return $verifier->getMultiCount($table, $column, $value, $extra); 1070 | } else { 1071 | return $verifier->getCount($table, $column, $value, null, null, $extra); 1072 | } 1073 | } 1074 | 1075 | /** 1076 | * Get the extra exist conditions. 1077 | * 1078 | * @param array $parameters 1079 | * 1080 | * @return array 1081 | */ 1082 | protected function getExtraExistConditions(array $parameters) 1083 | { 1084 | return $this->getExtraConditions(array_values(array_slice($parameters, 2))); 1085 | } 1086 | 1087 | /** 1088 | * Get the extra conditions for a unique / exists rule. 1089 | * 1090 | * @param array $segments 1091 | * 1092 | * @return array 1093 | */ 1094 | protected function getExtraConditions(array $segments) 1095 | { 1096 | $extra = []; 1097 | 1098 | $count = count($segments); 1099 | 1100 | for ($i = 0; $i < $count; $i = $i + 2) { 1101 | $extra[$segments[$i]] = $segments[$i + 1]; 1102 | } 1103 | 1104 | return $extra; 1105 | } 1106 | 1107 | /** 1108 | * Validate that an attribute is a valid IP. 1109 | * 1110 | * @param string $attribute 1111 | * @param mixed $value 1112 | * 1113 | * @return bool 1114 | */ 1115 | protected function validateIp($attribute, $value) 1116 | { 1117 | return filter_var($value, FILTER_VALIDATE_IP) !== false; 1118 | } 1119 | 1120 | /** 1121 | * Validate that an attribute is a valid e-mail address. 1122 | * 1123 | * @param string $attribute 1124 | * @param mixed $value 1125 | * 1126 | * @return bool 1127 | */ 1128 | protected function validateEmail($attribute, $value) 1129 | { 1130 | return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; 1131 | } 1132 | 1133 | /** 1134 | * Validate that an attribute is a valid URL. 1135 | * 1136 | * @param string $attribute 1137 | * @param mixed $value 1138 | * 1139 | * @return bool 1140 | */ 1141 | protected function validateUrl($attribute, $value) 1142 | { 1143 | return filter_var($value, FILTER_VALIDATE_URL) !== false; 1144 | } 1145 | 1146 | /** 1147 | * Validate that an attribute is an active URL. 1148 | * 1149 | * @param string $attribute 1150 | * @param mixed $value 1151 | * 1152 | * @return bool 1153 | */ 1154 | protected function validateActiveUrl($attribute, $value) 1155 | { 1156 | $url = str_replace(['http://', 'https://', 'ftp://'], '', strtolower($value)); 1157 | 1158 | return checkdnsrr($url); 1159 | } 1160 | 1161 | /** 1162 | * Validate the MIME type of a file is an image MIME type. 1163 | * 1164 | * @param string $attribute 1165 | * @param mixed $value 1166 | * 1167 | * @return bool 1168 | */ 1169 | protected function validateImage($attribute, $value) 1170 | { 1171 | return $this->validateMimes($attribute, $value, ['jpeg', 'png', 'gif', 'bmp']); 1172 | } 1173 | 1174 | /** 1175 | * Validate the MIME type of a file upload attribute is in a set of MIME types. 1176 | * 1177 | * @param string $attribute 1178 | * @param array $value 1179 | * @param array $parameters 1180 | * 1181 | * @return bool 1182 | */ 1183 | protected function validateMimes($attribute, $value, $parameters) 1184 | { 1185 | if (!in_array($value, $_FILES, true)) { 1186 | return false; 1187 | } 1188 | 1189 | if (!empty($value['tmp_name']) && is_uploaded_file($value['tmp_name']) && $value['error']) { 1190 | return false; 1191 | } 1192 | 1193 | // The Symfony File class should do a decent job of guessing the extension 1194 | // based on the true MIME type so we'll just loop through the array of 1195 | // extensions and compare it to the guessed extension of the files. 1196 | if ($value['tmp_name'] !== '') { 1197 | return in_array(pathinfo($value['name'], PATHINFO_EXTENSION), $parameters, true); 1198 | } else { 1199 | return false; 1200 | } 1201 | } 1202 | 1203 | /** 1204 | * Validate that an attribute contains only alphabetic characters. 1205 | * 1206 | * @param string $attribute 1207 | * @param mixed $value 1208 | * 1209 | * @return bool 1210 | */ 1211 | protected function validateAlpha($attribute, $value) 1212 | { 1213 | return preg_match('/^[\pL\pM]+$/u', $value); 1214 | } 1215 | 1216 | /** 1217 | * Validate that an attribute contains only alpha-numeric characters. 1218 | * 1219 | * @param string $attribute 1220 | * @param mixed $value 1221 | * 1222 | * @return bool 1223 | */ 1224 | protected function validateAlphaNum($attribute, $value) 1225 | { 1226 | return preg_match('/^[\pL\pM\pN]+$/u', $value); 1227 | } 1228 | 1229 | /** 1230 | * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores. 1231 | * 1232 | * @param string $attribute 1233 | * @param mixed $value 1234 | * 1235 | * @return bool 1236 | */ 1237 | protected function validateAlphaDash($attribute, $value) 1238 | { 1239 | return preg_match('/^[\pL\pM\pN_-]+$/u', $value); 1240 | } 1241 | 1242 | /** 1243 | * Validate that an attribute passes a regular expression check. 1244 | * 1245 | * @param string $attribute 1246 | * @param mixed $value 1247 | * @param array $parameters 1248 | * 1249 | * @return bool 1250 | */ 1251 | protected function validateRegex($attribute, $value, $parameters) 1252 | { 1253 | $this->requireParameterCount(1, $parameters, 'regex'); 1254 | 1255 | return preg_match($parameters[0], $value); 1256 | } 1257 | 1258 | /** 1259 | * Validate that an attribute is a valid date. 1260 | * 1261 | * @param string $attribute 1262 | * @param mixed $value 1263 | * 1264 | * @return bool 1265 | */ 1266 | protected function validateDate($attribute, $value) 1267 | { 1268 | if ($value instanceof DateTime) { 1269 | return true; 1270 | } 1271 | 1272 | if (strtotime($value) === false) { 1273 | return false; 1274 | } 1275 | 1276 | $date = date_parse($value); 1277 | 1278 | return checkdate($date['month'], $date['day'], $date['year']); 1279 | } 1280 | 1281 | /** 1282 | * Validate that an attribute matches a date format. 1283 | * 1284 | * @param string $attribute 1285 | * @param mixed $value 1286 | * @param array $parameters 1287 | * 1288 | * @return bool 1289 | */ 1290 | protected function validateDateFormat($attribute, $value, $parameters) 1291 | { 1292 | $this->requireParameterCount(1, $parameters, 'date_format'); 1293 | 1294 | $parsed = date_parse_from_format($parameters[0], $value); 1295 | 1296 | return $parsed['error_count'] === 0 && $parsed['warning_count'] === 0; 1297 | } 1298 | 1299 | /** 1300 | * Validate the date is before a given date. 1301 | * 1302 | * @param string $attribute 1303 | * @param mixed $value 1304 | * @param array $parameters 1305 | * 1306 | * @return bool 1307 | */ 1308 | protected function validateBefore($attribute, $value, $parameters) 1309 | { 1310 | $this->requireParameterCount(1, $parameters, 'before'); 1311 | 1312 | if ($format = $this->getDateFormat($attribute)) { 1313 | return $this->validateBeforeWithFormat($format, $value, $parameters); 1314 | } 1315 | 1316 | if (!($date = strtotime($parameters[0]))) { 1317 | return strtotime($value) < strtotime($this->getValue($parameters[0])); 1318 | } else { 1319 | return strtotime($value) < $date; 1320 | } 1321 | } 1322 | 1323 | /** 1324 | * Validate the date is before a given date with a given format. 1325 | * 1326 | * @param string $format 1327 | * @param mixed $value 1328 | * @param array $parameters 1329 | * 1330 | * @return bool 1331 | */ 1332 | protected function validateBeforeWithFormat($format, $value, $parameters) 1333 | { 1334 | $param = $this->getValue($parameters[0]) ?: $parameters[0]; 1335 | 1336 | return $this->checkDateTimeOrder($format, $value, $param); 1337 | } 1338 | 1339 | /** 1340 | * Validate the date is after a given date. 1341 | * 1342 | * @param string $attribute 1343 | * @param mixed $value 1344 | * @param array $parameters 1345 | * 1346 | * @return bool 1347 | */ 1348 | protected function validateAfter($attribute, $value, $parameters) 1349 | { 1350 | $this->requireParameterCount(1, $parameters, 'after'); 1351 | 1352 | if ($format = $this->getDateFormat($attribute)) { 1353 | return $this->validateAfterWithFormat($format, $value, $parameters); 1354 | } 1355 | 1356 | if (!($date = strtotime($parameters[0]))) { 1357 | return strtotime($value) > strtotime($this->getValue($parameters[0])); 1358 | } else { 1359 | return strtotime($value) > $date; 1360 | } 1361 | } 1362 | 1363 | /** 1364 | * Validate the date is after a given date with a given format. 1365 | * 1366 | * @param string $format 1367 | * @param mixed $value 1368 | * @param array $parameters 1369 | * 1370 | * @return bool 1371 | */ 1372 | protected function validateAfterWithFormat($format, $value, $parameters) 1373 | { 1374 | $param = $this->getValue($parameters[0]) ?: $parameters[0]; 1375 | 1376 | return $this->checkDateTimeOrder($format, $param, $value); 1377 | } 1378 | 1379 | /** 1380 | * Given two date/time strings, check that one is after the other. 1381 | * 1382 | * @param string $format 1383 | * @param string $before 1384 | * @param string $after 1385 | * 1386 | * @return bool 1387 | */ 1388 | protected function checkDateTimeOrder($format, $before, $after) 1389 | { 1390 | $before = $this->getDateTimeWithOptionalFormat($format, $before); 1391 | 1392 | $after = $this->getDateTimeWithOptionalFormat($format, $after); 1393 | 1394 | return ($before && $after) && ($after > $before); 1395 | } 1396 | 1397 | /** 1398 | * Get a DateTime instance from a string. 1399 | * 1400 | * @param string $format 1401 | * @param string $value 1402 | * 1403 | * @return \DateTime|null 1404 | */ 1405 | protected function getDateTimeWithOptionalFormat($format, $value) 1406 | { 1407 | $date = DateTime::createFromFormat($format, $value); 1408 | 1409 | if ($date) { 1410 | return $date; 1411 | } 1412 | 1413 | try { 1414 | return new DateTime($value); 1415 | } catch (\Exception $e) { 1416 | return; 1417 | } 1418 | } 1419 | 1420 | /** 1421 | * Validate that an attribute is a valid timezone. 1422 | * 1423 | * @param string $attribute 1424 | * @param mixed $value 1425 | * 1426 | * @return bool 1427 | */ 1428 | protected function validateTimezone($attribute, $value) 1429 | { 1430 | try { 1431 | new DateTimeZone($value); 1432 | } catch (\Exception $e) { 1433 | return false; 1434 | } 1435 | 1436 | return true; 1437 | } 1438 | 1439 | /** 1440 | * Get the date format for an attribute if it has one. 1441 | * 1442 | * @param string $attribute 1443 | * 1444 | * @return string|null 1445 | */ 1446 | protected function getDateFormat($attribute) 1447 | { 1448 | if ($result = $this->getRule($attribute, 'DateFormat')) { 1449 | return $result[1][0]; 1450 | } 1451 | } 1452 | 1453 | /** 1454 | * Get the validation message for an attribute and rule. 1455 | * 1456 | * @param string $attribute 1457 | * @param string $rule 1458 | * 1459 | * @return string 1460 | */ 1461 | protected function getMessage($attribute, $rule) 1462 | { 1463 | $lowerRule = snake_case($rule); 1464 | 1465 | $inlineMessage = $this->getInlineMessage($attribute, $lowerRule); 1466 | 1467 | // First we will retrieve the custom message for the validation rule if one 1468 | // exists. If a custom validation message is being used we'll return the 1469 | // custom message, otherwise we'll keep searching for a valid message. 1470 | if (!is_null($inlineMessage)) { 1471 | return $inlineMessage; 1472 | } 1473 | 1474 | $customKey = "validation.custom.{$attribute}.{$lowerRule}"; 1475 | 1476 | $customMessage = $this->translator->trans($customKey); 1477 | 1478 | // First we check for a custom defined validation message for the attribute 1479 | // and rule. This allows the developer to specify specific messages for 1480 | // only some attributes and rules that need to get specially formed. 1481 | if ($customMessage !== $customKey) { 1482 | return $customMessage; 1483 | } 1484 | 1485 | // If the rule being validated is a "size" rule, we will need to gather the 1486 | // specific error message for the type of attribute being validated such 1487 | // as a number, file or string which all have different message types. 1488 | elseif (in_array($rule, $this->sizeRules, true)) { 1489 | return $this->getSizeMessage($attribute, $rule); 1490 | } 1491 | 1492 | // Finally, if no developer specified messages have been set, and no other 1493 | // special messages apply for this rule, we will just pull the default 1494 | // messages out of the translator service for this validation rule. 1495 | $key = "validation.{$lowerRule}"; 1496 | 1497 | if ($key !== ($value = $this->translator->trans($key))) { 1498 | return $value; 1499 | } 1500 | 1501 | return $this->getInlineMessage( 1502 | $attribute, $lowerRule, $this->fallbackMessages 1503 | ) ?: $key; 1504 | } 1505 | 1506 | /** 1507 | * Get the inline message for a rule if it exists. 1508 | * 1509 | * @param string $attribute 1510 | * @param string $lowerRule 1511 | * @param array $source 1512 | * 1513 | * @return string 1514 | */ 1515 | protected function getInlineMessage($attribute, $lowerRule, $source = null) 1516 | { 1517 | $source = $source ?: $this->customMessages; 1518 | 1519 | $keys = ["{$attribute}.{$lowerRule}", $lowerRule]; 1520 | 1521 | // First we will check for a custom message for an attribute specific rule 1522 | // message for the fields, then we will check for a general custom line 1523 | // that is not attribute specific. If we find either we'll return it. 1524 | foreach ($keys as $key) { 1525 | if (isset($source[$key])) { 1526 | return $source[$key]; 1527 | } 1528 | } 1529 | } 1530 | 1531 | /** 1532 | * Get the proper error message for an attribute and size rule. 1533 | * 1534 | * @param string $attribute 1535 | * @param string $rule 1536 | * 1537 | * @return string 1538 | */ 1539 | protected function getSizeMessage($attribute, $rule) 1540 | { 1541 | $lowerRule = snake_case($rule); 1542 | 1543 | // There are three different types of size validations. The attribute may be 1544 | // either a number, file, or string so we will check a few things to know 1545 | // which type of value it is and return the correct line for that type. 1546 | $type = $this->getAttributeType($attribute); 1547 | 1548 | $key = "validation.{$lowerRule}.{$type}"; 1549 | 1550 | return $this->translator->trans($key); 1551 | } 1552 | 1553 | /** 1554 | * Get the data type of the given attribute. 1555 | * 1556 | * @param string $attribute 1557 | * 1558 | * @return string 1559 | */ 1560 | protected function getAttributeType($attribute) 1561 | { 1562 | // We assume that the attributes present in the file array are files so that 1563 | // means that if the attribute does not have a numeric rule and the files 1564 | // list doesn't have it we'll just consider it a string by elimination. 1565 | if ($this->hasRule($attribute, $this->numericRules)) { 1566 | return 'numeric'; 1567 | } elseif ($this->hasRule($attribute, ['Array'])) { 1568 | return 'array'; 1569 | } elseif (array_key_exists($attribute, $this->files)) { 1570 | return 'file'; 1571 | } 1572 | 1573 | return 'string'; 1574 | } 1575 | 1576 | /** 1577 | * Replace all error message place-holders with actual values. 1578 | * 1579 | * @param string $message 1580 | * @param string $attribute 1581 | * @param string $rule 1582 | * @param array $parameters 1583 | * 1584 | * @return string 1585 | */ 1586 | protected function doReplacements($message, $attribute, $rule, $parameters) 1587 | { 1588 | $value = $this->getAttribute($attribute); 1589 | 1590 | $message = str_replace( 1591 | [':ATTRIBUTE', ':Attribute', ':attribute'], 1592 | [strtoupper($value), ucfirst($value), $value], 1593 | $message 1594 | ); 1595 | 1596 | if (isset($this->replacers[snake_case($rule)])) { 1597 | $message = $this->callReplacer($message, $attribute, snake_case($rule), $parameters); 1598 | } elseif (method_exists($this, $replacer = "replace{$rule}")) { 1599 | $message = $this->$replacer($message, $attribute, $rule, $parameters); 1600 | } 1601 | 1602 | return $message; 1603 | } 1604 | 1605 | /** 1606 | * Transform an array of attributes to their displayable form. 1607 | * 1608 | * @param array $values 1609 | * 1610 | * @return array 1611 | */ 1612 | protected function getAttributeList(array $values) 1613 | { 1614 | $attributes = []; 1615 | 1616 | // For each attribute in the list we will simply get its displayable form as 1617 | // this is convenient when replacing lists of parameters like some of the 1618 | // replacement functions do when formatting out the validation message. 1619 | foreach ($values as $key => $value) { 1620 | $attributes[$key] = $this->getAttribute($value); 1621 | } 1622 | 1623 | return $attributes; 1624 | } 1625 | 1626 | /** 1627 | * Get the displayable name of the attribute. 1628 | * 1629 | * @param string $attribute 1630 | * 1631 | * @return string 1632 | */ 1633 | protected function getAttribute($attribute) 1634 | { 1635 | // The developer may dynamically specify the array of custom attributes 1636 | // on this Validator instance. If the attribute exists in this array 1637 | // it takes precedence over all other ways we can pull attributes. 1638 | if (isset($this->customAttributes[$attribute])) { 1639 | return $this->customAttributes[$attribute]; 1640 | } 1641 | 1642 | $key = "validation.attributes.{$attribute}"; 1643 | 1644 | // We allow for the developer to specify language lines for each of the 1645 | // attributes allowing for more displayable counterparts of each of 1646 | // the attributes. This provides the ability for simple formats. 1647 | if (($line = $this->translator->trans($key)) !== $key) { 1648 | return $line; 1649 | } 1650 | 1651 | // If no language line has been specified for the attribute all of the 1652 | // underscores are removed from the attribute name and that will be 1653 | // used as default versions of the attribute's displayable names. 1654 | else { 1655 | return str_replace('_', ' ', snake_case($attribute)); 1656 | } 1657 | } 1658 | 1659 | /** 1660 | * Get the displayable name of the value. 1661 | * 1662 | * @param string $attribute 1663 | * @param mixed $value 1664 | * 1665 | * @return string 1666 | */ 1667 | public function getDisplayableValue($attribute, $value) 1668 | { 1669 | if (isset($this->customValues[$attribute][$value])) { 1670 | return $this->customValues[$attribute][$value]; 1671 | } 1672 | 1673 | $key = "validation.values.{$attribute}.{$value}"; 1674 | 1675 | if (($line = $this->translator->trans($key)) !== $key) { 1676 | return $line; 1677 | } else { 1678 | return $value; 1679 | } 1680 | } 1681 | 1682 | /** 1683 | * Replace all place-holders for the between rule. 1684 | * 1685 | * @param string $message 1686 | * @param string $attribute 1687 | * @param string $rule 1688 | * @param array $parameters 1689 | * 1690 | * @return string 1691 | */ 1692 | protected function replaceBetween($message, $attribute, $rule, $parameters) 1693 | { 1694 | return str_replace([':min', ':max'], $parameters, $message); 1695 | } 1696 | 1697 | /** 1698 | * Replace all place-holders for the digits rule. 1699 | * 1700 | * @param string $message 1701 | * @param string $attribute 1702 | * @param string $rule 1703 | * @param array $parameters 1704 | * 1705 | * @return string 1706 | */ 1707 | protected function replaceDigits($message, $attribute, $rule, $parameters) 1708 | { 1709 | return str_replace(':digits', $parameters[0], $message); 1710 | } 1711 | 1712 | /** 1713 | * Replace all place-holders for the digits (between) rule. 1714 | * 1715 | * @param string $message 1716 | * @param string $attribute 1717 | * @param string $rule 1718 | * @param array $parameters 1719 | * 1720 | * @return string 1721 | */ 1722 | protected function replaceDigitsBetween($message, $attribute, $rule, $parameters) 1723 | { 1724 | return str_replace([':min', ':max'], $parameters, $message); 1725 | } 1726 | 1727 | /** 1728 | * Replace all place-holders for the size rule. 1729 | * 1730 | * @param string $message 1731 | * @param string $attribute 1732 | * @param string $rule 1733 | * @param array $parameters 1734 | * 1735 | * @return string 1736 | */ 1737 | protected function replaceSize($message, $attribute, $rule, $parameters) 1738 | { 1739 | return str_replace(':size', $parameters[0], $message); 1740 | } 1741 | 1742 | /** 1743 | * Replace all place-holders for the min rule. 1744 | * 1745 | * @param string $message 1746 | * @param string $attribute 1747 | * @param string $rule 1748 | * @param array $parameters 1749 | * 1750 | * @return string 1751 | */ 1752 | protected function replaceMin($message, $attribute, $rule, $parameters) 1753 | { 1754 | return str_replace(':min', $parameters[0], $message); 1755 | } 1756 | 1757 | /** 1758 | * Replace all place-holders for the max rule. 1759 | * 1760 | * @param string $message 1761 | * @param string $attribute 1762 | * @param string $rule 1763 | * @param array $parameters 1764 | * 1765 | * @return string 1766 | */ 1767 | protected function replaceMax($message, $attribute, $rule, $parameters) 1768 | { 1769 | return str_replace(':max', $parameters[0], $message); 1770 | } 1771 | 1772 | /** 1773 | * Replace all place-holders for the in rule. 1774 | * 1775 | * @param string $message 1776 | * @param string $attribute 1777 | * @param string $rule 1778 | * @param array $parameters 1779 | * 1780 | * @return string 1781 | */ 1782 | protected function replaceIn($message, $attribute, $rule, $parameters) 1783 | { 1784 | foreach ($parameters as &$parameter) { 1785 | $parameter = $this->getDisplayableValue($attribute, $parameter); 1786 | } 1787 | 1788 | return str_replace(':values', implode(', ', $parameters), $message); 1789 | } 1790 | 1791 | /** 1792 | * Replace all place-holders for the not_in rule. 1793 | * 1794 | * @param string $message 1795 | * @param string $attribute 1796 | * @param string $rule 1797 | * @param array $parameters 1798 | * 1799 | * @return string 1800 | */ 1801 | protected function replaceNotIn($message, $attribute, $rule, $parameters) 1802 | { 1803 | return $this->replaceIn($message, $attribute, $rule, $parameters); 1804 | } 1805 | 1806 | /** 1807 | * Replace all place-holders for the mimes rule. 1808 | * 1809 | * @param string $message 1810 | * @param string $attribute 1811 | * @param string $rule 1812 | * @param array $parameters 1813 | * 1814 | * @return string 1815 | */ 1816 | protected function replaceMimes($message, $attribute, $rule, $parameters) 1817 | { 1818 | return str_replace(':values', implode(', ', $parameters), $message); 1819 | } 1820 | 1821 | /** 1822 | * Replace all place-holders for the required_with rule. 1823 | * 1824 | * @param string $message 1825 | * @param string $attribute 1826 | * @param string $rule 1827 | * @param array $parameters 1828 | * 1829 | * @return string 1830 | */ 1831 | protected function replaceRequiredWith($message, $attribute, $rule, $parameters) 1832 | { 1833 | $parameters = $this->getAttributeList($parameters); 1834 | 1835 | return str_replace(':values', implode(' / ', $parameters), $message); 1836 | } 1837 | 1838 | /** 1839 | * Replace all place-holders for the required_with_all rule. 1840 | * 1841 | * @param string $message 1842 | * @param string $attribute 1843 | * @param string $rule 1844 | * @param array $parameters 1845 | * 1846 | * @return string 1847 | */ 1848 | protected function replaceRequiredWithAll($message, $attribute, $rule, $parameters) 1849 | { 1850 | return $this->replaceRequiredWith($message, $attribute, $rule, $parameters); 1851 | } 1852 | 1853 | /** 1854 | * Replace all place-holders for the required_without rule. 1855 | * 1856 | * @param string $message 1857 | * @param string $attribute 1858 | * @param string $rule 1859 | * @param array $parameters 1860 | * 1861 | * @return string 1862 | */ 1863 | protected function replaceRequiredWithout($message, $attribute, $rule, $parameters) 1864 | { 1865 | return $this->replaceRequiredWith($message, $attribute, $rule, $parameters); 1866 | } 1867 | 1868 | /** 1869 | * Replace all place-holders for the required_without_all rule. 1870 | * 1871 | * @param string $message 1872 | * @param string $attribute 1873 | * @param string $rule 1874 | * @param array $parameters 1875 | * 1876 | * @return string 1877 | */ 1878 | protected function replaceRequiredWithoutAll($message, $attribute, $rule, $parameters) 1879 | { 1880 | return $this->replaceRequiredWith($message, $attribute, $rule, $parameters); 1881 | } 1882 | 1883 | /** 1884 | * Replace all place-holders for the required_if rule. 1885 | * 1886 | * @param string $message 1887 | * @param string $attribute 1888 | * @param string $rule 1889 | * @param array $parameters 1890 | * 1891 | * @return string 1892 | */ 1893 | protected function replaceRequiredIf($message, $attribute, $rule, $parameters) 1894 | { 1895 | $parameters[1] = $this->getDisplayableValue($parameters[0], array_get($this->data, $parameters[0])); 1896 | 1897 | $parameters[0] = $this->getAttribute($parameters[0]); 1898 | 1899 | return str_replace([':other', ':value'], $parameters, $message); 1900 | } 1901 | 1902 | /** 1903 | * Replace all place-holders for the same rule. 1904 | * 1905 | * @param string $message 1906 | * @param string $attribute 1907 | * @param string $rule 1908 | * @param array $parameters 1909 | * 1910 | * @return string 1911 | */ 1912 | protected function replaceSame($message, $attribute, $rule, $parameters) 1913 | { 1914 | return str_replace(':other', $this->getAttribute($parameters[0]), $message); 1915 | } 1916 | 1917 | /** 1918 | * Replace all place-holders for the different rule. 1919 | * 1920 | * @param string $message 1921 | * @param string $attribute 1922 | * @param string $rule 1923 | * @param array $parameters 1924 | * 1925 | * @return string 1926 | */ 1927 | protected function replaceDifferent($message, $attribute, $rule, $parameters) 1928 | { 1929 | return $this->replaceSame($message, $attribute, $rule, $parameters); 1930 | } 1931 | 1932 | /** 1933 | * Replace all place-holders for the date_format rule. 1934 | * 1935 | * @param string $message 1936 | * @param string $attribute 1937 | * @param string $rule 1938 | * @param array $parameters 1939 | * 1940 | * @return string 1941 | */ 1942 | protected function replaceDateFormat($message, $attribute, $rule, $parameters) 1943 | { 1944 | return str_replace(':format', $parameters[0], $message); 1945 | } 1946 | 1947 | /** 1948 | * Replace all place-holders for the before rule. 1949 | * 1950 | * @param string $message 1951 | * @param string $attribute 1952 | * @param string $rule 1953 | * @param array $parameters 1954 | * 1955 | * @return string 1956 | */ 1957 | protected function replaceBefore($message, $attribute, $rule, $parameters) 1958 | { 1959 | if (!(strtotime($parameters[0]))) { 1960 | return str_replace(':date', $this->getAttribute($parameters[0]), $message); 1961 | } else { 1962 | return str_replace(':date', $parameters[0], $message); 1963 | } 1964 | } 1965 | 1966 | /** 1967 | * Replace all place-holders for the after rule. 1968 | * 1969 | * @param string $message 1970 | * @param string $attribute 1971 | * @param string $rule 1972 | * @param array $parameters 1973 | * 1974 | * @return string 1975 | */ 1976 | protected function replaceAfter($message, $attribute, $rule, $parameters) 1977 | { 1978 | return $this->replaceBefore($message, $attribute, $rule, $parameters); 1979 | } 1980 | 1981 | /** 1982 | * Determine if the given attribute has a rule in the given set. 1983 | * 1984 | * @param string $attribute 1985 | * @param string|array $rules 1986 | * 1987 | * @return bool 1988 | */ 1989 | protected function hasRule($attribute, $rules) 1990 | { 1991 | return !is_null($this->getRule($attribute, $rules)); 1992 | } 1993 | 1994 | /** 1995 | * Get a rule and its parameters for a given attribute. 1996 | * 1997 | * @param string $attribute 1998 | * @param string|array $rules 1999 | * 2000 | * @return array|null 2001 | */ 2002 | protected function getRule($attribute, $rules) 2003 | { 2004 | if (!array_key_exists($attribute, $this->rules)) { 2005 | return; 2006 | } 2007 | 2008 | $rules = (array) $rules; 2009 | 2010 | foreach ($this->rules[$attribute] as $rule) { 2011 | list($rule, $parameters) = $this->parseRule($rule); 2012 | 2013 | if (in_array($rule, $rules, true)) { 2014 | return [$rule, $parameters]; 2015 | } 2016 | } 2017 | } 2018 | 2019 | /** 2020 | * Extract the rule name and parameters from a rule. 2021 | * 2022 | * @param array|string $rules 2023 | * 2024 | * @return array 2025 | */ 2026 | protected function parseRule($rules) 2027 | { 2028 | if (is_array($rules)) { 2029 | return $this->parseArrayRule($rules); 2030 | } 2031 | 2032 | return $this->parseStringRule($rules); 2033 | } 2034 | 2035 | /** 2036 | * Parse an array based rule. 2037 | * 2038 | * @param array $rules 2039 | * 2040 | * @return array 2041 | */ 2042 | protected function parseArrayRule(array $rules) 2043 | { 2044 | return [studly_case(trim(array_get($rules, 0))), array_slice($rules, 1)]; 2045 | } 2046 | 2047 | /** 2048 | * Parse a string based rule. 2049 | * 2050 | * @param string $rules 2051 | * 2052 | * @return array 2053 | */ 2054 | protected function parseStringRule($rules) 2055 | { 2056 | $parameters = []; 2057 | 2058 | // The format for specifying validation rules and parameters follows an 2059 | // easy {rule}:{parameters} formatting convention. For instance the 2060 | // rule "Max:3" states that the value may only be three letters. 2061 | if (strpos($rules, ':') !== false) { 2062 | list($rules, $parameter) = explode(':', $rules, 2); 2063 | 2064 | $parameters = $this->parseParameters($rules, $parameter); 2065 | } 2066 | 2067 | return [studly_case(trim($rules)), $parameters]; 2068 | } 2069 | 2070 | /** 2071 | * Parse a parameter list. 2072 | * 2073 | * @param string $rule 2074 | * @param string $parameter 2075 | * 2076 | * @return array 2077 | */ 2078 | protected function parseParameters($rule, $parameter) 2079 | { 2080 | if (strtolower($rule) === 'regex') { 2081 | return [$parameter]; 2082 | } 2083 | 2084 | return str_getcsv($parameter); 2085 | } 2086 | 2087 | /** 2088 | * Get the array of custom validator extensions. 2089 | * 2090 | * 2091 | * @return array 2092 | */ 2093 | public function getExtensions() 2094 | { 2095 | return $this->extensions; 2096 | } 2097 | 2098 | /** 2099 | * Register an array of custom validator extensions. 2100 | * 2101 | * @param array $extensions 2102 | */ 2103 | public function addExtensions(array $extensions) 2104 | { 2105 | if ($extensions) { 2106 | $keys = array_map('Overtrue\Validation\snake_case', array_keys($extensions)); 2107 | 2108 | $extensions = array_combine($keys, array_values($extensions)); 2109 | } 2110 | 2111 | $this->extensions = array_merge($this->extensions, $extensions); 2112 | } 2113 | 2114 | /** 2115 | * Register an array of custom implicit validator extensions. 2116 | * 2117 | * @param array $extensions 2118 | */ 2119 | public function addImplicitExtensions(array $extensions) 2120 | { 2121 | $this->addExtensions($extensions); 2122 | 2123 | foreach ($extensions as $rule => $extension) { 2124 | $this->implicitRules[] = studly_case($rule); 2125 | } 2126 | } 2127 | 2128 | /** 2129 | * Register a custom validator extension. 2130 | * 2131 | * @param string $rule 2132 | * @param \Closure|string $extension 2133 | */ 2134 | public function addExtension($rule, $extension) 2135 | { 2136 | $this->extensions[snake_case($rule)] = $extension; 2137 | } 2138 | 2139 | /** 2140 | * Register a custom implicit validator extension. 2141 | * 2142 | * @param string $rule 2143 | * @param \Closure|string $extension 2144 | */ 2145 | public function addImplicitExtension($rule, $extension) 2146 | { 2147 | $this->addExtension($rule, $extension); 2148 | 2149 | $this->implicitRules[] = studly_case($rule); 2150 | } 2151 | 2152 | /** 2153 | * Get the array of custom validator message replacers. 2154 | * 2155 | * 2156 | * @return array 2157 | */ 2158 | public function getReplacers() 2159 | { 2160 | return $this->replacers; 2161 | } 2162 | 2163 | /** 2164 | * Register an array of custom validator message replacers. 2165 | * 2166 | * @param array $replacers 2167 | */ 2168 | public function addReplacers(array $replacers) 2169 | { 2170 | if ($replacers) { 2171 | $keys = array_map('Overtrue\Validation\snake_case', array_keys($replacers)); 2172 | 2173 | $replacers = array_combine($keys, array_values($replacers)); 2174 | } 2175 | 2176 | $this->replacers = array_merge($this->replacers, $replacers); 2177 | } 2178 | 2179 | /** 2180 | * Register a custom validator message replacer. 2181 | * 2182 | * @param string $rule 2183 | * @param \Closure|string $replacer 2184 | */ 2185 | public function addReplacer($rule, $replacer) 2186 | { 2187 | $this->replacers[snake_case($rule)] = $replacer; 2188 | } 2189 | 2190 | /** 2191 | * Get the data under validation. 2192 | * 2193 | * 2194 | * @return array 2195 | */ 2196 | public function getData() 2197 | { 2198 | return $this->data; 2199 | } 2200 | 2201 | /** 2202 | * Set the data under validation. 2203 | * 2204 | * @param array $data 2205 | */ 2206 | public function setData(array $data) 2207 | { 2208 | $this->data = $this->parseData($data); 2209 | } 2210 | 2211 | /** 2212 | * Get the validation rules. 2213 | * 2214 | * 2215 | * @return array 2216 | */ 2217 | public function getRules() 2218 | { 2219 | return $this->rules; 2220 | } 2221 | 2222 | /** 2223 | * Set the validation rules. 2224 | * 2225 | * @param array $rules 2226 | * 2227 | * @return $this 2228 | */ 2229 | public function setRules(array $rules) 2230 | { 2231 | $this->rules = $this->explodeRules($rules); 2232 | 2233 | return $this; 2234 | } 2235 | 2236 | /** 2237 | * Set the custom attributes on the validator. 2238 | * 2239 | * @param array $attributes 2240 | * 2241 | * @return $this 2242 | */ 2243 | public function setAttributeNames(array $attributes) 2244 | { 2245 | $this->customAttributes = $attributes; 2246 | 2247 | return $this; 2248 | } 2249 | 2250 | /** 2251 | * Set the custom values on the validator. 2252 | * 2253 | * @param array $values 2254 | * 2255 | * @return $this 2256 | */ 2257 | public function setValueNames(array $values) 2258 | { 2259 | $this->customValues = $values; 2260 | 2261 | return $this; 2262 | } 2263 | 2264 | /** 2265 | * Get the files under validation. 2266 | * 2267 | * 2268 | * @return array 2269 | */ 2270 | public function getFiles() 2271 | { 2272 | return $this->files; 2273 | } 2274 | 2275 | /** 2276 | * Set the files under validation. 2277 | * 2278 | * @param array $files 2279 | * 2280 | * @return $this 2281 | */ 2282 | public function setFiles(array $files) 2283 | { 2284 | $this->files = $files; 2285 | 2286 | return $this; 2287 | } 2288 | 2289 | /** 2290 | * Get the Presence Verifier implementation. 2291 | * 2292 | * 2293 | * @return \Overtrue\Validation\PresenceVerifierInterface 2294 | * 2295 | * @throws \RuntimeException 2296 | */ 2297 | public function getPresenceVerifier() 2298 | { 2299 | if (!isset($this->presenceVerifier)) { 2300 | throw new \RuntimeException('Presence verifier has not been set.'); 2301 | } 2302 | 2303 | return $this->presenceVerifier; 2304 | } 2305 | 2306 | /** 2307 | * Set the Presence Verifier implementation. 2308 | * 2309 | * @param \Overtrue\Validation\PresenceVerifierInterface $presenceVerifier 2310 | */ 2311 | public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier) 2312 | { 2313 | $this->presenceVerifier = $presenceVerifier; 2314 | } 2315 | 2316 | /** 2317 | * Get the Translator implementation. 2318 | * 2319 | * 2320 | * @return \Overtrue\Validation\TranslatorInterface 2321 | */ 2322 | public function getTranslator() 2323 | { 2324 | return $this->translator; 2325 | } 2326 | 2327 | /** 2328 | * Set the Translator implementation. 2329 | * 2330 | * @param \Overtrue\Validation\TranslatorInterface $translator 2331 | */ 2332 | public function setTranslator(TranslatorInterface $translator) 2333 | { 2334 | $this->translator = $translator; 2335 | } 2336 | 2337 | /** 2338 | * Get the custom messages for the validator. 2339 | * 2340 | * 2341 | * @return array 2342 | */ 2343 | public function getCustomMessages() 2344 | { 2345 | return $this->customMessages; 2346 | } 2347 | 2348 | /** 2349 | * Set the custom messages for the validator. 2350 | * 2351 | * @param array $messages 2352 | */ 2353 | public function setCustomMessages(array $messages) 2354 | { 2355 | $this->customMessages = array_merge($this->customMessages, $messages); 2356 | } 2357 | 2358 | /** 2359 | * Get the custom attributes used by the validator. 2360 | * 2361 | * 2362 | * @return array 2363 | */ 2364 | public function getCustomAttributes() 2365 | { 2366 | return $this->customAttributes; 2367 | } 2368 | 2369 | /** 2370 | * Add custom attributes to the validator. 2371 | * 2372 | * @param array $customAttributes 2373 | * 2374 | * @return $this 2375 | */ 2376 | public function addCustomAttributes(array $customAttributes) 2377 | { 2378 | $this->customAttributes = array_merge($this->customAttributes, $customAttributes); 2379 | 2380 | return $this; 2381 | } 2382 | 2383 | /** 2384 | * Get the custom values for the validator. 2385 | * 2386 | * 2387 | * @return array 2388 | */ 2389 | public function getCustomValues() 2390 | { 2391 | return $this->customValues; 2392 | } 2393 | 2394 | /** 2395 | * Add the custom values for the validator. 2396 | * 2397 | * @param array $customValues 2398 | * 2399 | * @return $this 2400 | */ 2401 | public function addCustomValues(array $customValues) 2402 | { 2403 | $this->customValues = array_merge($this->customValues, $customValues); 2404 | 2405 | return $this; 2406 | } 2407 | 2408 | /** 2409 | * Get the fallback messages for the validator. 2410 | * 2411 | * 2412 | * @return array 2413 | */ 2414 | public function getFallbackMessages() 2415 | { 2416 | return $this->fallbackMessages; 2417 | } 2418 | 2419 | /** 2420 | * Set the fallback messages for the validator. 2421 | * 2422 | * @param array $messages 2423 | */ 2424 | public function setFallbackMessages(array $messages) 2425 | { 2426 | $this->fallbackMessages = $messages; 2427 | } 2428 | 2429 | /** 2430 | * Get the failed validation rules. 2431 | * 2432 | * 2433 | * @return array 2434 | */ 2435 | public function failed() 2436 | { 2437 | if (!$this->messages) { 2438 | $this->passes(); 2439 | } 2440 | 2441 | return $this->failedRules; 2442 | } 2443 | 2444 | /** 2445 | * Get the message container for the validator. 2446 | * 2447 | * 2448 | * @return \Overtrue\Validation\MessageBag 2449 | */ 2450 | public function messages() 2451 | { 2452 | if (!$this->messages) { 2453 | $this->passes(); 2454 | } 2455 | 2456 | return $this->messages; 2457 | } 2458 | 2459 | /** 2460 | * An alternative more semantic shortcut to the message container. 2461 | * 2462 | * 2463 | * @return \Overtrue\Validation\MessageBag 2464 | */ 2465 | public function errors() 2466 | { 2467 | return $this->messages(); 2468 | } 2469 | 2470 | /** 2471 | * Call a custom validator extension. 2472 | * 2473 | * @param string $rule 2474 | * @param array $parameters 2475 | * 2476 | * @return bool 2477 | */ 2478 | protected function callExtension($rule, $parameters) 2479 | { 2480 | $callback = $this->extensions[$rule]; 2481 | 2482 | if ($callback instanceof Closure) { 2483 | return call_user_func_array($callback, $parameters); 2484 | } elseif (is_string($callback)) { 2485 | return $this->callClassBasedExtension($callback, $parameters); 2486 | } 2487 | } 2488 | 2489 | /** 2490 | * Call a class based validator extension. 2491 | * 2492 | * @param string $callback 2493 | * @param array $parameters 2494 | * 2495 | * @return bool 2496 | */ 2497 | protected function callClassBasedExtension($callback, $parameters) 2498 | { 2499 | list($class, $method) = explode('@', $callback); 2500 | 2501 | return call_user_func_array([$class, $method], $parameters); 2502 | } 2503 | 2504 | /** 2505 | * Call a custom validator message replacer. 2506 | * 2507 | * @param string $message 2508 | * @param string $attribute 2509 | * @param string $rule 2510 | * @param array $parameters 2511 | * 2512 | * @return string 2513 | */ 2514 | protected function callReplacer($message, $attribute, $rule, $parameters) 2515 | { 2516 | $callback = $this->replacers[$rule]; 2517 | 2518 | if ($callback instanceof Closure) { 2519 | return call_user_func_array($callback, func_get_args()); 2520 | } elseif (is_string($callback)) { 2521 | return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters); 2522 | } 2523 | } 2524 | 2525 | /** 2526 | * Call a class based validator message replacer. 2527 | * 2528 | * @param string $callback 2529 | * @param string $message 2530 | * @param string $attribute 2531 | * @param string $rule 2532 | * @param array $parameters 2533 | * 2534 | * @return string 2535 | */ 2536 | protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters) 2537 | { 2538 | list($class, $method) = explode('@', $callback); 2539 | 2540 | return call_user_func_array([$class, $method], array_slice(func_get_args(), 1)); 2541 | } 2542 | 2543 | /** 2544 | * Require a certain number of parameters to be present. 2545 | * 2546 | * @param int $count 2547 | * @param array $parameters 2548 | * @param string $rule 2549 | * 2550 | * @throws \InvalidArgumentException 2551 | */ 2552 | protected function requireParameterCount($count, $parameters, $rule) 2553 | { 2554 | if (count($parameters) < $count) { 2555 | throw new \InvalidArgumentException("Validation rule $rule requires at least $count parameters."); 2556 | } 2557 | } 2558 | 2559 | /** 2560 | * Handle dynamic calls to class methods. 2561 | * 2562 | * @param string $method 2563 | * @param array $parameters 2564 | * 2565 | * @return mixed 2566 | * 2567 | * @throws \BadMethodCallException 2568 | */ 2569 | public function __call($method, $parameters) 2570 | { 2571 | $rule = snake_case(substr($method, 8)); 2572 | 2573 | if (isset($this->extensions[$rule])) { 2574 | return $this->callExtension($rule, $parameters); 2575 | } 2576 | 2577 | throw new \BadMethodCallException("Method [$method] does not exist."); 2578 | } 2579 | } 2580 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Overtrue\Validation; 13 | 14 | use ArrayAccess; 15 | use Closure; 16 | 17 | /** 18 | * Return array specific item. 19 | * 20 | * @param array $array 21 | * @param string $key 22 | * @param mixed $default 23 | * 24 | * @return mixed 25 | */ 26 | function array_get($array, $key, $default = null) 27 | { 28 | if (!array_accessible($array)) { 29 | return value($default); 30 | } 31 | 32 | if (is_null($key)) { 33 | return $array; 34 | } 35 | 36 | if (array_exists($array, $key)) { 37 | return $array[$key]; 38 | } 39 | 40 | foreach (explode('.', $key) as $segment) { 41 | if (array_accessible($array) && array_exists($array, $segment)) { 42 | $array = $array[$segment]; 43 | } else { 44 | return value($default); 45 | } 46 | } 47 | 48 | return $array; 49 | } 50 | 51 | /** 52 | * Flatten a multi-dimensional associative array with dots. 53 | * 54 | * @param array $array 55 | * @param string $prepend 56 | * @return array 57 | */ 58 | function array_dot($array, $prepend = '') 59 | { 60 | $results = []; 61 | 62 | foreach ($array as $key => $value) { 63 | if (is_array($value)) { 64 | $results = array_merge($results, array_dot($value, $prepend.$key.'.')); 65 | } else { 66 | $results[$prepend.$key] = $value; 67 | } 68 | } 69 | 70 | return $results; 71 | } 72 | 73 | /** 74 | * Check input is array accessable. 75 | * 76 | * @param mixed $value 77 | * 78 | * @return bool 79 | */ 80 | function array_accessible($value) 81 | { 82 | return is_array($value) || $value instanceof ArrayAccess; 83 | } 84 | 85 | /** 86 | * Check array key exists. 87 | * 88 | * @param array $array 89 | * @param string $key 90 | * 91 | * @return bool 92 | */ 93 | function array_exists($array, $key) 94 | { 95 | if ($array instanceof ArrayAccess) { 96 | return $array->offsetExists($key); 97 | } 98 | 99 | return array_key_exists($key, $array); 100 | } 101 | 102 | /** 103 | * Convert a string to snake case. 104 | * 105 | * @param string $string 106 | * @param string $delimiter 107 | * 108 | * @return string 109 | */ 110 | function snake_case($string, $delimiter = '_') 111 | { 112 | $replace = '$1'.$delimiter.'$2'; 113 | 114 | return ctype_lower($string) ? $string : strtolower(preg_replace('/(.)([A-Z])/', $replace, $string)); 115 | } 116 | 117 | /** 118 | * Convert a value to studly caps case. 119 | * 120 | * @param string $string 121 | * 122 | * @return string 123 | */ 124 | function studly_case($string) 125 | { 126 | $string = ucwords(str_replace(['-', '_'], ' ', $string)); 127 | 128 | return str_replace(' ', '', $string); 129 | } 130 | 131 | /** 132 | * Return the default value of the given value. 133 | * 134 | * @param mixed $value 135 | * @return mixed 136 | */ 137 | function value($value) 138 | { 139 | return $value instanceof Closure ? $value() : $value; 140 | } -------------------------------------------------------------------------------- /tests/ValidatorTest.php: -------------------------------------------------------------------------------- 1 | validator = new Factory(); 12 | } 13 | 14 | /** 15 | * @dataProvider cases 16 | * 17 | * @return 18 | */ 19 | public function testCases($input, $rule, $status, $message = null) 20 | { 21 | $validator = $this->validator->make($input, $rule); 22 | 23 | $this->assertEquals($status, $validator->passes()); 24 | 25 | if (!$status) { 26 | $this->assertSame($message, $validator->messages()->first()); 27 | } 28 | } 29 | 30 | public function cases() 31 | { 32 | return [ 33 | // sometimes 34 | [ 35 | ['foo' => 'foo'], 36 | ['foo' => 'sometimes'], 37 | true, 38 | ], 39 | [ 40 | ['foo' => false], 41 | ['foo' => 'sometimes'], 42 | true, 43 | ], 44 | 45 | // required 46 | [ 47 | ['foo' => 'foo', 'bar' => 1, 'baz' => 0, 'overtrue' => array('0')], 48 | ['foo' => 'required', 'bar' => 'required', 'baz' => 'required', 'overtrue' => 'required'], 49 | true, 50 | ], 51 | [ 52 | ['foo' => ' ', 'bar' => array()], 53 | ['foo' => 'required'], 54 | false, 55 | 'The foo field is required.' 56 | ], 57 | [ 58 | ['bar' => array()], 59 | ['bar' => 'required'], 60 | false, 61 | 'The bar field is required.' 62 | ], 63 | 64 | // filled 65 | [ 66 | [], 67 | ['foo' => 'filled'], 68 | true, 69 | ], 70 | [ 71 | ['bar' => ''], 72 | ['bar' => 'required|filled'], 73 | false, 74 | 'The bar field is required.' 75 | ], 76 | 77 | // required_with 78 | [ 79 | ['bar' => ''], 80 | ['bar' => 'required_with:foo'], 81 | true, 82 | ], 83 | [ 84 | ['foo' => 'overtrue'], 85 | ['bar' => 'required_with:foo'], 86 | false, 87 | 'The bar field is required when foo is present.', 88 | ], 89 | 90 | // required_with_all 91 | [ 92 | ['baz' => ''], 93 | ['bar' => 'required_with_all:foo,baz'], 94 | true, 95 | ], 96 | [ 97 | ['foo' => 'overtrue', 'baz' => 123], 98 | ['bar' => 'required_with_all:foo,baz'], 99 | false, 100 | 'The bar field is required when foo / baz is present.', 101 | ], 102 | 103 | // required_without 104 | [ 105 | ['foo' => 'overtrue'], 106 | ['bar' => 'required_without:foo'], 107 | true, 108 | ], 109 | [ 110 | ['foo' => '', 'baz' => 123], 111 | ['bar' => 'required_without:foo'], 112 | false, 113 | 'The bar field is required when foo is not present.', 114 | ], 115 | 116 | // required_without_all 117 | [ 118 | ['bar' => 123], 119 | ['bar' => 'required_without_all:foo,baz'], 120 | true, 121 | ], 122 | [ 123 | ['foo' => '', 'baz' => ''], 124 | ['bar' => 'required_without_all:foo,baz'], 125 | false, 126 | 'The bar field is required when none of foo / baz are present.', 127 | ], 128 | 129 | // required_if 130 | [ 131 | ['foo' => 1], 132 | ['bar' => 'required_if:foo,2'], 133 | true 134 | ], 135 | [ 136 | ['foo' => 1], 137 | ['bar' => 'required_if:foo,1'], 138 | false, 139 | 'The bar field is required when foo is 1.', 140 | ], 141 | 142 | // comfirmed 143 | [ 144 | ['password' => 'foo', 'password_confirmation' => 'foo'], 145 | ['password' => 'confirmed'], 146 | true, 147 | ], 148 | 149 | [ 150 | ['password' => 'foo'], 151 | ['password' => 'confirmed'], 152 | false, 153 | 'The password confirmation does not match.', 154 | ], 155 | [ 156 | ['password' => 'foo', 'password_confirmation' => 'foo'], 157 | ['password' => 'confirmed'], 158 | true, 159 | ], 160 | 161 | // same 162 | [ 163 | ['foo' => 'hello', 'bar' => 'hello'], 164 | ['foo' => 'same:bar'], 165 | true, 166 | ], 167 | [ 168 | ['foo' => 'overtrue', 'bar' => 'hello'], 169 | ['foo' => 'same:bar'], 170 | false, 171 | 'The foo and bar must match.', 172 | ], 173 | [ 174 | ['foo' => 'overtrue', 'bar' => ''], 175 | ['foo' => 'same:bar'], 176 | false, 177 | 'The foo and bar must match.', 178 | ], 179 | 180 | // different 181 | [ 182 | ['foo' => 'overtrue', 'bar' => 'hello'], 183 | ['foo' => 'different:bar'], 184 | true, 185 | ], 186 | [ 187 | ['foo' => 'a', 'bar' => 'a'], 188 | ['foo' => 'different:bar'], 189 | false, 190 | 'The foo and bar must be different.', 191 | ], 192 | 193 | // accepted 194 | [ 195 | ['foo' => 'yes', 'bar' => 'on', 'one' => 1, 'two' => true, 'three' => 'true'], 196 | [ 197 | 'foo' => 'accepted', 198 | 'bar' => 'accepted', 199 | 'one' => 'accepted', 200 | 'two' => 'accepted', 201 | 'three' => 'accepted', 202 | ], 203 | true, 204 | ], 205 | [ 206 | ['foo' => 'onn'], 207 | ['foo' => 'accepted'], 208 | false, 209 | 'The foo must be accepted.', 210 | ], 211 | [ 212 | ['foo' => 'off'], 213 | ['foo' => 'accepted'], 214 | false, 215 | 'The foo must be accepted.', 216 | ], 217 | 218 | // boolean 219 | [ 220 | [ 221 | 'one' => false, 222 | 'two' => 1, 223 | 'three' => '1', 224 | 'four' => true, 225 | 'five' => 0, 226 | 'six' => '0', 227 | ], 228 | [ 229 | 'one' => 'boolean', 230 | 'two' => 'boolean', 231 | 'three' => 'boolean', 232 | 'four' => 'boolean', 233 | 'five' => 'boolean', 234 | 'six' => 'boolean', 235 | ], 236 | true, 237 | ], 238 | [ 239 | ['foo' => 'onn'], 240 | ['foo' => 'boolean'], 241 | false, 242 | 'The foo field must be true or false', 243 | ], 244 | [ 245 | ['foo' => 'off'], 246 | ['foo' => 'boolean'], 247 | false, 248 | 'The foo field must be true or false', 249 | ], 250 | 251 | // array 252 | [ 253 | ['foo' => []], 254 | ['foo' => 'array'], 255 | true 256 | ], 257 | [ 258 | ['foo' => [1,2,3]], 259 | ['foo' => 'array'], 260 | true 261 | ], 262 | [ 263 | ['foo' => 'string'], 264 | ['foo' => 'array'], 265 | false, 266 | 'The foo must be an array.', 267 | ], 268 | // numeric 269 | [ 270 | ['foo' => 1], 271 | ['foo' => 'numeric'], 272 | true 273 | ], 274 | [ 275 | ['foo' => '2356'], 276 | ['foo' => 'numeric'], 277 | true 278 | ], 279 | [ 280 | ['foo' => '1234string'], 281 | ['foo' => 'numeric'], 282 | false, 283 | 'The foo must be a number.', 284 | ], 285 | [ 286 | ['foo' => 'string1234'], 287 | ['foo' => 'numeric'], 288 | false, 289 | 'The foo must be a number.', 290 | ], 291 | 292 | // integer 293 | [ 294 | ['foo' => 0, 'bar' => 1, 'baz' => -3], 295 | ['foo' => 'integer'], 296 | true 297 | ], 298 | [ 299 | ['foo' => 1234.5], 300 | ['foo' => 'integer'], 301 | false, 302 | 'The foo must be an integer.', 303 | ], 304 | [ 305 | ['foo' => '000'], 306 | ['foo' => 'integer'], 307 | false, 308 | 'The foo must be an integer.', 309 | ], 310 | 311 | // digits 312 | [ 313 | ['foo' => 0, 'bar' => 1, 'baz' => '3', 'baba' => '0'], 314 | ['foo' => 'digits:1'], 315 | true 316 | ], 317 | [ 318 | ['foo' => 999999999999], 319 | ['foo' => 'digits:12'], 320 | true, 321 | ], 322 | [ 323 | ['foo' => '1234string'], 324 | ['foo' => 'digits:4'], 325 | false, 326 | 'The foo must be 4 digits.', 327 | ], 328 | 329 | // digits_between 330 | [ 331 | ['foo' => 0, 'bar' => 134, 'baz' => '32345', 'baba' => '0'], 332 | [ 333 | 'foo' => 'digits_between:1,5', 334 | 'bar' => 'digits_between:1,5', 335 | 'baz' => 'digits_between:1,5', 336 | 'baba' => 'digits_between:1,5', 337 | ], 338 | true 339 | ], 340 | [ 341 | ['foo' => 123], 342 | ['foo' => 'digits_between:1,3'], 343 | true, 344 | ], 345 | [ 346 | ['foo' => '1234'], 347 | ['foo' => 'digits_between:2,3'], 348 | false, 349 | 'The foo must be between 2 and 3 digits.', 350 | ], 351 | 352 | // size 353 | [ 354 | ['foo' => 12, 'bar' => [3,45], 'one' => 'ab'], 355 | ['foo' => 'size:2', 'bar' => 'size:2', 'one' => 'size:2'], 356 | true 357 | ], 358 | [ 359 | ['foo' => 123], 360 | ['foo' => 'size:1'], 361 | false, 362 | 'The foo must be 1 characters.' 363 | ], 364 | 365 | // between 366 | [ 367 | ['foo' => 12, 'bar' => [3,45], 'one' => 'ab'], 368 | ['foo' => 'between:2,12', 'bar' => 'between:2,4', 'one' => 'between:1,2'], 369 | true 370 | ], 371 | [ 372 | ['foo' => 123], 373 | ['foo' => 'numeric|between:1,2'], 374 | false, 375 | 'The foo must be between 1 and 2.' 376 | ], 377 | [ 378 | ['foo' => [1,2,3,4]], 379 | ['foo' => 'array|between:1,2'], 380 | false, 381 | 'The foo must have between 1 and 2 items.' 382 | ], 383 | [ 384 | ['foo' => 'item'], 385 | ['foo' => 'between:2,3'], 386 | false, 387 | 'The foo must be between 2 and 3 characters.' 388 | ], 389 | 390 | // min 391 | [ 392 | ['foo' => 1, 'bar' => [1,2], 'baz' => 'abc'], 393 | ['foo' => 'numeric|min:0', 'bar' => 'array|min:2', 'baz' => 'min:3'], 394 | true, 395 | ], 396 | [ 397 | ['foo' => 1,], 398 | ['foo' => 'numeric|min:2'], 399 | false, 400 | 'The foo must be at least 2.' 401 | ], 402 | [ 403 | ['foo' => [1],], 404 | ['foo' => 'array|min:2'], 405 | false, 406 | 'The foo must have at least 2 items.' 407 | ], 408 | 409 | // max 410 | [ 411 | ['foo' => 1, 'bar' => [1,2], 'baz' => 'abc'], 412 | ['foo' => 'numeric|max:1', 'bar' => 'array|max:2', 'baz' => 'max:4'], 413 | true, 414 | ], 415 | [ 416 | ['foo' => 1,], 417 | ['foo' => 'numeric|max:0'], 418 | false, 419 | 'The foo may not be greater than 0.' 420 | ], 421 | [ 422 | ['foo' => [1,5],], 423 | ['foo' => 'array|max:1'], 424 | false, 425 | 'The foo may not have more than 1 items.' 426 | ], 427 | 428 | // in 429 | [ 430 | ['foo' => 1, 'bar' => 'abc'], 431 | ['foo' => 'in:1,2,3', 'bar' => 'in:abc,def'], 432 | true, 433 | ], 434 | [ 435 | ['foo' => 1 ], 436 | ['foo' => 'in:2,3'], 437 | false, 438 | 'The selected foo is invalid.', 439 | ], 440 | [ 441 | ['foo' => 'abc' ], 442 | ['foo' => 'in:cde,def'], 443 | false, 444 | 'The selected foo is invalid.', 445 | ], 446 | 447 | // not_in 448 | [ 449 | ['foo' => 1, 'bar' => 'abc'], 450 | ['foo' => 'not_in:2,3', 'bar' => 'not_in:bcd,def'], 451 | true, 452 | ], 453 | [ 454 | ['foo' => 1 ], 455 | ['foo' => 'not_in:1,2'], 456 | false, 457 | 'The selected foo is invalid.', 458 | ], 459 | [ 460 | ['foo' => 'abc' ], 461 | ['foo' => 'not_in:abc,def'], 462 | false, 463 | 'The selected foo is invalid.', 464 | ], 465 | 466 | // ip 467 | [ 468 | ['ip_address' => '127.0.0.1'], 469 | ['ip_address' => 'ip'], 470 | true, 471 | ], 472 | [ 473 | ['ip_address' => '127.0.0.1.1'], 474 | ['ip_address' => 'ip'], 475 | false, 476 | 'The ip address must be a valid IP address.', 477 | ], 478 | 479 | // email 480 | [ 481 | ['email_address' => 'foo@bar.com', 'foo' => 'f.bar@bar.com'], 482 | ['email_address' => 'email', 'foo' => 'email'], 483 | true, 484 | ], 485 | [ 486 | ['email_address' => '127.0.0.1.1'], 487 | ['email_address' => 'email'], 488 | false, 489 | 'The email address must be a valid email address.', 490 | ], 491 | 492 | // url 493 | [ 494 | ['foo' => 'http://abc.m', 'bar' => 'https://f.bar.com', 'ftp' => 'ftp://a.c.com'], 495 | ['foo' => 'url', 'bar' => 'url', 'ftp' => 'url'], 496 | true, 497 | ], 498 | [ 499 | ['foo' => 'abc.com'], 500 | ['foo' => 'url'], 501 | false, 502 | 'The foo format is invalid.', 503 | ], 504 | 505 | // active_url 506 | [ 507 | ['foo' => 'http://google.com', 'bar' => 'https://ip.com'], 508 | ['foo' => 'active_url', 'bar' => 'active_url'], 509 | true, 510 | ], 511 | [ 512 | ['foo' => 'thisisanonexistswebsite.com'], 513 | ['foo' => 'active_url'], 514 | false, 515 | 'The foo is not a valid URL.', 516 | ], 517 | 518 | // alpha 519 | [ 520 | ['foo' => 'abcd', 'bar' => '汉字', 'baz' => 'Пароль'], 521 | ['foo' => 'alpha', 'bar' => 'alpha', 'baz' => 'alpha'], 522 | true, 523 | ], 524 | 525 | [ 526 | ['foo' => '123abcd'], 527 | ['foo' => 'alpha'], 528 | false, 529 | 'The foo may only contain letters.', 530 | ], 531 | 532 | // alpha_num 533 | [ 534 | ['foo' => 'a13bcd', 'bar' => '汉2字', 'baz' => '123Пароль3'], 535 | ['foo' => 'alpha_num', 'bar' => 'alpha_num', 'baz' => 'alpha_num'], 536 | true, 537 | ], 538 | 539 | [ 540 | ['foo' => '123abcd.w'], 541 | ['foo' => 'alpha_num'], 542 | false, 543 | 'The foo may only contain letters and numbers.', 544 | ], 545 | 546 | // alpha_dash 547 | [ 548 | ['foo' => 'a13b_cd', 'bar' => '汉2字_', 'baz' => '123П__ароль3'], 549 | ['foo' => 'alpha_dash', 'bar' => 'alpha_dash', 'baz' => 'alpha_dash'], 550 | true, 551 | ], 552 | 553 | [ 554 | ['foo' => '123ab_cd.w'], 555 | ['foo' => 'alpha_dash'], 556 | false, 557 | 'The foo may only contain letters, numbers, and dashes.', 558 | ], 559 | 560 | // regex 561 | [ 562 | ['foo' => 'a13b_cd', 'bar' => '汉2字_', 'baz' => '123П__ароль3'], 563 | ['foo' => 'regex:/^[a-z_13]+$/', 'bar' => 'regex:/^[_2\p{Han}]+$/u', 'baz' => 'regex:/^[\p{L}_0-9]+$/u'], 564 | true, 565 | ], 566 | 567 | [ 568 | ['foo' => '123ab_cd.w'], 569 | ['foo' => 'regex:/^[a-z]+$/'], 570 | false, 571 | 'The foo format is invalid.', 572 | ], 573 | 574 | // date 575 | [ 576 | ['foo' => '2016-03-06', 'bar' => '1970/09/08 12:23:00'], 577 | ['foo' => 'date', 'bar' => 'date'], 578 | true, 579 | ], 580 | [ 581 | ['foo' => '+1 days'], 582 | ['foo' => 'date'], 583 | false, 584 | 'The foo is not a valid date.', 585 | ], 586 | [ 587 | ['foo' => '2016年 4月27日 星期三 22时08分39秒 CST'], 588 | ['foo' => 'date'], 589 | false, 590 | 'The foo is not a valid date.', 591 | ], 592 | [ 593 | ['foo' => '2016 12'], 594 | ['foo' => 'date'], 595 | false, 596 | 'The foo is not a valid date.', 597 | ], 598 | 599 | // date_format 600 | [ 601 | ['foo' => '2016-03-06', 'bar' => '1970/09/08 12:23:00'], 602 | ['foo' => 'date_format:Y-m-d', 'bar' => 'date_format:Y/m/d H:i:s'], 603 | true, 604 | ], 605 | [ 606 | ['foo' => '1970/09/08 12:23:00'], 607 | ['foo' => 'date_format:Y-m-d'], 608 | false, 609 | 'The foo does not match the format Y-m-d.', 610 | ], 611 | 612 | // before & after 613 | [ 614 | ['foo' => '2016-03-04', 'bar' => '2016-05-06 12:06', 'baz' => '2016-05-06 12:06:02'], 615 | ['foo' => 'before:2016-03-04 01:00:00', 'bar' => 'before:2016-05-06 12:06:01', 'baz' => 'before:2016-05-06 12:06:03'], 616 | true, 617 | ], 618 | [ 619 | ['start' => '2013-01-02', 'end' => '2014-02-02'], 620 | ['start' => 'before:end', 'end' => 'after:start'], 621 | true, 622 | ], 623 | [ 624 | ['foo' => '2016-03-04'], 625 | ['foo' => 'before:2016-03-04 00:00:00'], 626 | false, 627 | 'The foo must be a date before 2016-03-04 00:00:00.', 628 | ], 629 | 630 | // after 631 | [ 632 | ['foo' => '2016-03-04', 'bar' => '2016-05-06 12:06', 'baz' => '2016-05-06 12:06:02'], 633 | ['foo' => 'after:2016-03-03 23:59:59', 'bar' => 'after:2016-05-06 12:05:59', 'baz' => 'after:2016-05-06 12:06:01'], 634 | true, 635 | ], 636 | [ 637 | ['foo' => '2016-03-04'], 638 | ['foo' => 'after:2016-03-04 00:00:00'], 639 | false, 640 | 'The foo must be a date after 2016-03-04 00:00:00.', 641 | ], 642 | 643 | // timezone 644 | [ 645 | ['zone' => 'PRC', 'zone' => 'asia/chongqing', 'UTC'], 646 | ['zone' => 'timezone'], 647 | true, 648 | ], 649 | [ 650 | ['zone' => 'not_a_time_zone'], 651 | ['zone' => 'timezone'], 652 | false, 653 | 'The zone must be a valid zone.', 654 | ], 655 | ]; 656 | } 657 | } --------------------------------------------------------------------------------