Database and Eloquent ORM: New features and improvements since the original Laravel 8 release (2/2)

This blog post follows up on last week's overview of the new Database and Eloquent features in Laravel 8 since the original release in September 2020. I already covered Collections, and next week is all about Jobs and Queues. Enjoy!

I got most code examples and explanations from the PRs and official documentation.

v8.41.0 Added Model::updateQuietly() (#37169)

Sometimes you may wish to "update" a given model without dispatching any events. You may accomplish this using the updateQuietly method, which uses the saveQuietly method under the hood:

$flight->updateQuietly(['departed' => false]);

As of v8.59, you may also use the createOneQuietly, createManyQuietly and createQuietly methods when using Model Factories:

Post::factory()->createOneQuietly();
 
Post::factory()->count(3)->createQuietly();
 
Post::factory()->createManyQuietly([
['message' => 'A new comment'],
['message' => 'Another new comment'],
]);

v8.41.0 Added Model key extraction to id on whereKey() and whereKeyNot() (#37184)

You can now use a Model instance with the whereKey and whereKeyNot methods:

$passenger->tickets()
->whereHas('airline', fn (Builder $query) => $query->whereKey($airline))
->get();

v8.42.0 Added withExists method to QueriesRelationships (#37302)

In addition to the withCount method, you may now use the withExists method to check the existence of a relationship:

// before:
$users = User::withCount('posts')->get();
 
$isAuthor = $user->posts_count > 0;
 
// after:
$users = User::withExists('posts')->get();
 
$isAuthor = $user->posts_exists;
 
// the column name can also be aliased:
$users = User::withExists([
'posts as is_author',
'posts as is_tech_author' => function ($query) {
return $query->where('category', 'tech');
},
'comments',
])->get();

v8.42.0 Added loadExists on Model and Eloquent Collection (#37388)

This PR follows upon the withExists method in the example above. You may now use the loadExists method in both Model and Eloquent Collections:

$books = Book::all();
 
$books->loadExists(['author', 'publisher']);

v8.42.0 Added one-of-many relationship (inner join) (#37362)

One-to-one relations that are a partial relation of a one-to-many relation. You can retrieve the "latest" or "oldest" related model of the relationship:

/**
* Get the user's most recent order.
*/
public function latestOrder()
{
return $this->hasOne(Order::class)->latestOfMany();
}
 
/**
* Get the user's oldest order.
*/
public function oldestOrder()
{
return $this->hasOne(Order::class)->oldestOfMany();
}
 
/**
* Get the user's largest order.
*/
public function largestOrder()
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}

v8.43.0 Added eloquent strict loading mode (#37363)

You may instruct Laravel to always prevent the lazy loading of relationships. You should call this method within the boot method of your app's AppServiceProvider class.

Model::preventLazyLoading(! app()->isProduction());

v8.43.0 Added beforeQuery to base query builder (#37431)

This PR is a very advanced one! It allows you to change a "subselect"-builder, whereby the changes are also applied to the parent query builder. The preserved closures will be called before the query gets rendered. This way, you can keep "subquery"-builders alive and modify the subquery after applying it to the parent builder:

// 1. Add the subquery
$builder->beforeQuery(function ($query) use ($subQuery) {
$query->joinSub($subQuery, ...);
});
 
// 2. Add constraints to the subquery
$subQuery->where('foo', 'bar');
 
// 3. Render the subquery, the constraints from 2. are applied
$builder->get();

v8.50.0 Added the possibility of having "Prunable" models (#37889)

You may want to periodically delete models that are no longer needed. With the Prunable trait Laravel will automatically remove obsolete model records from the database via a scheduled command.

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
 
class Flight extends Model
{
use Prunable;
 
/**
* Get the prunable model query.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function prunable()
{
return static::where('created_at', '<=', now()->subMonth());
}
}

The Artisan CLI command:

php artisan model:prune

You may also define a pruning method on the model, which will be called before the model is deleted:

protected function pruning()
{
// Delete additional resources associated
// with the model, for example: files
 
Storage::disk('s3')->delete($this->filename);
}

v8.53.0 Added immutable date and datetime casting (#38199)

Ssupport for accessing dates as immutable. Returns a CarbonImmutable instance instead of a Carbon instance.

class User extends Model
{
public $casts = [
'date_field' => 'immutable_date',
'datetime_field' => 'immutable_datetime',
];
}

v8.57.0 Added a simple where helper for querying relations (#38499)

You may now use the whereRelation and whereMorphRelation methods to query for a relationship's existence with a single, simple where condition attached to the relationship query:

// Before:
User::whereHas('posts', function ($query) {
$query->where('published_at', '>', now());
})->get();
 
// After
User::whereRelation('posts', 'published_at', '>', now())->get();
 
// Morph relation
Comment::whereMorphRelation('commentable', '*', 'public', true);

v8.59.0 Added Eloquent builder whereMorphedTo method to streamline finding models morphed to another model (#38668)

The new whereMorphedTo and orWhereMorphedTo methods are a shortcut for adding a where condition looking for models that are morphed to a specific related model, without the overhead of a whereHas subquery:

Feedback::whereMorphedTo('subject', $user)->get();
 
Feedback::whereMorphedTo('subject', User::class)->get();

v8.59.0 Added support for disallowing class morphs (#38656)

You may call the enforceMorphMap method in the boot method of your apps' AppServiceProvider class. This disallows morphs without a morph map on it.

use Illuminate\Database\Eloquent\Relations\Relation;
 
Relation::enforceMorphMap([
'post' => Post::class,
'video' => Video::class,
]);

v8.60.0 Added the valueOfFail() Eloquent builder method (#38707)

In addition to the value method, you may now use the valueOrFail method. This will throw a ModelNotFoundException if the model is not found.

// Before:
$votes = User::where('name', 'John')->firstOrFail('votes')->votes;
 
// Now:
$votes = User::where('name', 'John')->valueOrFail('votes');

v8.63.0 Added whereBelongsTo() Eloquent builder method (#38927)

The new whereBelongsTo method automatically determines the proper relationship and foreign key for the given model:

// Before:
$posts = Post::where('user_id', $user->id)->get();
 
// After:
$posts = Post::whereBelongsTo($user)->get();