-
Notifications
You must be signed in to change notification settings - Fork 11k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[11.x] Added ModelExists
rule
#52505
base: 11.x
Are you sure you want to change the base?
Conversation
Would be super awesome if that one gets merged! |
yes, that would be super awesome, indeed! @pascalbaljet has developed this in my project and we're already using it in a lot of FormRequests, greatly streamlining our codebase. Was looking for this for a long time, as it never felt clean/Laravel-ish to use the |
The topic has also been discussed a little bit here: |
Would really be nice to have! Shouldn't this ideally go together with the sister Also, I'd have loved to see this use the current |
yes, that would be nice, but it would be a BC break, as |
If this PR gets merged, I could do a follow-up PR for a
You can already pass a class string to |
I don't mind this as a concept, but There could be a way to kinda hack it into Laravel 11.x by using named arguments like Rule::exists(model: Foo::class). |
Drafting this for now - mark as ready for review if you make any adjustments noted above. |
Should this not also be added here?
|
* @param Builder<TModel>|TModel|class-string<TModel> $model | ||
* @return \Illuminate\Validation\Rules\ModelExists<Builder<TModel>> | ||
*/ | ||
public static function modelExists(Builder|Model|string $model, ?string $column = null): ModelExists |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about relations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It uses the Builder contract (Illuminate\Contracts\Database\Eloquent\Builder
) which is implemented by all relation classes.
What do you think for adding "optimization" for array validation to avoid many requests to the DB? For example: when we are calling |
I've implemented the same validation rule in my project, but will happy if it will be exists in the Laravel: <?php
namespace App\Validation\Rules;
use Closure;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Translation\PotentiallyTranslatedString;
class ModelExists implements ValidationRule
{
protected bool $many = false;
protected array $modelIds = [];
public function __construct(protected Builder $modelQuery, protected $withTrashed = false) {}
public function many(): self
{
$this->many = true;
$this->modelIds = $this->resolveModelQuery()->pluck('id')->all();
return $this;
}
/**
* @param Closure(string): PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!$this->passed($value)) {
$fail('validation.exists')->translate();
}
}
protected function passed(mixed $value): bool
{
return $this->many
? in_array($value, $this->modelIds)
: $this->resolveModelQuery()->whereKey($value)->exists();
}
protected function resolveModelQuery(): Builder
{
return $this->modelQuery->when($this->withTrashed, fn (Builder $query) => $query->withTrashed())
->clone();
}
} |
: $this->query->whereKey($value); | ||
|
||
if (! $this->query->exists()) { | ||
$fail('validation.exists')->translate(['attribute' => $attribute]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove ['attribute' => $attribute]
and leave only ->translate()
I'll refactor it to use named arguments first, and if you're not happy with it, I can always rework it for Laravel 12 :) |
There is an
exists
rule to check the existence of a record in the database. You can already pass an Eloquent Model class as the table argument in theRule::exists()
method, but that just extracts the table and connection from the Model. You can apply additional constraints by chaining thewhere()
method, but under the hood, it uses theIlluminate\Database\Query\Builder
, not theIlluminate\Database\Eloquent\Builder
, so you can't use scopes, for instance.What we have found in our application is that we are repeating code by writing
exists
rules and reimplementing the constraints for which we actually already have scopes defined.This new
Rule::modelExists()
rule lets you use scopes and other Eloquent Builder features:By default, it checks the model key (using
whereKey()
), but you can change that with the optional second argument:Besides the Model class string, you can also pass a Builder or Model instance: