[13.x] feat: refresh configured model attributes after writes

Merged
CA
1mo ago
+158-0

Hello!

Motivation

I recently began taking advantage of virtualAs columns in our codebase which really cleaned up a ton of manual sql concatenations and removed the need for a computed property on the model (since the db has it).

For example, take the users table with the following columns:

$table->string('first_name');
$table->string('last_name');
$table->string('name')->virtualAs("first_name || ' ' || last_name");

Now I can just directly select/pluck/etc the name from the db without having to worry about having a computed property on the model:

User::query()->select(['id', 'first_name', 'last_name'])->get()->pluck('name', 'id');
// becomes
User::query()->pluck('name', 'id');

->selectRaw("users.first_name || ' ' || users.last_name as name");
// becomes
->select('users.name');

which is awesome and much cleaner!

However, another problem appeared---the name column does not exist on the model right after creating, and grows stale after an update (until the model is refreshed / next retrieved from the database):

$user = User::create(['first_name' => 'Taylor', 'last_name' => 'Otwell']);
$user->name; // this is NULL!
$user->refresh()->name; // this is set

$user->update(['first_name' => 'Abigail']);
$user->name; // still has `Taylor`!
$user->refresh()->name; // this is correct

Obviously, always having to refresh the entire model just to ensure you have up-to-date information (especially when dealing with created/updated event listeners) is a code smell and not desirable---we need a better solution in the framework.

Implementation

This PR creates a new $refreshes array on the Model class that allows developers to specify which columns (if any) should be refreshed after every model write, if the array is empty then no queries are executed.

class User extends Model
{
    protected array $refreshes = ['name'];
}

$user = User::create(['first_name' => 'Taylor', 'last_name' => 'Otwell']);
$user->name; // this is set!

$user->update(['first_name' => 'Abigail']);
$user->name; // this is correct!

Thanks!