├── src ├── ValidatingModel.php ├── Injectors │ ├── UniqueWithInjector.php │ └── UniqueInjector.php ├── ValidationException.php ├── ValidatingObserver.php ├── ValidatingInterface.php └── ValidatingTrait.php ├── composer.json ├── LICENSE.txt ├── .github └── workflows │ └── run-tests.yml └── README.md /src/ValidatingModel.php: -------------------------------------------------------------------------------- 1 | getErrors(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Injectors/UniqueWithInjector.php: -------------------------------------------------------------------------------- 1 | exists) { 19 | // If the identifier isn't set, add it. 20 | if (count($parameters) < 3 || ! preg_match('/^\d+(\s?=\s?\w*)?$/', last($parameters))) { 21 | $parameters[] = $this->getModel()->getKey(); 22 | } 23 | } 24 | 25 | return 'unique_with:' . implode(',', $parameters); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "watson/validating", 3 | "description": "Eloquent model validating trait.", 4 | "keywords": [ 5 | "laravel", 6 | "eloquent", 7 | "validation" 8 | ], 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Dwight Watson", 13 | "email": "dwight@studiousapp.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.1", 18 | "illuminate/contracts": "~9.0|~10.0|~11.0|~12.0", 19 | "illuminate/database": "~9.0|~10.0|~11.0|~12.0", 20 | "illuminate/events": "~9.0|~10.0|~11.0|~12.0", 21 | "illuminate/support": "~9.0|~10.0|~11.0|~12.0", 22 | "illuminate/validation": "~9.0|~10.0|~11.0|~12.0" 23 | }, 24 | "require-dev": { 25 | "mockery/mockery": "^1.4.4", 26 | "phpunit/phpunit": "~9.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Watson\\Validating\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Watson\\Validating\\Tests\\": "tests/" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dwight Watson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: ['push'] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | fail-fast: true 10 | matrix: 11 | os: [ubuntu-latest, windows-latest] 12 | php: [8.1, 8.2] 13 | laravel: [9.*, 10.*] 14 | stability: [prefer-lowest, prefer-stable] 15 | include: 16 | - laravel: 9.* 17 | - laravel: 10.* 18 | 19 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v2 24 | 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php }} 29 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 30 | coverage: none 31 | 32 | - name: Install dependencies 33 | run: | 34 | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update 35 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 36 | 37 | - name: Execute tests 38 | run: vendor/bin/phpunit -------------------------------------------------------------------------------- /src/Injectors/UniqueInjector.php: -------------------------------------------------------------------------------- 1 | getModel()->getTable(); 20 | } 21 | 22 | // If the connection name isn't set but exists, infer it. 23 | if ((strpos($parameters[0], '.') === false) && (($connectionName = $this->getModel()->getConnectionName()) !== null)) { 24 | $parameters[0] = $connectionName.'.'.$parameters[0]; 25 | } 26 | 27 | // If the field name isn't get, infer it. 28 | if (! isset($parameters[1])) { 29 | $parameters[1] = $field; 30 | } 31 | 32 | if ($this->exists) { 33 | // If the identifier isn't set, infer it. 34 | if (! isset($parameters[2]) || strtolower($parameters[2]) === 'null') { 35 | $parameters[2] = $this->getModel()->getKey(); 36 | } 37 | 38 | // If the primary key isn't set, infer it. 39 | if (! isset($parameters[3])) { 40 | $parameters[3] = $this->getModel()->getKeyName(); 41 | } 42 | 43 | // If the additional where clause isn't set, infer it. 44 | // Example: unique:users,email,123,id,username,NULL 45 | foreach ($parameters as $key => $parameter) { 46 | if (strtolower((string) $parameter) === 'null') { 47 | // Maintain NULL as string in case the model returns a null value 48 | $value = $this->getModel()->{$parameters[$key - 1]}; 49 | $parameters[$key] = is_null($value) ? 'NULL' : $value; 50 | } 51 | } 52 | } 53 | 54 | return 'unique:' . implode(',', $parameters); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ValidationException.php: -------------------------------------------------------------------------------- 1 | model = $model; 31 | } 32 | 33 | /** 34 | * Get the model with validation errors. 35 | * 36 | * @return \Illuminate\Database\Eloquent\Model 37 | */ 38 | public function model() 39 | { 40 | return $this->model; 41 | } 42 | 43 | /** 44 | * Get the mdoel with validation errors. 45 | * 46 | * @return \Illuminate\Database\Eloquent\Model 47 | */ 48 | public function getModel() 49 | { 50 | return $this->model(); 51 | } 52 | 53 | /** 54 | * Get the validation errors. 55 | * 56 | * @return \Illuminate\Contracts\Support\Messagebag 57 | */ 58 | public function errors() 59 | { 60 | return $this->validator->errors(); 61 | } 62 | 63 | /** 64 | * Get the validation errors. 65 | * 66 | * @return \Illuminate\Contracts\Support\MessageBag 67 | */ 68 | public function getErrors() 69 | { 70 | return $this->errors(); 71 | } 72 | 73 | /** 74 | * Get the messages for the instance. 75 | * 76 | * @return \Illuminate\Contracts\Support\MessageBag 77 | */ 78 | public function getMessageBag() 79 | { 80 | return $this->errors(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/ValidatingObserver.php: -------------------------------------------------------------------------------- 1 | performValidation($model, 'saving'); 21 | } 22 | 23 | /** 24 | * Register the validation event for restoring the model. 25 | * 26 | * @param \Illuminate\Database\Eloquent\Model $model 27 | * @return boolean 28 | */ 29 | public function restoring(Model $model) 30 | { 31 | return $this->performValidation($model, 'restoring'); 32 | } 33 | 34 | /** 35 | * Perform validation with the specified ruleset. 36 | * 37 | * @param \Illuminate\Database\Eloquent\Model $model 38 | * @param string $event 39 | * @return boolean 40 | */ 41 | protected function performValidation(Model $model, $event) 42 | { 43 | // If the model has validating enabled, perform it. 44 | if ($model->getValidating()) { 45 | // Fire the namespaced validating event and prevent validation 46 | // if it returns a value. 47 | if ($this->fireValidatingEvent($model, $event) !== null) { 48 | return; 49 | } 50 | 51 | if ($model->isValid() === false) { 52 | // Fire the validating failed event. 53 | $this->fireValidatedEvent($model, 'failed'); 54 | 55 | if ($model->getThrowValidationExceptions()) { 56 | $model->throwValidationException(); 57 | } 58 | 59 | return false; 60 | } 61 | // Fire the validating.passed event. 62 | $this->fireValidatedEvent($model, 'passed'); 63 | } else { 64 | $this->fireValidatedEvent($model, 'skipped'); 65 | } 66 | } 67 | 68 | /** 69 | * Fire the namespaced validating event. 70 | * 71 | * @param \Illuminate\Database\Eloquent\Model $model 72 | * @param string $event 73 | * @return mixed 74 | */ 75 | protected function fireValidatingEvent(Model $model, $event) 76 | { 77 | return Event::until("eloquent.validating: ".get_class($model), [$model, $event]); 78 | } 79 | 80 | /** 81 | * Fire the namespaced post-validation event. 82 | * 83 | * @param \Illuminate\Database\Eloquent\Model $model 84 | * @param string $status 85 | * @return void 86 | */ 87 | protected function fireValidatedEvent(Model $model, $status) 88 | { 89 | Event::dispatch("eloquent.validated: ".get_class($model), [$model, $status]); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/ValidatingInterface.php: -------------------------------------------------------------------------------- 1 | 'required', 49 | 'slug' => 'required|unique:posts,slug', 50 | 'content' => 'required' 51 | ]; 52 | } 53 | ``` 54 | 55 | You can also add the trait to a `BaseModel` if you're using one and it will work on all models that extend from it, otherwise you can just extend `Watson\Validating\ValidatingModel` instead of `Eloquent`. 56 | 57 | *Note: you will need to set the `$rules` property on any models that extend from a `BaseModel` that uses the trait, or otherwise set an empty array as the `$rules` for the `BaseModel`. If you do not, you will inevitably end up with `LogicException with message 'Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation'`.* 58 | 59 | Now, you have access to some pleasant functionality. 60 | 61 | ```php 62 | // Check whether the model is valid or not. 63 | $post->isValid(); // true 64 | 65 | // Or check if it is invalid or not. 66 | $post->isInvalid(); // false 67 | 68 | // Once you've determined the validity of the model, 69 | // you can get the errors. 70 | $post->getErrors(); // errors MessageBag 71 | ``` 72 | 73 | Model validation also becomes really simple. 74 | 75 | ```php 76 | if ( ! $post->save()) { 77 | // Oops. 78 | return redirect()->route('posts.create') 79 | ->withErrors($post->getErrors()) 80 | ->withInput(); 81 | } 82 | 83 | return redirect()->route('posts.show', $post->id) 84 | ->withSuccess("Your post was saved successfully."); 85 | ``` 86 | 87 | Otherwise, if you prefer to use exceptions when validating models you can use the `saveOrFail()` method. Now, an exception will be raised when you attempt to save an invalid model. 88 | 89 | ```php 90 | $post->saveOrFail(); 91 | ``` 92 | 93 | You *don't need to catch the exception*, if you don't want to. Laravel knows how to handle a `ValidationException` and will automatically redirect back with form input and errors. If you want to handle it yourself though you may. 94 | 95 | ```php 96 | try { 97 | $post->saveOrFail(); 98 | 99 | } catch (Watson\Validating\ValidationException $e) { 100 | $errors = $e->getErrors(); 101 | 102 | return redirect()->route('posts.create') 103 | ->withErrors($errors) 104 | ->withInput(); 105 | } 106 | ``` 107 | 108 | Note that you can just pass the exception to the `withErrors()` method like `withErrors($e)` and Laravel will know how to handle it. 109 | 110 | ### Bypass validation 111 | If you're using the model and you wish to perform a save that bypasses validation you can. This will return the same result as if you called `save()` on a model without the trait. 112 | 113 | ```php 114 | $post->forceSave(); 115 | ``` 116 | 117 | ### Validation exceptions by default 118 | If you would prefer to have exceptions thrown by default when using the `save()` method instead of having to use `saveOrFail()` you can just set the following property on your model or `BaseModel`. 119 | 120 | ```php 121 | /** 122 | * Whether the model should throw a ValidationException if it 123 | * fails validation. If not set, it will default to false. 124 | * 125 | * @var boolean 126 | */ 127 | protected $throwValidationExceptions = true; 128 | ``` 129 | 130 | If you'd like to perform a one-off save using exceptions or return values, you can use the `saveOrFail()` and `saveOrReturn` methods. 131 | 132 | ### Validation messages 133 | To show custom validation error messages, just add the `$validationMessages` property to your model. 134 | 135 | ```php 136 | /** 137 | * Validation messages to be passed to the validator. 138 | * 139 | * @var array 140 | */ 141 | protected $validationMessages = [ 142 | 'slug.unique' => "Another post is using that slug already." 143 | ]; 144 | ``` 145 | 146 | ### Unique rules 147 | You may have noticed we're using the `unique` rule on the slug, which wouldn't work if we were updating a persisted model. Luckily, Validation will take care of this for you and append the model's primary key to the rule so that the rule will work as expected; ignoring the current model. 148 | 149 | You can adjust this functionality by setting the `$injectUniqueIdentifier` property on your model. 150 | 151 | ```php 152 | /** 153 | * Whether the model should inject it's identifier to the unique 154 | * validation rules before attempting validation. If this property 155 | * is not set in the model it will default to true. 156 | * 157 | * @var boolean 158 | */ 159 | protected $injectUniqueIdentifier = true; 160 | ``` 161 | 162 | Out of the box, we support the Laravel provided `unique` rule. We also support the popular [felixkiss/uniquewith-validator](https://github.com/felixkiss/uniquewith-validator) rule, but you'll need to opt-in. Just add `use \Watson\Validating\Injectors\UniqueWithInjector` after you've imported the validating trait. 163 | 164 | It's easy to support additional injection rules too, if you like. Say you wanted to support an additional rule you've got called `unique_ids` which simply takes the model's primary key (for whatever reason). You just need to add a camel-cased rule which accepts any existing parameters and the field name, and returns the replacement rule. 165 | 166 | ```php 167 | /** 168 | * Prepare a unique_ids rule, adding a model identifier if required. 169 | * 170 | * @param array $parameters 171 | * @param string $field 172 | * @return string 173 | */ 174 | protected function prepareUniqueIdsRule($parameters, $field) 175 | { 176 | // Only perform a replacement if the model has been persisted. 177 | if ($this->exists) { 178 | return 'unique_ids:' . $this->getKey(); 179 | } 180 | 181 | return 'unique_ids'; 182 | } 183 | ``` 184 | 185 | In this case if the model has been saved and has a primary key of `10`, the rule `unique_ids` will be replaced with `unique_ids:10`. 186 | 187 | ### Events 188 | Various events are fired by the trait during the validation process which you can hook into to impact the validation process. 189 | 190 | To hook in, you first need to add the `$observeables` property onto your model (or base model). This simply lets Eloquent know that your model can respond to these events. 191 | 192 | ```php 193 | /** 194 | * User exposed observable events 195 | * 196 | * @var array 197 | */ 198 | protected $observables = ['validating', 'validated']; 199 | ``` 200 | 201 | When validation is about to occur, the `eloquent.validating: ModelName` event will be fired, where the `$event` parameter will be `saving` or `restoring`. For example, if you were updating a namespaced model `App\User` the event would be `eloquent.validating: App\User`. If you listen for any of these events and return a value you can prevent validation from occurring completely. 202 | 203 | ```php 204 | Event::listen('eloquent.validating:*', function($model, $event) { 205 | // Pseudo-Russian roulette validation. 206 | if (rand(1, 6) === 1) { 207 | return false; 208 | } 209 | }); 210 | ``` 211 | 212 | After validation occurs, there are also a range of `validated` events you can hook into, for the `passed`, `failed` and `skipped` events. For the above example failing validation, you could get the event `eloquent.validated: App\User`. 213 | 214 | ## Testing 215 | There is currently a bug in Laravel (see issue [#1181](https://github.com/laravel/framework/issues/1181)) that prevents model events from firing more than once in a test suite. This means that the first test that uses model tests will pass but any subseqeuent tests will fail. There are a couple of temporary solutions listed in that thread which you can use to make your tests pass in the meantime. 216 | 217 | **Since Laravel has switched to Liferaft for the purpose of tracking bugs and pull requests, the issue mentioned above may not be available. [This Gist has an example `TestCase.php`](https://gist.github.com/dwightwatson/a645e7f5f6c8c52445d8) which shows you how to reset the events of all your models between tests so that they work as expected.** 218 | 219 | ## Controller usage 220 | There are a number of ways you can go about using the validating validating model in your controllers, however here is one example that makes use of the new FormRequest in Laravel 5 (if you'd like to see another controller example without the FormRequest, check the [4.2+ version of this package](https://github.com/dwightwatson/validating/tree/0.10). 221 | 222 | This example keeps your code clean by allowing the FormRequest to handle your form validation and the model to handle its own validation. By enabling validation exceptions you can reduce repetitive controller code (try/catch blocks) and handle model validation exceptions globally (your form requests should keep your models valid, so if your model becomes invalid it's an *exceptional* event). 223 | 224 | ```php 225 | post = $post; 237 | } 238 | 239 | // ... 240 | 241 | public function store(PostFormRequest $request) 242 | { 243 | // Post will throw an exception if it is not valid. 244 | $post = $this->post->create($request->input()); 245 | 246 | // Post was saved successfully. 247 | return redirect()->route('posts.show', $post); 248 | } 249 | } 250 | ``` 251 | 252 | You can then catch a model validation exception in your `app/Exceptions/Handler.php` and deal with it as you need. 253 | 254 | ```php 255 | public function render($request, Exception $e) 256 | { 257 | if ($e instanceof \Watson\Validating\ValidationException) { 258 | return back()->withErrors($e)->withInput(); 259 | } 260 | 261 | parent::render($request, $e); 262 | } 263 | ``` 264 | -------------------------------------------------------------------------------- /src/ValidatingTrait.php: -------------------------------------------------------------------------------- 1 | validating; 55 | } 56 | 57 | /** 58 | * Set whether the model should attempt validation on saving. 59 | * 60 | * @param bool $value 61 | * @return void 62 | */ 63 | public function setValidating($value) 64 | { 65 | $this->validating = (boolean) $value; 66 | } 67 | 68 | /** 69 | * Returns whether the model will raise an exception or 70 | * return a boolean when validating. 71 | * 72 | * @return bool 73 | */ 74 | public function getThrowValidationExceptions() 75 | { 76 | return isset($this->throwValidationExceptions) ? $this->throwValidationExceptions : false; 77 | } 78 | 79 | /** 80 | * Set whether the model should raise an exception or 81 | * return a boolean on a failed validation. 82 | * 83 | * @param bool $value 84 | * @return void 85 | * @throws InvalidArgumentException 86 | */ 87 | public function setThrowValidationExceptions($value) 88 | { 89 | $this->throwValidationExceptions = (boolean) $value; 90 | } 91 | 92 | /** 93 | * Returns whether or not the model will add it's unique 94 | * identifier to the rules when validating. 95 | * 96 | * @return bool 97 | */ 98 | public function getInjectUniqueIdentifier() 99 | { 100 | return isset($this->injectUniqueIdentifier) ? $this->injectUniqueIdentifier : true; 101 | } 102 | 103 | /** 104 | * Set the model to add unique identifier to rules when performing 105 | * validation. 106 | * 107 | * @param bool $value 108 | * @return void 109 | * @throws InvalidArgumentException 110 | */ 111 | public function setInjectUniqueIdentifier($value) 112 | { 113 | $this->injectUniqueIdentifier = (boolean) $value; 114 | } 115 | 116 | /** 117 | * Get the model. 118 | * 119 | * @return \Illuminate\Database\Eloquent\Model 120 | */ 121 | public function getModel() 122 | { 123 | return $this; 124 | } 125 | 126 | /** 127 | * Get the casted model attributes. 128 | * 129 | * @return array 130 | */ 131 | public function getModelAttributes() 132 | { 133 | $attributes = $this->getModel()->getAttributes(); 134 | 135 | foreach ($attributes as $key => $value) { 136 | // The validator doesn't handle Carbon instances, so instead of casting 137 | // them we'll return their raw value instead. 138 | if (in_array($key, $this->getDates()) || $this->isDateCastable($key) || $this->isDateCastableWithCustomFormat($key)) { 139 | $attributes[$key] = $value; 140 | continue; 141 | } 142 | 143 | $attributes[$key] = $this->getModel()->getAttributeValue($key); 144 | } 145 | 146 | return $attributes; 147 | } 148 | 149 | /** 150 | * Get the custom validation messages being used by the model. 151 | * 152 | * @return array 153 | */ 154 | public function getValidationMessages() 155 | { 156 | return isset($this->validationMessages) ? $this->validationMessages : []; 157 | } 158 | 159 | /** 160 | * Handy method for using the static call Model::validationMessages(). 161 | * Protected access only to allow __callStatic to get to it. 162 | * 163 | * @return array 164 | */ 165 | protected function modelValidationMessages() 166 | { 167 | return (new static)->getValidationMessages(); 168 | } 169 | 170 | /** 171 | * Get the validating attribute names. 172 | * 173 | * @return array 174 | */ 175 | public function getValidationAttributeNames() 176 | { 177 | return isset($this->validationAttributeNames) ? $this->validationAttributeNames : []; 178 | } 179 | 180 | /** 181 | * Handy method for using the static call Model::validationAttributeNames(). 182 | * Protected access only to allow __callStatic to get to it. 183 | * 184 | * @return array 185 | */ 186 | protected function modelValidationAttributeNames() 187 | { 188 | return (new static)->getValidationAttributeNames(); 189 | } 190 | 191 | /** 192 | * Set the validating attribute names. 193 | * 194 | * @param array $attributeNames 195 | * @return void 196 | */ 197 | public function setValidationAttributeNames(?array $attributeNames = null) 198 | { 199 | $this->validationAttributeNames = $attributeNames; 200 | } 201 | 202 | /** 203 | * Get the global validation rules. 204 | * 205 | * @return array 206 | */ 207 | public function getRules() 208 | { 209 | return isset($this->rules) ? $this->rules : []; 210 | } 211 | 212 | /** 213 | * Get the validation rules after being prepared by the injectors. 214 | * 215 | * @return array 216 | */ 217 | public function getPreparedRules() 218 | { 219 | return $this->injectUniqueIdentifierToRules($this->getRules()); 220 | } 221 | 222 | /** 223 | * Handy method for using the static call Model::rules(). Protected access 224 | * only to allow __callStatic to get to it. 225 | * 226 | * @return array 227 | */ 228 | protected function rules() 229 | { 230 | return $this->getRules(); 231 | } 232 | 233 | /** 234 | * Set the global validation rules. 235 | * 236 | * @param array $rules 237 | * @return void 238 | */ 239 | public function setRules(?array $rules = null) 240 | { 241 | $this->rules = $rules; 242 | } 243 | 244 | /** 245 | * Get the validation error messages from the model. 246 | * 247 | * @return \Illuminate\Support\MessageBag 248 | */ 249 | public function getErrors() 250 | { 251 | return $this->validationErrors ?: new MessageBag; 252 | } 253 | 254 | /** 255 | * Set the error messages. 256 | * 257 | * @param \Illuminate\Support\MessageBag $validationErrors 258 | * @return void 259 | */ 260 | public function setErrors(MessageBag $validationErrors) 261 | { 262 | $this->validationErrors = $validationErrors; 263 | } 264 | 265 | /** 266 | * Returns whether the model is valid or not. 267 | * 268 | * @return bool 269 | */ 270 | public function isValid() 271 | { 272 | $rules = $this->getRules(); 273 | 274 | return $this->performValidation($rules); 275 | } 276 | 277 | /** 278 | * Returns if the model is valid, otherwise throws an exception. 279 | * 280 | * @return bool 281 | * @throws \Watson\Validating\ValidationException 282 | */ 283 | public function isValidOrFail() 284 | { 285 | if ( ! $this->isValid()) { 286 | $this->throwValidationException(); 287 | } 288 | 289 | return true; 290 | } 291 | 292 | /** 293 | * Returns whether the model is invalid or not. 294 | * 295 | * @return bool 296 | */ 297 | public function isInvalid() 298 | { 299 | return ! $this->isValid(); 300 | } 301 | 302 | /** 303 | * Force the model to be saved without undergoing validation. 304 | * 305 | * @param array $options 306 | * @return bool 307 | */ 308 | public function forceSave(array $options = []) 309 | { 310 | $currentValidatingSetting = $this->getValidating(); 311 | 312 | $this->setValidating(false); 313 | 314 | $result = $this->getModel()->save($options); 315 | 316 | $this->setValidating($currentValidatingSetting); 317 | 318 | return $result; 319 | } 320 | 321 | /** 322 | * Perform a one-off save that will raise an exception on validation error 323 | * instead of returning a boolean (which is the default behaviour). 324 | * 325 | * @param array $options 326 | * @return bool 327 | * @throws \Throwable 328 | */ 329 | public function saveOrFail(array $options = []) 330 | { 331 | if ($this->isInvalid()) { 332 | return $this->throwValidationException(); 333 | } 334 | 335 | return $this->getModel()->parentSaveOrFail($options); 336 | } 337 | 338 | /** 339 | * Call the parent save or fail method provided by Eloquent. 340 | * 341 | * @param array $options 342 | * @return bool 343 | * @throws \Throwable 344 | */ 345 | public function parentSaveOrFail($options) 346 | { 347 | return parent::saveOrFail($options); 348 | } 349 | 350 | /** 351 | * Perform a one-off save that will return a boolean on 352 | * validation error instead of raising an exception. 353 | * 354 | * @param array $options 355 | * @return bool 356 | */ 357 | public function saveOrReturn(array $options = []) 358 | { 359 | return $this->getModel()->save($options); 360 | } 361 | 362 | /** 363 | * Get the Validator instance. 364 | * 365 | * @return \Illuminate\Validation\Factory 366 | */ 367 | public function getValidator() 368 | { 369 | return $this->validator ?: Validator::getFacadeRoot(); 370 | } 371 | 372 | /** 373 | * Set the Validator instance. 374 | * 375 | * @param \Illuminate\Validation\Factory $validator 376 | */ 377 | public function setValidator(Factory $validator) 378 | { 379 | $this->validator = $validator; 380 | } 381 | 382 | /** 383 | * Make a Validator instance for a given ruleset. 384 | * 385 | * @param array $rules 386 | * @return \Illuminate\Contracts\Validation\Validator 387 | */ 388 | protected function makeValidator($rules = []) 389 | { 390 | // Get the casted model attributes. 391 | $attributes = $this->getModelAttributes(); 392 | 393 | if ($this->getInjectUniqueIdentifier()) { 394 | $rules = $this->injectUniqueIdentifierToRules($rules); 395 | } 396 | 397 | $messages = $this->getValidationMessages(); 398 | 399 | $validator = $this->getValidator()->make($attributes, $rules, $messages); 400 | 401 | if ($this->getValidationAttributeNames()) { 402 | $validator->setAttributeNames($this->getValidationAttributeNames()); 403 | } 404 | 405 | $this->withValidator($validator); 406 | 407 | return $validator; 408 | } 409 | 410 | /** 411 | * Provide a hook to interact with the validator before it is used. 412 | * 413 | * @param \Illuminate\Contracts\Validation\Validator $validator 414 | * @return void 415 | */ 416 | protected function withValidator($validator) 417 | { 418 | // 419 | } 420 | 421 | /** 422 | * Validate the model against it's rules, returning whether 423 | * or not it passes and setting the error messages on the 424 | * model if required. 425 | * 426 | * @param array $rules 427 | * @return bool 428 | * @throws \Watson\Validating\ValidationException 429 | */ 430 | protected function performValidation($rules = []) 431 | { 432 | $validation = $this->makeValidator($rules); 433 | 434 | $result = $validation->passes(); 435 | 436 | $this->setErrors($validation->messages()); 437 | 438 | return $result; 439 | } 440 | 441 | /** 442 | * Throw a validation exception. 443 | * 444 | * @throws \Watson\Validating\ValidationException 445 | */ 446 | public function throwValidationException() 447 | { 448 | $validator = $this->makeValidator($this->getRules()); 449 | 450 | throw new ValidationException($validator, $this); 451 | } 452 | 453 | /** 454 | * Update the unique rules of the global rules to 455 | * include the model identifier. 456 | * 457 | * @return void 458 | */ 459 | public function updateRulesUniques() 460 | { 461 | $rules = $this->getRules(); 462 | 463 | $this->setRules($this->injectUniqueIdentifierToRules($rules)); 464 | } 465 | 466 | /** 467 | * If the model already exists and it has unique validations 468 | * it is going to fail validation unless we also pass it's 469 | * primary key to the rule so that it may be ignored. 470 | * 471 | * This will go through all the rules and append the model's 472 | * primary key to the unique rules so that the validation 473 | * will work as expected. 474 | * 475 | * @param array $rules 476 | * @return array 477 | */ 478 | protected function injectUniqueIdentifierToRules(array $rules) 479 | { 480 | foreach ($rules as $field => &$ruleset) { 481 | // If the ruleset is a pipe-delimited string, convert it to an array. 482 | $ruleset = is_string($ruleset) ? explode('|', $ruleset) : $ruleset; 483 | 484 | foreach ($ruleset as &$rule) { 485 | // Only treat stringy definitions and leave Rule classes and Closures as-is. 486 | if (is_string($rule)) { 487 | $parameters = explode(':', $rule); 488 | $validationRule = array_shift($parameters); 489 | 490 | if ($method = $this->getUniqueIdentifierInjectorMethod($validationRule)) { 491 | $rule = call_user_func_array( 492 | [$this, $method], 493 | [explode(',', head($parameters)), $field] 494 | ); 495 | } 496 | } 497 | } 498 | } 499 | 500 | return $rules; 501 | } 502 | 503 | /** 504 | * Get the dynamic method name for a unique identifier injector rule if it 505 | * exists, otherwise return false. 506 | * 507 | * @param string $validationRule 508 | * @return mixed 509 | */ 510 | protected function getUniqueIdentifierInjectorMethod($validationRule) 511 | { 512 | $method = 'prepare' . Str::studly($validationRule) . 'Rule'; 513 | 514 | return method_exists($this, $method) ? $method : false; 515 | } 516 | } 517 | --------------------------------------------------------------------------------