Laravel Eloquent Power Joins Package

Published on by

Laravel Eloquent Power Joins Package image

If you have some experience using databases, it is very likely you have used joins at least once in your career. Joins can be used for a bunch of different reasons, from selecting data from other tables to limiting the matches of your query.

I'm going to give a few examples on this post, so, in order to contextualize the examples, imagine we have the following database/models structure.

User -> hasMany -> Post
Post -> hasMany -> Comment
Post -> morphMany -> Image

On Laravel, using eloquent, joining the posts table would look something like this:

User::select('users.*')->join('posts', 'posts.user_id', '=', 'users.id');

In case you want to join the posts and the comments table, your query would look something like this:

User::select('users.*')
->join('posts', 'posts.user_id', '=', 'users.id')
->join('comments', 'comments.post_id', '=', 'posts.id');

This is fine and we can understand, but we can do better. We already have all these relationships defined in our models, but we are repeating some of the implementation details when we write the join statements. So, instead of doing this, wouldn't be cool if you could just do the following?

// example 1
User::joinRelationship('posts');
 
// example 2
User::joinRelationship('posts.comments');

This is less code to read, and more importantly, easier code to read. It also hides any implementation details on how your relationships work. So, if your relationship changes, your joins will be automatically updated.

Introducing the Eloquent Power Joins package

We felt the way we did joins in our applications wasn't really the “Laravel way”, so we decided to introduce some of the Laravel fine touch into the way we do joins.

joinRelationship is a method introduced by the Eloquent Power Joins package. It works with any type of the existing Laravel relationships.

The installation of the package is as simple as just running the following composer command, and you should already have access to everything that will be mentioned on this post.

composer require kirschbaum-development/eloquent-power-joins

On any model you want to be able to use the methods described below, you should use the following trait:

use Kirschbaum\PowerJoins\PowerJoins;
 
class User extends Model
{
use PowerJoins;
}

Joining polymorphic relationships

The joinRelationship method also works polymorphic relationships. Besides performing the regular join, it also performs the {morph}_type == Model::class check, as you can see below.

Post::joinRelationship('images')->toSql();
 
// select * from posts
// inner join images on images.imageable_id = posts.id AND images.imageable_id = 'App\\Post'

Joining nested relationships

And, it also works with nested relationships.

User::joinRelationship('posts.images')->toSql();
 
// select * from users
// inner join posts on posts.user_id = users.id
// inner join images on images.imageable_id = posts.id AND images.imageable_id = 'App\\Post'

It works with any relationship

The package will work with any of the native relationship types provided from Laravel.

BelongsToMany will make 2 joins considering the pivot table as well. HasManyThrough also makes the 2 necessary joins.

Eloquent Power Joins also applies any soft deletes clauses in case the related model uses the SoftDeletes trait.

But, the package also provides you with a few other very useful features, as you can see below.

Applying extra conditions to the joins

You can apply any extra condition you need to the joins, as well.

User::joinRelationship('posts', function ($join) {
$join->where('posts.published', true);
});

For nested calls, and/or BelongsToMany or HasManyThrough relationships, you need to pass an array with the relationship as the key.

User::joinRelationship('posts.images', [
'posts' => function ($join) {
$join->where('posts.published', true);
},
'images' => function ($join) {
$join->where('images.cover', true);
},
]);

Using model scopes inside the callbacks 🤯

We consider this one of the most useful features of this package. Let's say, you have a published scope on your Post model:

public function scopePublished($query)
{
$query->where('published', true);
}

When joining relationships, you can use the scopes defined in the model being joined. How cool is this?

User::joinRelationshio('posts', function ($join) {
// the $join instance here can access any of the scopes defined in the Post model 🤯
$join->published();
});

Querying relationship existence

Querying relationship existence is a very powerful and convenient feature of Eloquent. However, it uses the where exists syntax which is not always the best and more performant choice, depending on how many records you have or the structure of your table.

This package also implements almost all Laravel methods for querying relationship existence using joins instead of where exists.

Performance

First thing to be aware here, is that the below example is one use-case where using joins over where exists is a lot more performant. You shouldn't assume this is true for every query, and you should use tools like Laravel Debugbar , Laravel Telescope or any tool of your choice to figure out what's best for YOUR use-case.

