@@ -3,6 +3,221 @@ section: Eloquent
3
3
title : Tenant Child Models
4
4
position : 1
5
5
slug : tenant-child-models
6
- description :
7
- The Description
6
+ description :
7
+ When your tenant is an Eloquent model, you'll also have child models that belong to the tenant. Scoping every query on these models to the current tenant is often messy and complex, but fortunately, Sprout has something for that.
8
8
---
9
+
10
+ ## Introduction
11
+
12
+ When users are making use of your application, it's important to ensure
13
+ that they only have access to data that,
14
+ ignoring application-specific instructions, is available to the current tenant.
15
+ If your data is stored in the database,
16
+ and possibly represented by an Eloquent model,
17
+ you're going
18
+ to need to add conditions to the database queries that ensure the data is accessible to the current tenant.
19
+ This can be a frustrating process, and it's easy to overlook, accidentally exposing cross-tenant data.
20
+
21
+ ## How it works
22
+
23
+ Sprout supports two tenant relations,
24
+ [ one-to-many] ( https://laravel.com/docs/11.x/eloquent-relationships#one-to-many-inverse )
25
+ and [ many-to-many] ( https://laravel.com/docs/11.x/eloquent-relationships#many-to-many ) .
26
+ While the exact specifics depend on the type of relation, both do the following.
27
+
28
+ - Accessed through a trait
29
+ - Adds a global scope to the model
30
+ - Adds an observer to the model
31
+ - Automatically scopes queries to the current tenant
32
+ - Automatically sets the tenant relation when creating new models
33
+ - Automatically populates the tenant relation when retrieving models from the database
34
+ - Checks that models tenant matches the current tenant when creating or retrieving
35
+
36
+ > [ !WARNING]
37
+ > If the model is being accessed outside a [ multitenanted context] ( multitenanted-context ) ,
38
+ > the functionality provided by these features will be skipped.
39
+
40
+ ### The tenant relation
41
+
42
+ Both relations require that the model has a relationship definition to the tenant model,
43
+ and that the relation is flagged appropriately.
44
+ This can be done by overriding a method, or using a PHP attribute.
45
+
46
+ If you want to have a method that returns the name of the tenant relation,
47
+ you can override the ` getTenantRelationName() ` method.
48
+
49
+ ``` php
50
+ public function blog(): BelongsTo
51
+ {
52
+ return $this->belongsTo(Blog::class);
53
+ }
54
+
55
+ public function getTenantRelationName(): string
56
+ {
57
+ return 'blog';
58
+ }
59
+ ```
60
+
61
+ If you'd rather use a PHP attribute,
62
+ you can add the ` Sprout\Attributes\TenantRelation ` attribute to the method
63
+ that represents the relationship to the tenant.
64
+
65
+ ``` php
66
+ #[TenantRelation]
67
+ public function blog(): BelongsTo
68
+ {
69
+ return $this->belongsTo(Blog::class);
70
+ }
71
+ ```
72
+
73
+ ### Optional tenant
74
+
75
+ Sometimes you'll have models that don't need a tenant, but can have one.
76
+ In those cases,
77
+ you'll need to implement the marker interface ` Sprout\Database\Eloquent\Contracts\OptionalTenant ` on your model.
78
+
79
+ ``` php
80
+ class Post extends Model implements OptionalTenant
81
+ {
82
+ //...
83
+ }
84
+ ```
85
+
86
+ Models with optional tenants can be retrieved and created outside an active tenancy.
87
+ Normally, if you attempt to create a new instance of a child model,
88
+ or retrieve one from the database,
89
+ and there isn't a current tenant, an [ exception would be thrown] ( exceptions#tenant-missing-exception ) .
90
+
91
+ > [ !NOTE]
92
+ > An [ exception will still be thrown] ( exceptions#tenant-mismatch-exception )
93
+ > if the model has a different tenant to the current one.
94
+
95
+ ### Ignoring tenant restrictions
96
+
97
+ If you want to temporarily make a model have an optional tenant,
98
+ without implementing the interface, you can also do that.
99
+ To disable the restrictions, call the static method ` ignoreTenantRestrictions() ` on the model,
100
+ and then call ` resetTenantRestrictions() ` once you've finished.
101
+
102
+ ``` php
103
+ Post::ignoreTenantRestrictions();
104
+
105
+ $posts = Post::get();
106
+
107
+ Post::resetTenantRestrictions();
108
+ ```
109
+
110
+ Alternatively, if you want to disable restrictions for a single action or operation, you can use the static method
111
+ ` withoutTenantRestrictions() ` .
112
+
113
+ ``` php
114
+ $posts = Post::withoutTenantRestrictions(function() {
115
+ return Post::get();
116
+ });
117
+ ```
118
+
119
+ This method disables the restrictions, calls the callback without arguments,
120
+ enables the restrictions, and returns the callbacks' return value.
121
+
122
+ ## Belongs to Tenant
123
+
124
+ If your model belongs to a specific tenant, as in,
125
+ it has a relationship to it
126
+ using Laravel's [ inverse one-to-many] ( https://laravel.com/docs/11.x/eloquent-relationships#one-to-many-inverse )
127
+ relation,
128
+ you can add the ` Sprout\Database\Eloquent\Concerns\BelongsToTenant ` trait.
129
+
130
+ ``` php
131
+ class Post extends Model
132
+ {
133
+ use BelongsToTenant;
134
+
135
+ //...
136
+
137
+ #[TenantRelation]
138
+ public function blog(): BelongsTo
139
+ {
140
+ return $this->belongsTo(Blog::class);
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Querying a belongs to tenant model
146
+
147
+ When attempting to query a model that belongs to a tenant,
148
+ a where clause will automatically be added for the current tenant.
149
+ This call uses the [ tenants' key] ( tenants#tenant-keys ) ,
150
+ and the relations foreign key.
151
+
152
+ ### Creating a belongs to tenant model
153
+
154
+ When attempting to create a new model that belongs to a tenant,
155
+ the child model will automatically be associated with the current tenant when attempting to save the model.
156
+ If when saving, the tenant relation is already populated,
157
+ its current value will be compared to the current [ tenants' key] ( tenants#tenant-keys ) ,
158
+ throwing a [ tenant mismatch exception] ( exceptions#tenant-mismatch-exception ) if they do not match.
159
+
160
+ > [ !NOTE]
161
+ > Because of the way one-to-many relations work with Eloquent,
162
+ > this also means the relation will be populated by the tenant model too.
163
+
164
+ ### Retrieving a belongs to tenant model
165
+
166
+ When retrieving an existing tenant child model from the database,
167
+ when the model is being hydrated (the data from the database is populating the model),
168
+ the tenant relation will also be populated with the current tenant.
169
+ During this process, the models' tenant will be compared to the current tenant,
170
+ throwing a [ tenant mismatch exception] ( exceptions#tenant-mismatch-exception ) if they do not match.
171
+ You can disable this functionality
172
+ by removing the [ hydrating tenant relations] ( tenancy-options#hydrating-tenant-relations ) tenancy option.
173
+
174
+ ## Belongs to many Tenants
175
+
176
+ If your model belongs to one or more tenants, as in,
177
+ it has a relationship to its tenants
178
+ using Laravel's [ many-to-many] ( https://laravel.com/docs/11.x/eloquent-relationships#many-to-many ) relation,
179
+ you can add the ` Sprout\Database\Eloquent\Concerns\BelongsToManyTenants ` trait.
180
+
181
+ ``` php
182
+ class Category extends Model
183
+ {
184
+ use BelongsToManyTenants;
185
+
186
+ //...
187
+
188
+ #[TenantRelation]
189
+ public function blogs(): BelongsToMany
190
+ {
191
+ return $this->belongsToMany(Blog::class);
192
+ }
193
+ }
194
+ ```
195
+
196
+ ### Querying a belongs to many tenants model
197
+
198
+ When attempting to query a model that belongs to multiple tenants,
199
+ a where clause will automatically be added for the current tenant.
200
+ This call uses the [ tenants' key] ( tenants#tenant-keys ) ,
201
+ and the [ ` whereHas() ` method] ( https://laravel.com/docs/11.x/eloquent-relationships#querying-relationship-existence ) .
202
+
203
+ ### Creating a belongs to many tenants model
204
+
205
+ When attempting to create a new model that belongs to multiple tenants,
206
+ the child model will automatically be associated with the current tenant once the model has been saved.
207
+ Unlike the belongs to functionality, the relationship can only be saved once the child model has been saved.
208
+
209
+ ### Retrieving a belongs to many tenants model
210
+
211
+ When retrieving an existing tenant child model from the database,
212
+ when the model is being hydrated (the data from the database is populating the model),
213
+ the tenant relation will also be populated with the current tenant.
214
+ During this process, the models' tenant will be compared to the current tenant,
215
+ throwing a [ tenant mismatch exception] ( exceptions#tenant-mismatch-exception ) if they do not match.
216
+ You can disable this functionality
217
+ by removing the [ hydrating tenant relations] ( tenancy-options#hydrating-tenant-relations ) tenancy option.
218
+
219
+ > [ !WARNING]
220
+ > This functionality does not query the database
221
+ > to ensure that the retrieved models to do in fact belong to the current tenant.
222
+ > This should be okay assuming the tenant restrictions were not disabled, and it was queried normally.
223
+ > It will also overwrite any value for the relation to only contain the current tenant.
0 commit comments