Skip to content

[12.x] Adds conditionals to Model #55359

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

Closed

Conversation

DarkGhostHunter
Copy link
Contributor

What?

Adds conditional fill, save, update, delete and force-delete to Models. This way developers don't have to be confused with the when and unless operations that are proxied to the Eloquent Builder, which are kept as-is and accessible as a static methods from the Model class.

use App\Models\User;

$user = User::find(1);

$user->updateWhen(fn () => 'truthy', ['is_cool' => true]);

The methods are:

  • forceFillWhen() | forceFillUnless(): Accepts the condition and fills the attributes using fill() method.
  • fillWhen() | fillUnless(): Accepts the condition and fills the attributes using forceFill() method.
  • saveWhen() | saveUnless(): Accepts the condition and the options to pass to the save() method.
  • updateWhen() | updateUnelss(): Accepts the condition, the attributes, and the options to pass to the update() method.
  • deleteWhen() | deleteUnless(): Accepts the condition and calls delete().
  • forceDeleteWhen() | forceDeleteUnless(): Same logic as before but for forceDelete().

Notice that the conditional methods that accept attributes as an array also accept a callback that returns the array of attributes. This is because a developer may have to execute computationally expensive attributes based on the condition, which can be avoided if the condition fails.

// Before

$model->updateWhen($mayBeTrue, [
    // This gets executed even if the condition is falsy.
    'value' => Something::computationallyExpensive()
);
// After

$model->updateWhen($mayBeTrue, fn (Model $model) => [
    // This will get executed only after the condition is truthy.
    'value' => Something::computationallyExpensive()
]);

These callbacks receive the model instance, allowing the developer to use a callback from elsewhere, which can make controllers or other methods grow in size.

$model->updateWhen($mayBeTrue, Payments::modifyModel(...));

@imanghafoori1
Copy link
Contributor

imanghafoori1 commented Apr 10, 2025

These may need another parameter to make them quiet or a brother method set:
$user->updateQuitelyWhen(fn () => 'truthy', ['is_cool' => true]);
as they are on the model object and not the query builder.

@DarkGhostHunter
Copy link
Contributor Author

These may need another parameter to make them quiet or a brother method set: $user->updateQuitelyWhen(fn () => 'truthy', ['is_cool' => true]); as they are on the model object and not the query builder.

I would prefer updateWhen(..., $quiet = false) and updateQuietlyWhen() calling the latter.

@devajmeireles
Copy link
Contributor

What will be the difference if we compare this PR with the current when/unless format?

use App\Models\User;

$user = User::first();

$user->when(true, fn (User $model) => $model->update(['name' => 'Test']));

@osbre
Copy link
Contributor

osbre commented Apr 11, 2025

There is an alternative one-liner approach:

if ($condition) $user->update(['value' => computationallyExpensive()]);

Or, if you don't like putting "if":

$condition && $user->update(['value' => computationallyExpensive()]);

Powered by PHP, always has been 👀

@DarkGhostHunter
Copy link
Contributor Author

DarkGhostHunter commented Apr 11, 2025

This one-liner should work equally well.

if ($condition) $user->update(['value' => computationallyExpensive()]);

That's the antichrist and I have a permit to use a M14A.

Or, if you don't like putting if:

$condition && $user->update(['value' => computationallyExpensive()]);

Powered by PHP, always has been 👀

I'll let this marinate because also other parts of the framework can be similarly approached using that syntax.

Update: Also, that returns a boolean and I can't use tap() on that, like $condition && tap($user->update(['value' => 'something'));

@timacdonald
Copy link
Member

Rather than having dedicated methods for all of these, I feel like we would be better off adding a higher-order when to support everything.

$user->when($condition)->update([]);
$user->when($condition)->forceFill([]);
$user->when($condition)->save();

Although, as @osbre mentioned, this is already rather terse in native PHP:

$condition && $user->update([]);
$condition && $user->forceFill([]);
$condition && $user->save();

@DarkGhostHunter
Copy link
Contributor Author

Rather than having dedicated methods for all of these, I feel like we would be better off adding a higher-order when to support everything.

$user->when($condition)->update([]);
$user->when($condition)->forceFill([]);
$user->when($condition)->save();

Again, when() and unless() are proxied to the builder. Try on your app and see.

Although, as @osbre mentioned, this is already rather terse in native PHP:

$condition && $user->update([]);
$condition && $user->forceFill([]);
$condition && $user->save();

While save() and update() returns a boolean, fill() and forceFill() return the model and these conditions return a boolean, which breaks the fluid method calling.

@imanghafoori1
Copy link
Contributor

This way, we can get results:
$condition && ($model = $user->forceFill([...]));

@donnysim
Copy link
Contributor

donnysim commented Apr 11, 2025

Most actions modify the same model, there's no point in expecting the value being returned from fill or forceFill as you already have the variable, the only logical use case would be $condition && ($model = User::create([...])); or something that returns a new instance, but even then you'd have to check if the variable is set afterwards so in most cases it defeats the purpose. I personally hate built in conditionals as it just makes it so much harder to read the logical flow, especially when it's littered with all these callbacks - saving few characters for me will never be worth the tradeoff.

@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!

@shaedrich
Copy link
Contributor

Rather than having dedicated methods for all of these, I feel like we would be better off adding a higher-order when to support everything.

$user->when($condition)->update([]);
$user->when($condition)->forceFill([]);
$user->when($condition)->save();

Again, when() and unless() are proxied to the builder. Try on your app and see.

Technically, we could differentiate this depending on the first argument passed to the callback (assuming, a callback itself is passed):

$user = User::when(true, function (Builder $builder) { // -> proxied to builder
    return $builder->where('is_deleted', false);
})->get();
$user->when(true, function (User $user) { // -> executed on model
    return $user->is_deleted = false;
});

Another way to differentiate might be whether it's called statically or on the instance.

However, both options might have drawbacks and breaking changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants