All Projects → archtechx → laravel-hasmanywithinverse

archtechx / laravel-hasmanywithinverse

Licence: MIT license
Define HasMany while also setting the inverse relationship in Laravel.

Programming Languages

PHP
23972 projects - #3 most used programming language
shell
77523 projects

Projects that are alternatives of or similar to laravel-hasmanywithinverse

eloquent-filemaker
A Model extension and Eloquent driver for Laravel connecting to FileMaker through the Data API
Stars: ✭ 38 (-33.33%)
Mutual labels:  eloquent
state-machine
The hyn state machine package is a flexible library that helps you move Eloquent models from States through Transitions while emitting events along the way.
Stars: ✭ 14 (-75.44%)
Mutual labels:  eloquent
query-filter
🔍 Database/Eloquent Query Builder filters for Laravel
Stars: ✭ 69 (+21.05%)
Mutual labels:  eloquent
plug
A collection of pluggable traits for Eloquent (Laravel) models
Stars: ✭ 13 (-77.19%)
Mutual labels:  eloquent
eloquent-filter
Library to form search criteria through expressions in the query string
Stars: ✭ 23 (-59.65%)
Mutual labels:  eloquent
php-orm-benchmark
The benchmark to compare performance of PHP ORM solutions.
Stars: ✭ 82 (+43.86%)
Mutual labels:  eloquent
laravel-quasar
⏰📊✨Laravel Time Series - Provides an API to create and maintain data projections (statistics, aggregates, etc.) from your Eloquent models, and convert them to time series.
Stars: ✭ 78 (+36.84%)
Mutual labels:  eloquent
db-safedelete
Attempts to invoke force delete, if it fails - falls back to soft delete
Stars: ✭ 16 (-71.93%)
Mutual labels:  eloquent
acorn-db
Provides Acorn projects with Eloquent Models for WordPress data.
Stars: ✭ 30 (-47.37%)
Mutual labels:  eloquent
laravel-scoped-cache
Easily cache items specific to your Eloquent models without having to append the ID.
Stars: ✭ 28 (-50.88%)
Mutual labels:  eloquent
slim-boilerplate
A PHP boilerplate,for a fast API prototyping based on Slim Framework, for start projects with Eloquent ORM, Validation, Auth (JWT), Repositories and Transformers ready
Stars: ✭ 58 (+1.75%)
Mutual labels:  eloquent
laravel-query-inspector
The missing laravel helper that allows you to inspect your eloquent queries with it's bind parameters
Stars: ✭ 59 (+3.51%)
Mutual labels:  eloquent
laravel-sybase
Connection and Laravel Eloquent driver for Sybase
Stars: ✭ 29 (-49.12%)
Mutual labels:  eloquent
laravel-simplegrid
A simple component for generating powerful grids with Laravel.
Stars: ✭ 35 (-38.6%)
Mutual labels:  eloquent
LazyBelongsToMany
A lightweight implementation of Laravel's belongs To many
Stars: ✭ 23 (-59.65%)
Mutual labels:  eloquent
inertiajs-tables-laravel-query-builder
Inertia.js Tables for Laravel Query Builder
Stars: ✭ 391 (+585.96%)
Mutual labels:  eloquent
laravel-cachable-attributes
Allows to cache attribute accessor values in an easy way.
Stars: ✭ 24 (-57.89%)
Mutual labels:  eloquent
laravel-auto-morph-map
THIS PACKAGE HAS BEEN DEPRECATED — Automatically alias and map the polymorphic types of Eloquent models.
Stars: ✭ 55 (-3.51%)
Mutual labels:  eloquent
Laravel-Auto-Hard-Deleter
Laravel and Lumen Auto Hard Deleter
Stars: ✭ 34 (-40.35%)
Mutual labels:  eloquent
eloquent-hashids
Automatically generate and persist Hashids for newly created Eloquent models.
Stars: ✭ 17 (-70.18%)
Mutual labels:  eloquent

stancl/laravel-hasmanywithinverse

Why?

Jonathan Reinink wrote a great blog post about Optimizing circular relationships in Laravel

By manually setting the (belongsTo) relationship to a parent model on related (hasMany) child models, you can save unnecessary queries for the parent model -- when the child needs an instance of the parent model.

This probably sounds confusing, so just read the blog post. It's very good.

