{"id":13610520,"url":"https://github.com/kirschbaum-development/eloquent-power-joins","last_synced_at":"2026-05-28T22:00:39.651Z","repository":{"id":37973894,"uuid":"247844867","full_name":"kirschbaum-development/eloquent-power-joins","owner":"kirschbaum-development","description":"The Laravel magic you know, now applied to joins.","archived":false,"fork":false,"pushed_at":"2026-03-29T12:05:30.000Z","size":615,"stargazers_count":1569,"open_issues_count":13,"forks_count":101,"subscribers_count":28,"default_branch":"master","last_synced_at":"2026-05-26T17:03:38.738Z","etag":null,"topics":["hacktoberfest","laravel","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kirschbaum-development.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2020-03-17T00:32:57.000Z","updated_at":"2026-05-25T13:57:50.000Z","dependencies_parsed_at":"2024-02-13T16:46:04.634Z","dependency_job_id":"37e9b60f-b142-40fa-9ab7-200247e9624c","html_url":"https://github.com/kirschbaum-development/eloquent-power-joins","commit_stats":{"total_commits":168,"total_committers":18,"mean_commits":9.333333333333334,"dds":"0.16666666666666663","last_synced_commit":"b9aa4c2364e2776e5aacca49160d4b519ae82768"},"previous_names":[],"tags_count":75,"template":false,"template_full_name":null,"purl":"pkg:github/kirschbaum-development/eloquent-power-joins","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kirschbaum-development%2Feloquent-power-joins","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kirschbaum-development%2Feloquent-power-joins/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kirschbaum-development%2Feloquent-power-joins/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kirschbaum-development%2Feloquent-power-joins/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kirschbaum-development","download_url":"https://codeload.github.com/kirschbaum-development/eloquent-power-joins/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kirschbaum-development%2Feloquent-power-joins/sbom","scorecard":{"id":561417,"data":{"date":"2025-08-11","repo":{"name":"github.com/kirschbaum-development/eloquent-power-joins","commit":"d67c7e2efa886d2ef8bb29e86c3ddb9438ac6390"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.7,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":10,"reason":"6 commit(s) and 7 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":3,"reason":"Found 9/26 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 19 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T13:49:05.943Z","repository_id":37973894,"created_at":"2025-08-20T13:49:05.944Z","updated_at":"2025-08-20T13:49:05.944Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33627938,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","laravel","php"],"created_at":"2024-08-01T19:01:45.513Z","updated_at":"2026-05-28T22:00:39.638Z","avatar_url":"https://github.com/kirschbaum-development.png","language":"PHP","funding_links":[],"categories":["PHP","Development"],"sub_categories":["Eloquent"],"readme":"![Eloquent Power Joins](screenshots/eloquent-power-joins.jpg \"Eloquent Power Joins\")\n\n![Laravel Supported Versions](https://img.shields.io/badge/laravel-11.x/12.x/13.x-green.svg)\n[![run-tests](https://github.com/kirschbaum-development/eloquent-power-joins/actions/workflows/ci.yaml/badge.svg)](https://github.com/kirschbaum-development/eloquent-power-joins/actions/workflows/ci.yaml)\n[![MIT Licensed](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/kirschbaum-development/eloquent-power-joins.svg?style=flat-square)](https://packagist.org/packages/kirschbaum-development/eloquent-power-joins)\n[![Total Downloads](https://img.shields.io/packagist/dt/kirschbaum-development/eloquent-power-joins.svg?style=flat-square)](https://packagist.org/packages/kirschbaum-development/eloquent-power-joins)\n\nThe Laravel magic you know, now applied to joins.\n\nJoins are very useful in a lot of ways. If you are here, you most likely know about and use them. Eloquent is very powerful, but it lacks a bit of the \"Laravel way\" when using joins. This package make your joins in a more Laravel way, with more readable with less code while hiding implementation details from places they don't need to be exposed.\n\nA few things we consider is missing when using joins which are very powerful Eloquent features:\n\n* Ability to use relationship definitions to make joins;\n* Ability to use model scopes inside different contexts;\n* Ability to query relationship existence using joins instead of where exists;\n* Ability to easily sort results based on columns or aggregations from related tables;\n\nYou can read a more detailed explanation on the problems this package solves on [this blog post](https://kirschbaumdevelopment.com/insights/power-joins).\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require kirschbaum-development/eloquent-power-joins\n```\n\nFor Laravel versions \u003c 10, use the 3.* version. For Laravel versions \u003c 8, use the 2.* version:\n\n```bash\ncomposer require kirschbaum-development/eloquent-power-joins:3.*\n```\n\n## Usage\n\nThis package provides a few features.\n\n### 1 - Join Relationship\n\nLet's say you have a `User` model with a `hasMany` relationship to the `Post` model. If you want to join the tables, you would usually write something like:\n\n```php\nUser::select('users.*')-\u003ejoin('posts', 'posts.user_id', '=', 'users.id');\n```\n\nThis package provides you with a new `joinRelationship()` method, which does the exact same thing.\n\n```php\nUser::joinRelationship('posts');\n```\n\nBoth options produce the same results. In terms of code, you didn't save THAT much, but you are now using the relationship between the `User` and the `Post` models to join the tables. This means that you are now hiding how this relationship works behind the scenes (implementation details). You also don't need to change the code if the relationship type changes. You now have more readable and less overwhelming code.\n\nBut, **it gets better** when you need to **join nested relationships**. Let's assume you also have a `hasMany` relationship between the `Post` and `Comment` models and you need to join these tables, you can simply write:\n\n```php\nUser::joinRelationship('posts.comments');\n```\n\nSo much better, wouldn't you agree?! You can also `left` or `right` join the relationships as needed.\n\n```php\nUser::leftJoinRelationship('posts.comments');\nUser::rightJoinRelationship('posts.comments');\n```\n\n#### Joining polymorphic relationships\n\nLet's imagine, you have a `Image` model that is a polymorphic relationship (`Post -\u003e morphMany -\u003e Image`). Besides the regular join, you would also need to apply the `where imageable_type = Post::class` condition, otherwise you could get messy results.\n\nTurns out, if you join a polymorphic relationship, Eloquent Power Joins automatically applies this condition for you. You simply need to call the same method.\n\n```php\nPost::joinRelationship('images');\n```\n\nYou can also join MorphTo relationships.\n\n```php\nImage::joinRelationship('imageable', morphable: Post::class);\n```\n\nNote: Querying morph to relationships only supports one morphable type at a time.\n\n**Applying conditions \u0026 callbacks to the joins**\n\nNow, let's say you want to apply a condition to the join you are making. You simply need to pass a callback as the second parameter to the `joinRelationship` method.\n\n```php\nUser::joinRelationship('posts', fn ($join) =\u003e $join-\u003ewhere('posts.approved', true))-\u003etoSql();\n```\n\nYou can also specify the type of join you want to make in the callback:\n\n```php\nUser::joinRelationship('posts', fn ($join) =\u003e $join-\u003eleft());\n```\n\nFor **nested calls**, you simply need to pass an array referencing the relationship names.\n\n```php\nUser::joinRelationship('posts.comments', [\n    'posts' =\u003e fn ($join) =\u003e $join-\u003ewhere('posts.published', true),\n    'comments' =\u003e fn ($join) =\u003e $join-\u003ewhere('comments.approved', true),\n]);\n```\n\nFor **belongs to many** calls, you need to pass an array with the relationship, and then an array with the table names.\n\n```php\nUser::joinRelationship('groups', [\n    'groups' =\u003e [\n        'groups' =\u003e function ($join) {\n            // ...\n        },\n        // group_members is the intermediary table here\n        'group_members' =\u003e fn ($join) =\u003e $join-\u003ewhere('group_members.active', true),\n    ]\n]);\n```\n\n#### Using model scopes inside the join callbacks 🤯\n\nWe consider this one of the most useful features of this package. Let's say, you have a `published` scope on your `Post` model:\n\n```php\n    public function scopePublished($query)\n    {\n        $query-\u003ewhere('published', true);\n    }\n```\n\nWhen joining relationships, you **can** use the scopes defined in the model being joined. How cool is this?\n\n```php\nUser::joinRelationship('posts', function ($join) {\n    // the $join instance here can access any of the scopes defined in Post 🤯\n    $join-\u003epublished();\n});\n```\n\nWhen using model scopes inside a join clause, you **can't** type hint the `$query` parameter in your scope. Also, keep in mind you are inside a join, so you are limited to use only conditions supported by joins.\n\n#### Using aliases\n\nSometimes, you are going to need to use table aliases on your joins because you are joining the same table more than once. One option to accomplish this is to use the `joinRelationshipUsingAlias` method.\n\n```php\nPost::joinRelationshipUsingAlias('category.parent')-\u003eget();\n```\n\nIn case you need to specify the name of the alias which is going to be used, you can do in two different ways:\n\n1. Passing a string as the second parameter (this won't work for nested joins):\n\n```php\nPost::joinRelationshipUsingAlias('category', 'category_alias')-\u003eget();\n```\n\n2. Calling the `as` function inside the join callback.\n\n```php\nPost::joinRelationship('category.parent', [\n    'category' =\u003e fn ($join) =\u003e $join-\u003eas('category_alias'),\n    'parent' =\u003e fn ($join) =\u003e $join-\u003eas('category_parent'),\n])-\u003eget()\n```\n\nFor *belongs to many* or *has many through* calls, you need to pass an array with the relationship, and then an array with the table names.\n\n```php\nGroup::joinRelationship('posts.user', [\n    'posts' =\u003e [\n        'posts' =\u003e fn ($join) =\u003e $join-\u003eas('posts_alias'),\n        'post_groups' =\u003e fn ($join) =\u003e $join-\u003eas('post_groups_alias'),\n    ],\n])-\u003etoSql();\n```\n\n#### Select * from table\n\nWhen making joins, using `select * from ...` can be dangerous as fields with the same name between the parent and the joined tables could conflict. Thinking on that, if you call the `joinRelationship` method without previously selecting any specific columns, Eloquent Power Joins will automatically include that for you. For instance, take a look at the following examples:\n\n```php\nUser::joinRelationship('posts')-\u003etoSql();\n// select users.* from users inner join posts on posts.user_id = users.id\n```\n\nAnd, if you specify the select statement:\n\n```php\nUser::select('users.id')-\u003ejoinRelationship('posts')-\u003etoSql();\n// select users.id from users inner join posts on posts.user_id = users.id\n```\n\n#### Soft deletes\n\nWhen joining any models which uses the `SoftDeletes` trait, the following condition will be also automatically applied to all your joins:\n\n```sql\nand \"users\".\"deleted_at\" is null\n```\n\nIn case you want to include trashed models, you can call the `-\u003ewithTrashed()` method in the join callback.\n\n```php\nUserProfile::joinRelationship('users', fn ($join) =\u003e $join-\u003ewithTrashed());\n```\n\nYou can also call the `onlyTrashed` model as well:\n\n```php\nUserProfile::joinRelationship('users', ($join) =\u003e $join-\u003eonlyTrashed());\n```\n\n#### Extra conditions defined in relationships\n\nIf you have extra conditions in your relationship definitions, they will get automatically applied for you.\n\n```php\nclass User extends Model\n{\n    public function publishedPosts()\n    {\n        return $this-\u003ehasMany(Post::class)-\u003epublished();\n    }\n}\n```\n\nIf you call `User::joinRelationship('publishedPosts')-\u003eget()`, it will also apply the additional published scope to the join clause. It would produce an SQL more or less like this:\n\n```sql\nselect users.* from users inner join posts on posts.user_id = posts.id and posts.published = 1\n```\n\n#### Global Scopes\n\nIf your model have global scopes applied to it, you can enable the global scopes by calling the `withGlobalScopes` method in your join clause, like this:\n\n```php\nUserProfile::joinRelationship('users', fn ($join) =\u003e $join-\u003ewithGlobalScopes());\n```\n\nThere's, though, a gotcha here. Your global scope **cannot** type-hint the `Eloquent\\Builder` class in the first parameter of the `apply` method, otherwise you will get errors.\n\n### 2 - Querying relationship existence (Using Joins)\n\n[Querying relationship existence](https://laravel.com/docs/7.x/eloquent-relationships#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 may not be the more performant choice, depending on how many records you have or the structure of your tables.\n\nThis packages implements the same functionality, but instead of using the `where exists` syntax, it uses **joins**. Below, you can see the methods this package implements and also the Laravel equivalent.\n\nPlease note that although the methods are similar, you will not always get the same results when using joins, depending on the context of your query. You should be aware of the differences between querying the data with `where exists` vs `joins`.\n\n**Laravel Native Methods**\n\n``` php\nUser::has('posts');\nUser::has('posts.comments');\nUser::has('posts', '\u003e', 3);\nUser::whereHas('posts', fn ($query) =\u003e $query-\u003ewhere('posts.published', true));\nUser::whereHas('posts.comments', ['posts' =\u003e fn ($query) =\u003e $query-\u003ewhere('posts.published', true));\nUser::doesntHave('posts');\n```\n\n**Package equivalent, but using joins**\n\n```php\nUser::powerJoinHas('posts');\nUser::powerJoinHas('posts.comments');\nUser::powerJoinHas('posts.comments', '\u003e', 3);\nUser::powerJoinWhereHas('posts', function ($join) {\n    $join-\u003ewhere('posts.published', true);\n});\nUser::powerJoinDoesntHave('posts');\n```\n\nWhen using the `powerJoinWhereHas` method with relationships that involves more than 1 table (One to Many, Many to Many, etc.), use the array syntax to pass the callback:\n\n```php\nUser::powerJoinWhereHas('commentsThroughPosts', [\n    'comments' =\u003e fn ($query) =\u003e $query-\u003ewhere('body', 'a')\n])-\u003eget());\n```\n\n### 3 - Order by\n\nYou can also sort your query results using a column from another table using the `orderByPowerJoins` method.\n\n```php\nUser::orderByPowerJoins('profile.city');\n```\n\nIf you need to pass some raw values for the order by function, you can do like this:\n\n```php\nUser::orderByPowerJoins(['profile', DB::raw('concat(city, \", \", state)')]);\n```\n\nThis query will sort the results based on the `city` column on the `user_profiles` table. You can also sort your results by aggregations (`COUNT`, `SUM`, `AVG`, `MIN` or `MAX`).\n\nFor instance, to sort users with the highest number of posts, you can do this:\n\n```php\n$users = User::orderByPowerJoinsCount('posts.id', 'desc')-\u003eget();\n```\n\nOr, to get the list of posts where the comments contain the highest average of votes.\n\n```php\n$posts = Post::orderByPowerJoinsAvg('comments.votes', 'desc')-\u003eget();\n```\n\nYou also have methods for `SUM`, `MIN` and `MAX`:\n\n```php\nPost::orderByPowerJoinsSum('comments.votes');\nPost::orderByPowerJoinsMin('comments.votes');\nPost::orderByPowerJoinsMax('comments.votes');\n```\n\nIn case you want to use left joins in sorting, you also can:\n\n```php\nPost::orderByLeftPowerJoinsCount('comments.votes');\nPost::orderByLeftPowerJoinsAvg('comments.votes');\nPost::orderByLeftPowerJoinsSum('comments.votes');\nPost::orderByLeftPowerJoinsMin('comments.votes');\nPost::orderByLeftPowerJoinsMax('comments.votes');\n```\n\n***\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n### Security\n\nIf you discover any security related issues, please email security@kirschbaumdevelopment.com instead of using the issue tracker.\n\n## Credits\n\n- [Luis Dalmolin](https://github.com/luisdalmolin)\n\n## Sponsorship\n\nDevelopment of this package is sponsored by Kirschbaum Development Group, a developer driven company focused on problem solving, team building, and community. Learn more [about us](https://kirschbaumdevelopment.com) or [join us](https://careers.kirschbaumdevelopment.com)!\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkirschbaum-development%2Feloquent-power-joins","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkirschbaum-development%2Feloquent-power-joins","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkirschbaum-development%2Feloquent-power-joins/lists"}