This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Rinvex\Tenants\Traits; |
||
6 | |||
7 | use Illuminate\Support\Arr; |
||
8 | use Illuminate\Database\Eloquent\Model; |
||
9 | use Illuminate\Database\Eloquent\Builder; |
||
10 | use Illuminate\Database\Eloquent\Collection; |
||
11 | use Illuminate\Support\Collection as BaseCollection; |
||
12 | use Illuminate\Database\Eloquent\Relations\MorphToMany; |
||
13 | use Illuminate\Database\Eloquent\ModelNotFoundException; |
||
14 | use Rinvex\Tenants\Exceptions\ModelNotFoundForTenantException; |
||
15 | |||
16 | trait Tenantable |
||
17 | { |
||
18 | /** |
||
19 | * Register a saved model event with the dispatcher. |
||
20 | * |
||
21 | * @param \Closure|string $callback |
||
22 | * |
||
23 | * @return void |
||
24 | */ |
||
25 | abstract public static function saved($callback); |
||
26 | |||
27 | /** |
||
28 | * Register a deleted model event with the dispatcher. |
||
29 | * |
||
30 | * @param \Closure|string $callback |
||
31 | * |
||
32 | * @return void |
||
33 | */ |
||
34 | abstract public static function deleted($callback); |
||
35 | |||
36 | /** |
||
37 | * Define a polymorphic many-to-many relationship. |
||
38 | * |
||
39 | * @param string $related |
||
40 | * @param string $name |
||
41 | * @param string $table |
||
0 ignored issues
–
show
|
|||
42 | * @param string $foreignPivotKey |
||
0 ignored issues
–
show
Should the type for parameter
$foreignPivotKey not be string|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
43 | * @param string $relatedPivotKey |
||
0 ignored issues
–
show
Should the type for parameter
$relatedPivotKey not be string|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
44 | * @param string $parentKey |
||
0 ignored issues
–
show
Should the type for parameter
$parentKey not be string|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
45 | * @param string $relatedKey |
||
0 ignored issues
–
show
Should the type for parameter
$relatedKey not be string|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
46 | * @param bool $inverse |
||
47 | * |
||
48 | * @return \Illuminate\Database\Eloquent\Relations\MorphToMany |
||
49 | */ |
||
50 | abstract public function morphToMany( |
||
51 | $related, |
||
52 | $name, |
||
53 | $table = null, |
||
54 | $foreignPivotKey = null, |
||
55 | $relatedPivotKey = null, |
||
56 | $parentKey = null, |
||
57 | $relatedKey = null, |
||
58 | $inverse = false |
||
59 | ); |
||
60 | |||
61 | /** |
||
62 | * Get all attached tenants to the model. |
||
63 | * |
||
64 | * @return \Illuminate\Database\Eloquent\Relations\MorphToMany |
||
65 | */ |
||
66 | public function tenants(): MorphToMany |
||
67 | { |
||
68 | return $this->morphToMany(config('rinvex.tenants.models.tenant'), 'tenantable', config('rinvex.tenants.tables.tenantables'), 'tenantable_id', 'tenant_id') |
||
69 | ->withTimestamps(); |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * Attach the given tenant(s) to the model. |
||
74 | * |
||
75 | * @param mixed $tenants |
||
76 | * |
||
77 | * @return void |
||
78 | */ |
||
79 | public function setTenantsAttribute($tenants): void |
||
80 | { |
||
81 | static::saved(function (self $model) use ($tenants) { |
||
82 | $model->syncTenants($tenants); |
||
83 | }); |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Boot the tenantable trait for the model. |
||
88 | * |
||
89 | * @return void |
||
90 | */ |
||
91 | public static function bootTenantable() |
||
92 | { |
||
93 | if (app()->has('request.tenant') && $tenant = app('request.tenant')) { |
||
94 | static::addGlobalScope('tenantable', function (Builder $builder) use ($tenant) { |
||
95 | $builder->whereHas('tenants', function (Builder $builder) use ($tenant) { |
||
96 | $key = $tenant instanceof Model ? $tenant->getKeyName() : (is_int($tenant) ? 'id' : 'slug'); |
||
97 | $value = $tenant instanceof Model ? $tenant->{$key} : $tenant; |
||
98 | $builder->where($key, $value); |
||
99 | }); |
||
100 | }); |
||
101 | |||
102 | static::saved(function (self $model) use ($tenant) { |
||
103 | $model->attachTenants($tenant); |
||
104 | }); |
||
105 | } |
||
106 | |||
107 | static::deleted(function (self $model) { |
||
108 | $model->tenants()->detach(); |
||
109 | }); |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * Returns a new query builder without any of the tenant scopes applied. |
||
114 | * |
||
115 | * @return \Illuminate\Database\Eloquent\Builder |
||
116 | */ |
||
117 | public static function forAllTenants() |
||
118 | { |
||
119 | return (new static())->newQuery()->withoutGlobalScopes(['tenantable']); |
||
0 ignored issues
–
show
It seems like
newQuery() must be provided by classes using this trait. How about adding it as abstract method to this trait?
This check looks for methods that are used by a trait but not required by it. To illustrate, let’s look at the following code example trait Idable {
public function equalIds(Idable $other) {
return $this->getId() === $other->getId();
}
}
The trait Adding the ![]() |
|||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Override the default findOrFail method so that we can re-throw |
||
124 | * a more useful exception. Otherwise it can be very confusing |
||
125 | * why queries don't work because of tenant scoping issues. |
||
126 | * |
||
127 | * @param mixed $id |
||
128 | * @param array $columns |
||
129 | * |
||
130 | * @throws \Rinvex\Tenants\Exceptions\ModelNotFoundForTenantException |
||
131 | * |
||
132 | * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection |
||
133 | */ |
||
134 | public static function findOrFail($id, $columns = ['*']) |
||
135 | { |
||
136 | try { |
||
137 | return static::query()->findOrFail($id, $columns); |
||
138 | } catch (ModelNotFoundException $exception) { |
||
139 | // If it DOES exist, just not for this tenant, throw a nicer exception |
||
140 | if (! is_null(static::forAllTenants()->find($id, $columns))) { |
||
141 | throw (new ModelNotFoundForTenantException())->setModel(static::class, [$id]); |
||
142 | } |
||
143 | |||
144 | throw $exception; |
||
145 | } |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Scope query with all the given tenants. |
||
150 | * |
||
151 | * @param \Illuminate\Database\Eloquent\Builder $builder |
||
152 | * @param mixed $tenants |
||
153 | * |
||
154 | * @return \Illuminate\Database\Eloquent\Builder |
||
155 | */ |
||
156 | public function scopeWithAllTenants(Builder $builder, $tenants): Builder |
||
157 | { |
||
158 | $tenants = $this->prepareTenantIds($tenants); |
||
159 | |||
160 | collect($tenants)->each(function ($tenant) use ($builder) { |
||
161 | $builder->whereHas('tenants', function (Builder $builder) use ($tenant) { |
||
162 | return $builder->where('id', $tenant); |
||
163 | }); |
||
164 | }); |
||
165 | |||
166 | return $builder; |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * Scope query with any of the given tenants. |
||
171 | * |
||
172 | * @param \Illuminate\Database\Eloquent\Builder $builder |
||
173 | * @param mixed $tenants |
||
174 | * |
||
175 | * @return \Illuminate\Database\Eloquent\Builder |
||
176 | */ |
||
177 | public function scopeWithAnyTenants(Builder $builder, $tenants): Builder |
||
178 | { |
||
179 | $tenants = $this->prepareTenantIds($tenants); |
||
180 | |||
181 | return $builder->whereHas('tenants', function (Builder $builder) use ($tenants) { |
||
182 | $builder->whereIn('id', $tenants); |
||
183 | }); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Scope query with any of the given tenants. |
||
188 | * |
||
189 | * @param \Illuminate\Database\Eloquent\Builder $builder |
||
190 | * @param mixed $tenants |
||
191 | * |
||
192 | * @return \Illuminate\Database\Eloquent\Builder |
||
193 | */ |
||
194 | public function scopeWithTenants(Builder $builder, $tenants): Builder |
||
195 | { |
||
196 | return static::scopeWithAnyTenants($builder, $tenants); |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * Scope query without any of the given tenants. |
||
201 | * |
||
202 | * @param \Illuminate\Database\Eloquent\Builder $builder |
||
203 | * @param mixed $tenants |
||
204 | * |
||
205 | * @return \Illuminate\Database\Eloquent\Builder |
||
206 | */ |
||
207 | public function scopeWithoutTenants(Builder $builder, $tenants): Builder |
||
208 | { |
||
209 | $tenants = $this->prepareTenantIds($tenants); |
||
210 | |||
211 | return $builder->whereDoesntHave('tenants', function (Builder $builder) use ($tenants) { |
||
212 | $builder->whereIn('id', $tenants); |
||
213 | }); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Scope query without any tenants. |
||
218 | * |
||
219 | * @param \Illuminate\Database\Eloquent\Builder $builder |
||
220 | * |
||
221 | * @return \Illuminate\Database\Eloquent\Builder |
||
222 | */ |
||
223 | public function scopeWithoutAnyTenants(Builder $builder): Builder |
||
224 | { |
||
225 | return $builder->doesntHave('tenants'); |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Determine if the model has any of the given tenants. |
||
230 | * |
||
231 | * @param mixed $tenants |
||
232 | * |
||
233 | * @return bool |
||
234 | */ |
||
235 | public function hasTenants($tenants): bool |
||
236 | { |
||
237 | $tenants = $this->prepareTenantIds($tenants); |
||
238 | |||
239 | return ! $this->tenants->pluck('id')->intersect($tenants)->isEmpty(); |
||
0 ignored issues
–
show
The property
tenants does not exist. Did you maybe forget to declare it?
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code: class MyClass { }
$x = new MyClass();
$x->foo = true;
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: class MyClass {
public $foo;
}
$x = new MyClass();
$x->foo = true;
![]() |
|||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Determine if the model has any the given tenants. |
||
244 | * |
||
245 | * @param mixed $tenants |
||
246 | * |
||
247 | * @return bool |
||
248 | */ |
||
249 | public function hasAnyTenants($tenants): bool |
||
250 | { |
||
251 | return static::hasTenants($tenants); |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Determine if the model has all of the given tenants. |
||
256 | * |
||
257 | * @param mixed $tenants |
||
258 | * |
||
259 | * @return bool |
||
260 | */ |
||
261 | public function hasAllTenants($tenants): bool |
||
262 | { |
||
263 | $tenants = $this->prepareTenantIds($tenants); |
||
264 | |||
265 | return collect($tenants)->diff($this->tenants->pluck('id'))->isEmpty(); |
||
266 | } |
||
267 | |||
268 | /** |
||
269 | * Sync model tenants. |
||
270 | * |
||
271 | * @param mixed $tenants |
||
272 | * @param bool $detaching |
||
273 | * |
||
274 | * @return $this |
||
275 | */ |
||
276 | public function syncTenants($tenants, bool $detaching = true) |
||
277 | { |
||
278 | // Find tenants |
||
279 | $tenants = $this->prepareTenantIds($tenants); |
||
280 | |||
281 | // Sync model tenants |
||
282 | $this->tenants()->sync($tenants, $detaching); |
||
283 | |||
284 | return $this; |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * Attach model tenants. |
||
289 | * |
||
290 | * @param mixed $tenants |
||
291 | * |
||
292 | * @return $this |
||
293 | */ |
||
294 | public function attachTenants($tenants) |
||
295 | { |
||
296 | return $this->syncTenants($tenants, false); |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * Detach model tenants. |
||
301 | * |
||
302 | * @param mixed $tenants |
||
303 | * |
||
304 | * @return $this |
||
305 | */ |
||
306 | public function detachTenants($tenants = null) |
||
307 | { |
||
308 | $tenants = ! is_null($tenants) ? $this->prepareTenantIds($tenants) : null; |
||
309 | |||
310 | // Sync model tenants |
||
311 | $this->tenants()->detach($tenants); |
||
312 | |||
313 | return $this; |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * Prepare tenant IDs. |
||
318 | * |
||
319 | * @param mixed $tenants |
||
320 | * |
||
321 | * @return array |
||
322 | */ |
||
323 | protected function prepareTenantIds($tenants): array |
||
324 | { |
||
325 | // Convert collection to plain array |
||
326 | if ($tenants instanceof BaseCollection && is_string($tenants->first())) { |
||
327 | $tenants = $tenants->toArray(); |
||
328 | } |
||
329 | |||
330 | // Find tenants by their ids |
||
331 | if (is_numeric($tenants) || (is_array($tenants) && is_numeric(Arr::first($tenants)))) { |
||
0 ignored issues
–
show
$tenants is of type array , but the function expects a object<Illuminate\Support\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
332 | return array_map('intval', (array) $tenants); |
||
333 | } |
||
334 | |||
335 | // Find tenants by their slugs |
||
336 | if (is_string($tenants) || (is_array($tenants) && is_string(Arr::first($tenants)))) { |
||
0 ignored issues
–
show
$tenants is of type array , but the function expects a object<Illuminate\Support\iterable> .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
337 | $tenants = app('rinvex.tenants.tenant')->whereIn('slug', $tenants)->get()->pluck('id'); |
||
338 | } |
||
339 | |||
340 | if ($tenants instanceof Model) { |
||
341 | return [$tenants->getKey()]; |
||
342 | } |
||
343 | |||
344 | if ($tenants instanceof Collection) { |
||
345 | return $tenants->modelKeys(); |
||
346 | } |
||
347 | |||
348 | if ($tenants instanceof BaseCollection) { |
||
349 | return $tenants->toArray(); |
||
350 | } |
||
351 | |||
352 | return (array) $tenants; |
||
353 | } |
||
354 | } |
||
355 |
This check looks for
@param
annotations where the type inferred by our type inference engine differs from the declared type.It makes a suggestion as to what type it considers more descriptive.
Most often this is a case of a parameter that can be null in addition to its declared types.