That said, below you can see one example of the MySQL CPU usage after deploying a change to use powerJoinHas instead of has, in one of our client's application. MySQL was running on RDS, and this image was took from AWS CloudWatch.

Show me the code

Below, you can see the methods this package implements and also the Laravel equivalent.

Laravel Native Methods

User::has('posts');
User::has('posts.comments');
User::has('posts', '>', 3);
User::whereHas('posts', function ($query) {
$query->where('posts.published', true);
});
User::doesntHave('posts');

Package implementations using joins

User::powerJoinHas('posts');
User::powerJoinHas('posts.comments');
User::powerJoinHas('posts.comments', '>', 3);
User::powerJoinWhereHas('posts', function ($query) {
$query->where('posts.published', true);
});
User::powerJoinDoesntHave('posts');

Sorting your query results

Another useful feature os to sort your query results using a column from another table using the orderByPowerJoins method.

User::orderByPowerJoins('profile.city')->toSql();
// select "users".* from "users"
// inner join "user_profiles" on "user_profiles"."user_id" = "users"."id"
// order by "user_profiles"."city" asc

You can also sort your results by aggregations (COUNT, SUM, AVG, MIN or MAX).

For instance, to sort users with the highest number of posts, you would do this:

$users = User::orderByPowerJoinsCount('posts.id', 'desc')->get();

Or, to get the list of posts sorted by the ones with comments which contain the highest average of votes.

$posts = Post::orderByPowerJoinsAvg('comments.votes', 'desc')->get();

And you also have methods for SUM, MIN and MAX:

Post::orderByPowerJoinsSum('…');
Post::orderByPowerJoinsMin('…');
Post::orderByPowerJoinsMax('…');

Joins, the Laravel way

IMO, one of the advantages of the package is being able to write code in a more “Laravel way”. So, below you can see a few examples of how much better the code looks after using it. Any examples described here produces the EXACT same result.

Example 1

BuilderFile::select('builder_detail_builder_file.*')
->join('builder_detail_builder_file', 'builder_files.id', '=', 'builder_detail_builder_file.builder_file_id')
->join('builder_details', 'builder_details.id', '=', 'builder_detail_builder_file.builder_detail_id')
->join('documents', 'builder_details.document_id', '=', 'ces_documents.id');

With Eloquent Power Joins

BuilderFile::joinRelationship('details.document');

Example 2

CesDocument::query()
->join('term_relations', function ($join) {
$join
->on('term_relations.relationable_id', '=', 'ces_documents.id')
->where('term_relations.relationable_type', '=', CesDocument::class);
})
->join('terms', 'term_relations.term_id', '=', 'terms.id')
->join('vocabularies', 'terms.vocabulary_id', '=', 'vocabularies.id')
->get();

With Eloquent Power Joins

CesDocument::query()
->joinRelationship('related.terms')
->joinRelationship('related.vocabulary')
->get();

That's it. Hopefully this package is going to be as useful to you as it is to us. Happy joining!

Luis Dalmolin photo

Luis is a senior developer at Kirschbaum and has over 10 years of experience architecting complex applications and has been working with Laravel since the early days of Laravel 4.

In addition to PHP and Laravel, Luis specializes in VueJS/Javascript and everything DevOps related. He loves working with Open Source and has contributed several open-source projects to the community.

Luis taught an AngularJS course at University Feevale, where he also earned his degree in Internet Systems, and he translated the Laravel book "Code Bright" by Dayle Rees into Portuguese.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project.

Visit No Compromises
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
All Green logo

All Green

All Green is a SaaS test runner that can execute your whole Laravel test suite in mere seconds so that you don't get blocked – you get feedback almost instantly and you can deploy to production very quickly.

All Green
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article
Asserting Exceptions in Laravel Tests image

Asserting Exceptions in Laravel Tests

Read article
Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4 image

Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4

Read article
Basset is an alternative way to load CSS & JS assets image

Basset is an alternative way to load CSS & JS assets

Read article
Integrate Laravel with Stripe Connect Using This Package image

Integrate Laravel with Stripe Connect Using This Package

Read article
The Random package generates cryptographically secure random values image

The Random package generates cryptographically secure random values

Read article