Jonathan's approach suggests using something like this:

$category->products->each->setRelation('category', $category);

This works, but it's not very clean and there are cases when it doesn't work. For example, on model creation.

If you're accessing the parent model in creating and saving events on the children, the ->each->setRelation() approach won't help you at all. (And if you're building a complex app with Laravel Nova, there's a high chance you're using lots of such events.)

Practical Example & Benchmarks

I have an e-commerce application where an Order has child models: OrderProduct, OrderStatus and OrderFee (think shipping costs, payment fees, etc).

When some of those models are being created (creating Eloquent event), they are accessing the parent model.

For example, OrderProducts convert their prices to $this->order->currency. OrderFees check for other order fees, and they prevent creating themselves if a fee with the same code already exists (so that you can't have, say, the shipping cost counted twice). Etc.

This results in order creation being expensive, resulting in a large amount of n+1 queries.

Benchmark

I haven't run a huge amount of tests, so I won't present the time differences here. I will only talk about database query count.

I have created an order with 6 products.

This is the amount of queries made with regular hasMany()

Query count with hasMany()

And now I just replace all of these calls:

return $this->hasMany(...);

with these calls

return $this->hasManyWithInverse(..., 'order');

inside the Order model.

And this is the amount of queries made with hasManyWithInverse()

Query count with hasManyWithInverse()

See the query count reduction.

The duration was also decreased from 114ms to 45ms on my machine, though note that I did not run this test a million times to calculate an average duration, so that benchmark might not be very accurate.

This is pretty impressive for a free improvement that only requires changing a few simple calls to a similar method.

But note that this is not a silver bullet for solving all n+1 queries. As you can see, even with this implemented, my app still has many duplicated queries. (Although not all are unintentional n+1s as there are a few $this->refresh() calls to keep the order up-to-date after state transitions).

Installation

Laravel 6.x, 7.x, and 8.x is supported.

composer require stancl/laravel-hasmanywithinverse

Usage

namespace App;

use Stancl\HasManyWithInverse\HasManyWithInverse;

class Order extends Model
{
    use HasManyWithInverse;

    public function products()
    {
        // 'order' is the name of the relationship in the other model, see below
        return $this->hasManyWithInverse(OrderProduct::class, 'order');
    }
}

class OrderProduct extends Model
{
    public function order()
    {
        return $this->belongsTo(Order::class);
    }
}

You may also want to use the trait in a base Eloquent model and then use $this->hasManyWithInverse() without thinking about traits in the specific models.

Details

The (simple) internals of the package are just methods copied from Eloquent source code, with a few lines added to them. The hasManyWithInverse() method signature is the same as hasMany() (you can set $foreignKey and $localKey), except the second argument ($inverse) was added to let you define the name of the relationship on the child model, and the last argument ($config) was added to let you configure the relation setting's behavior.

This package sets the parent relation on children both when creating children ($child = $parent->children()->create()) and when resolving parent's children ($children = $parent->children). You can customize this behavior for every relationship.

To disable setting the relationship during child creation, do this:

class Parent extends Model
{
    public function children()
    {
        return $this->hasManyWithInverse(Child::class, 'parent', null, null, ['setRelationOnCreation' => false]);
    }
}

To disable setting the relationship during child resolution, do this:

class Parent extends Model
{
    public function children()
    {
        return $this->hasManyWithInverse(Child::class, 'parent', null, null, ['setRelationOnResolution' => false]);
    }
}

You may also pass a callable as the config value. This is useful if you want to disable this behavior on some requests. See example below.

Laravel Nova

It's a good idea to disable setting the relationship on resolution for Nova requests. They tend to make a lot of queries and this can slow the page down (or result in 502 errors).

Here's an example implementation using a base model and adding config to filter out Nova requests.

abstract class Model extends EloquentModel
{
    use HasManyWithInverse {
        hasManyWithInverse as originalHasManyWithInverse;
    }

    public function hasManyWithInverse($related, $inverse, $foreignKey = null, $localKey = null, $config = [])
    {
        $config = array_merge(['setRelationOnResolution' => function () {
            if (request()->route() && in_array('nova', request()->route()->middleware())) {
                return false;
            }
        }], $config);

        return $this->originalHasManyWithInverse($related, $inverse, $foreignKey, $localKey, $config);
    }
}
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].