├── .gitignore
├── tests
├── ChildModel.php
├── ParentModel.php
├── ModelResolutionTest.php
├── TestCase.php
├── ConfigTest.php
└── ModelCreationTest.php
├── .travis.yml
├── src
├── HasManyWithInverse.php
└── HasManyWithInverseRelationship.php
├── composer.json
├── LICENSE
├── phpunit.xml
├── check
├── .github
└── workflows
│ └── ci.yml
├── .php-cs-fixer.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | composer.lock
3 | .phpunit.result.cache
4 | .php-cs-fixer.cache
5 |
--------------------------------------------------------------------------------
/tests/ChildModel.php:
--------------------------------------------------------------------------------
1 | belongsTo(ParentModel::class, 'parent_id');
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/ParentModel.php:
--------------------------------------------------------------------------------
1 | HasManyWithInverse(ChildModel::class, 'parent', 'parent_id');
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - '7.2'
4 | - '7.3'
5 |
6 | env:
7 | matrix:
8 | - LARAVEL='^6.0'
9 | - LARAVEL='^7.0'
10 | - LARAVEL='^8.0'
11 |
12 | jobs:
13 | exclude:
14 | - php: '7.2'
15 | env: LARAVEL='^8.0'
16 |
17 | install:
18 | - travis_retry composer require "illuminate/database:${LARAVEL}" --no-update
19 | - travis_retry composer install --prefer-source --no-interaction
20 |
21 | before_script:
22 | - export CODECOV_TOKEN="6de43b13-9ea8-498c-bb32-c29f3f098517"
23 |
24 | script: vendor/bin/phpunit
25 |
26 | after_success:
27 | - bash <(curl -s https://codecov.io/bash)
28 |
--------------------------------------------------------------------------------
/tests/ModelResolutionTest.php:
--------------------------------------------------------------------------------
1 | $parent->id]);
14 |
15 | /** @var ChildModel $child */
16 | $child = $parent->children->first();
17 |
18 | $this->assertTrue($child->relationLoaded('parent'));
19 | $this->assertSame($parent->id, $child->getRelations()['parent']->id);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/HasManyWithInverse.php:
--------------------------------------------------------------------------------
1 | newRelatedInstance($related);
15 | $foreignKey = $foreignKey ?: $this->getForeignKey();
16 | $localKey = $localKey ?: $this->getKeyName();
17 |
18 | return new HasManyWithInverseRelationship($instance->newQuery(), $this, $instance->getTable() . '.' . $foreignKey, $localKey, $inverse, $config);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | });
19 |
20 | Schema::create('children', function (Blueprint $table) {
21 | $table->bigIncrements('id');
22 |
23 | $table->unsignedBigInteger('parent_id');
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stancl/laravel-hasmanywithinverse",
3 | "description": "Define HasMany while also setting the inverse relationship in Laravel.",
4 | "type": "package",
5 | "license": "MIT",
6 | "autoload": {
7 | "psr-4": {
8 | "Stancl\\HasManyWithInverse\\": "src/"
9 | }
10 | },
11 | "autoload-dev": {
12 | "psr-4": {
13 | "Stancl\\HasManyWithInverse\\Tests\\": "tests/"
14 | }
15 | },
16 | "authors": [
17 | {
18 | "name": "Samuel Štancl",
19 | "email": "samuel.stancl@gmail.com"
20 | }
21 | ],
22 | "require": {
23 | "illuminate/database": "^9.0|^10.0"
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit": "^9.0|^10.0",
27 | "orchestra/testbench": "^7.0|^8.0"
28 | },
29 | "minimum-stability": "dev",
30 | "prefer-stable": true
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Samuel Štancl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./src
6 |
7 |
8 | ./src/routes.php
9 |
10 |
11 |
12 |
13 | ./tests
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/check:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | offer_run() {
5 | read -p "For more output, run $1. Run it now (Y/n)? " run
6 |
7 | case ${run:0:1} in
8 | n|N )
9 | exit 1
10 | ;;
11 | * )
12 | $1
13 | ;;
14 | esac
15 |
16 | exit 1
17 | }
18 |
19 | if (php-cs-fixer fix --dry-run --config=.php-cs-fixer.php > /dev/null 2>/dev/null); then
20 | echo '✅ php-cs-fixer OK'
21 | else
22 | read -p "⚠️ php-cs-fixer found issues. Fix (Y/n)? " fix
23 | case ${fix:0:1} in
24 | n|N )
25 | echo '❌ php-cs-fixer FAIL'
26 | offer_run 'php-cs-fixer fix --config=.php-cs-fixer.php'
27 | ;;
28 | * )
29 | if (php-cs-fixer fix --config=.php-cs-fixer.php > /dev/null 2>/dev/null); then
30 | echo '✅ php-cs-fixer OK'
31 | else
32 | echo '❌ php-cs-fixer FAIL'
33 | offer_run 'php-cs-fixer fix --config=.php-cs-fixer.php'
34 | fi
35 | ;;
36 | esac
37 | fi
38 |
39 | if (./vendor/bin/phpunit > /dev/null 2>/dev/null); then
40 | echo '✅ PHPUnit OK'
41 | else
42 | echo '❌ PHPUnit FAIL'
43 | offer_run './vendor/bin/phpunit'
44 | fi
45 |
46 | echo '=================='
47 | echo '✅ Everything OK'
48 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | phpunit:
7 | name: Tests (PHPUnit) L${{ matrix.laravel }}
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | laravel: [9, 10]
12 |
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v2
16 |
17 | - name: Setup PHP
18 | uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: '8.1'
21 |
22 | - name: Install dependencies
23 | run: composer require "laravel/framework:^${{matrix.laravel}}.0"
24 | - name: Run tests
25 | run: vendor/bin/phpunit
26 |
27 | php-cs-fixer:
28 | name: Code style (php-cs-fixer)
29 | runs-on: ubuntu-latest
30 | steps:
31 | - uses: actions/checkout@v2
32 | - name: Install php-cs-fixer
33 | run: composer global require friendsofphp/php-cs-fixer
34 | - name: Run php-cs-fixer
35 | run: $HOME/.composer/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php
36 | - name: Commit changes from php-cs-fixer
37 | uses: EndBug/add-and-commit@v5
38 | with:
39 | author_name: Samuel Štancl
40 | author_email: samuel.stancl@gmail.com
41 | message: Fix code style (php-cs-fixer)
42 |
--------------------------------------------------------------------------------
/src/HasManyWithInverseRelationship.php:
--------------------------------------------------------------------------------
1 | relationToParent = $relationToParent;
22 | $this->config = $config;
23 |
24 | parent::__construct($query, $parent, $foreignKey, $localKey);
25 | }
26 |
27 | /**
28 | * @param array $attributes
29 | *
30 | * @return \Illuminate\Database\Eloquent\Model
31 | */
32 | public function create(array $attributes = [])
33 | {
34 | return tap($this->related->newInstance($attributes), function ($instance) {
35 | $this->setForeignAttributesForCreate($instance);
36 |
37 | if ($this->config('setRelationOnCreation', true)) {
38 | $instance->setRelation($this->relationToParent, $this->getParent());
39 | }
40 |
41 | $instance->save();
42 | });
43 | }
44 |
45 | public function getResults()
46 | {
47 | $results = parent::getResults();
48 |
49 | if ($this->config('setRelationOnResolution', true)) {
50 | $results->each->setRelation($this->relationToParent, $this->getParent());
51 | }
52 |
53 | return $results;
54 | }
55 |
56 | protected function config(string $key, $default)
57 | {
58 | if (! isset($this->config[$key])) {
59 | return $default;
60 | }
61 |
62 | if (is_callable($this->config[$key])) {
63 | return $this->config[$key]();
64 | }
65 |
66 | return $this->config[$key];
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/ConfigTest.php:
--------------------------------------------------------------------------------
1 | hasManyWithInverse(ChildModel::class, 'parent', 'parent_id', null, [
15 | 'setRelationOnCreation' => false,
16 | ]);
17 | }
18 | })::create([]);
19 |
20 | /** @var ChildModel $child */
21 | $child = $parent->children()->create([]);
22 |
23 | $this->assertFalse($child->relationLoaded('parent'));
24 | }
25 |
26 | /** @test */
27 | public function setting_relation_on_resolution_can_be_disabled()
28 | {
29 | /** @var ParentModel $parent */
30 | $parent = (new class extends ParentModel {
31 | public function children()
32 | {
33 | return $this->hasManyWithInverse(ChildModel::class, 'parent', 'parent_id', null, [
34 | 'setRelationOnResolution' => false,
35 | ]);
36 | }
37 | })::create([]);
38 |
39 | /** @var ChildModel $child */
40 | $child = $parent->children()->create([]);
41 |
42 | $this->assertFalse($parent->children->first()->relationLoaded('parent'));
43 | }
44 |
45 | /** @test */
46 | public function config_value_can_be_a_closure()
47 | {
48 | /** @var ParentModel $parent */
49 | $parent = (new class extends ParentModel {
50 | public function children()
51 | {
52 | return $this->hasManyWithInverse(ChildModel::class, 'parent', 'parent_id', null, [
53 | 'setRelationOnResolution' => function () {
54 | return false;
55 | },
56 | ]);
57 | }
58 | })::create([]);
59 |
60 | /** @var ChildModel $child */
61 | $child = $parent->children()->create([]);
62 |
63 | $this->assertFalse($parent->children->first()->relationLoaded('parent'));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/ModelCreationTest.php:
--------------------------------------------------------------------------------
1 | children()->create([]);
15 |
16 | $this->assertTrue($child->relationLoaded('parent'));
17 | $this->assertSame($parent->id, $child->getRelations()['parent']->id);
18 | }
19 |
20 | /** @test */
21 | public function children_have_the_parent_relationship_automatically_set_when_being_created_using_createMany()
22 | {
23 | /** @var ParentModel $parent */
24 | $parent = ParentModel::create([]);
25 |
26 | /** @var ChildModel $child1 */
27 | /** @var ChildModel $child2 */
28 | [$child1, $child2] = $parent->children()->createMany([[], []]);
29 |
30 | $this->assertTrue($child1->relationLoaded('parent'));
31 | $this->assertSame($parent->id, $child1->getRelations()['parent']->id);
32 |
33 | $this->assertTrue($child2->relationLoaded('parent'));
34 | $this->assertSame($parent->id, $child2->getRelations()['parent']->id);
35 | }
36 |
37 | /** @test */
38 | public function children_have_the_parent_relationship_automatically_set_in_creating_event()
39 | {
40 | $parentId = null;
41 |
42 | /** @var ParentModel $parent */
43 | $parent = ParentModel::create([]);
44 |
45 | ChildModel::creating(function (ChildModel $child) use (&$parentId) {
46 | if ($child->relationLoaded('parent')) {
47 | $parentId = $child->getRelations()['parent']->id;
48 | }
49 | });
50 |
51 | $parent->children()->create([]);
52 |
53 | $this->assertSame($parent->id, $parentId);
54 | }
55 |
56 | /** @test */
57 | public function children_have_the_parent_relationship_automatically_set_in_saving_event()
58 | {
59 | $parentId = null;
60 |
61 | /** @var ParentModel $parent */
62 | $parent = ParentModel::create([]);
63 |
64 | ChildModel::saving(function (ChildModel $child) use (&$parentId) {
65 | if ($child->relationLoaded('parent')) {
66 | $parentId = $child->getRelations()['parent']->id;
67 | }
68 | });
69 |
70 | $parent->children()->create([]);
71 |
72 | $this->assertSame($parent->id, $parentId);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | ['syntax' => 'short'],
8 | 'binary_operator_spaces' => [
9 | 'default' => 'single_space',
10 | 'operators' => [
11 | '=>' => null,
12 | '|' => 'no_space',
13 | ],
14 | ],
15 | 'blank_line_after_namespace' => true,
16 | 'blank_line_after_opening_tag' => true,
17 | 'no_superfluous_phpdoc_tags' => true,
18 | 'blank_line_before_statement' => [
19 | 'statements' => ['return'],
20 | ],
21 | 'braces' => true,
22 | 'cast_spaces' => true,
23 | 'class_definition' => true,
24 | 'concat_space' => [
25 | 'spacing' => 'one',
26 | ],
27 | 'declare_equal_normalize' => true,
28 | 'elseif' => true,
29 | 'encoding' => true,
30 | 'full_opening_tag' => true,
31 | 'declare_strict_types' => true,
32 | 'fully_qualified_strict_types' => true, // added by Shift
33 | 'function_declaration' => true,
34 | 'function_typehint_space' => true,
35 | 'heredoc_to_nowdoc' => true,
36 | 'include' => true,
37 | 'increment_style' => ['style' => 'post'],
38 | 'indentation_type' => true,
39 | 'linebreak_after_opening_tag' => true,
40 | 'line_ending' => true,
41 | 'lowercase_cast' => true,
42 | 'constant_case' => true,
43 | 'lowercase_keywords' => true,
44 | 'lowercase_static_reference' => true, // added from Symfony
45 | 'magic_method_casing' => true, // added from Symfony
46 | 'magic_constant_casing' => true,
47 | 'method_argument_space' => true,
48 | 'native_function_casing' => true,
49 | 'no_alias_functions' => true,
50 | 'no_extra_blank_lines' => [
51 | 'tokens' => [
52 | 'extra',
53 | 'throw',
54 | 'use',
55 | 'use_trait',
56 | ],
57 | ],
58 | 'no_blank_lines_after_class_opening' => true,
59 | 'no_blank_lines_after_phpdoc' => true,
60 | 'no_closing_tag' => true,
61 | 'no_empty_phpdoc' => true,
62 | 'no_empty_statement' => true,
63 | 'no_leading_import_slash' => true,
64 | 'no_leading_namespace_whitespace' => true,
65 | 'no_mixed_echo_print' => [
66 | 'use' => 'echo',
67 | ],
68 | 'no_multiline_whitespace_around_double_arrow' => true,
69 | 'multiline_whitespace_before_semicolons' => [
70 | 'strategy' => 'no_multi_line',
71 | ],
72 | 'no_short_bool_cast' => true,
73 | 'no_singleline_whitespace_before_semicolons' => true,
74 | 'no_spaces_after_function_name' => true,
75 | 'no_spaces_around_offset' => true,
76 | 'no_spaces_inside_parenthesis' => true,
77 | 'no_trailing_comma_in_list_call' => true,
78 | 'no_trailing_comma_in_singleline_array' => true,
79 | 'no_trailing_whitespace' => true,
80 | 'no_trailing_whitespace_in_comment' => true,
81 | 'no_unneeded_control_parentheses' => true,
82 | 'no_unreachable_default_argument_value' => true,
83 | 'no_useless_return' => true,
84 | 'no_whitespace_before_comma_in_array' => true,
85 | 'no_whitespace_in_blank_line' => true,
86 | 'normalize_index_brace' => true,
87 | 'not_operator_with_successor_space' => true,
88 | 'object_operator_without_whitespace' => true,
89 | 'ordered_imports' => ['sort_algorithm' => 'alpha'],
90 | 'phpdoc_indent' => true,
91 | 'general_phpdoc_tag_rename' => true,
92 | 'phpdoc_no_access' => true,
93 | 'phpdoc_no_package' => true,
94 | 'phpdoc_no_useless_inheritdoc' => true,
95 | 'phpdoc_scalar' => true,
96 | 'phpdoc_single_line_var_spacing' => true,
97 | 'phpdoc_summary' => true,
98 | 'phpdoc_to_comment' => false,
99 | 'phpdoc_trim' => true,
100 | 'phpdoc_types' => true,
101 | 'phpdoc_var_without_name' => true,
102 | 'psr_autoloading' => true,
103 | 'self_accessor' => true,
104 | 'short_scalar_cast' => true,
105 | 'simplified_null_return' => false, // disabled by Shift
106 | 'single_blank_line_at_eof' => true,
107 | 'single_blank_line_before_namespace' => true,
108 | 'single_class_element_per_statement' => true,
109 | 'single_import_per_statement' => false,
110 | 'single_line_after_imports' => true,
111 | 'no_unused_imports' => true,
112 | 'single_line_comment_style' => [
113 | 'comment_types' => ['hash'],
114 | ],
115 | 'single_quote' => true,
116 | 'space_after_semicolon' => true,
117 | 'standardize_not_equals' => true,
118 | 'switch_case_semicolon_to_colon' => true,
119 | 'switch_case_space' => true,
120 | 'ternary_operator_spaces' => true,
121 | 'trailing_comma_in_multiline' => true,
122 | 'trim_array_spaces' => true,
123 | 'unary_operator_spaces' => true,
124 | 'whitespace_after_comma_in_array' => true,
125 | ];
126 |
127 | $project_path = getcwd();
128 | $finder = Finder::create()
129 | ->in([
130 | $project_path . '/src',
131 | ])
132 | ->name('*.php')
133 | ->notName('*.blade.php')
134 | ->ignoreDotFiles(true)
135 | ->ignoreVCS(true);
136 |
137 | return (new Config())
138 | ->setFinder($finder)
139 | ->setRules($rules)
140 | ->setRiskyAllowed(true)
141 | ->setUsingCache(true);
142 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # stancl/laravel-hasmanywithinverse
2 |
3 | ## Why?
4 |
5 | [Jonathan Reinink](https://github.com/reinink) wrote a great blog post about [Optimizing circular relationships in Laravel](https://reinink.ca/articles/optimizing-circular-relationships-in-laravel)
6 |
7 | 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.
8 |
9 | This probably sounds confusing, so just read the blog post. It's very good.
10 |
11 | Jonathan's approach suggests using something like this:
12 |
13 | ```php
14 | $category->products->each->setRelation('category', $category);
15 | ```
16 |
17 | This works, but it's not very clean and there are cases when it doesn't work. For example, on model creation.
18 |
19 | 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](https://nova.laravel.com), there's a high chance you're using lots of such events.)
20 |
21 | ## Practical Example & Benchmarks
22 |
23 | I have an e-commerce application where an `Order` has child models: `OrderProduct`, `OrderStatus` and `OrderFee` (think shipping costs, payment fees, etc).
24 |
25 | When some of those models are **being created** (`creating` Eloquent event), they are accessing the parent model.
26 |
27 | For example, `OrderProduct`s convert their prices to `$this->order->currency`. `OrderFee`s 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.
28 |
29 | This results in order creation being expensive, resulting in a large amount of n+1 queries.
30 |
31 | ### Benchmark
32 |
33 | 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.
34 |
35 | I have created an order with 6 products.
36 |
37 | #### This is the amount of queries made with regular `hasMany()`
38 |
39 | 
40 |
41 | And now I just replace all of these calls:
42 |
43 | ```php
44 | return $this->hasMany(...);
45 | ```
46 | with these calls
47 | ```php
48 | return $this->hasManyWithInverse(..., 'order');
49 | ```
50 |
51 | inside the `Order` model.
52 |
53 | #### And this is the amount of queries made with `hasManyWithInverse()`
54 |
55 | 
56 |
57 | See the query count reduction.
58 |
59 | 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.
60 |
61 | This is pretty impressive for **a free improvement that only requires changing a few simple calls to a similar method**.
62 |
63 | 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).
64 |
65 | ## Installation
66 |
67 | Laravel 9.x and 10.x are supported.
68 |
69 | ```
70 | composer require stancl/laravel-hasmanywithinverse
71 | ```
72 |
73 | ## Usage
74 |
75 | ```php
76 | namespace App;
77 |
78 | use Stancl\HasManyWithInverse\HasManyWithInverse;
79 |
80 | class Order extends Model
81 | {
82 | use HasManyWithInverse;
83 |
84 | public function products()
85 | {
86 | // 'order' is the name of the relationship in the other model, see below
87 | return $this->hasManyWithInverse(OrderProduct::class, 'order');
88 | }
89 | }
90 |
91 | class OrderProduct extends Model
92 | {
93 | public function order()
94 | {
95 | return $this->belongsTo(Order::class);
96 | }
97 | }
98 | ```
99 |
100 | 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.
101 |
102 | ## Details
103 |
104 | 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.
105 |
106 | **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.
107 |
108 | To disable setting the relationship during child **creation**, do this:
109 | ```php
110 | class Parent extends Model
111 | {
112 | public function children()
113 | {
114 | return $this->hasManyWithInverse(Child::class, 'parent', null, null, ['setRelationOnCreation' => false]);
115 | }
116 | }
117 | ```
118 |
119 | To disable setting the relationship during child **resolution**, do this:
120 | ```php
121 | class Parent extends Model
122 | {
123 | public function children()
124 | {
125 | return $this->hasManyWithInverse(Child::class, 'parent', null, null, ['setRelationOnResolution' => false]);
126 | }
127 | }
128 | ```
129 |
130 | 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.
131 |
132 | ## Laravel Nova
133 |
134 | 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).
135 |
136 | Here's an example implementation using a base model and adding config to filter out Nova requests.
137 |
138 | ```php
139 | abstract class Model extends EloquentModel
140 | {
141 | use HasManyWithInverse {
142 | hasManyWithInverse as originalHasManyWithInverse;
143 | }
144 |
145 | public function hasManyWithInverse($related, $inverse, $foreignKey = null, $localKey = null, $config = [])
146 | {
147 | $config = array_merge(['setRelationOnResolution' => function () {
148 | if (request()->route() && in_array('nova', request()->route()->middleware())) {
149 | return false;
150 | }
151 | }], $config);
152 |
153 | return $this->originalHasManyWithInverse($related, $inverse, $foreignKey, $localKey, $config);
154 | }
155 | }
156 | ```
157 |
--------------------------------------------------------------------------------