I am developing a sort of multi-tenant application with Laravel 11 on PHP 8.3, where each "user" must belong to a "company".
For user management, I started from Laravel Breeze, and then modified the users
table to include a company_id
which references companies
:
Schema::create("users", function (Blueprint $table) {
...
$table->foreignUuid("company_id")->constrained();
...
});
Then I defined the relation in the model:
public function company(): BelongsTo {
return $this->belongsTo(Company::class);
}
The problem is, if in the controller I try to access $request->user()->company
it comes out as null
; the same happens if I try to access it with $request->user()->company()->first()
.
However, if I try with artisan tinker
, I see it works properly:
$ php artisan tinker
Psy Shell v0.12.4 (PHP 8.3.8 — cli) by Justin Hileman
> $user = User::where('email', 'test@example.com')->firstOrFail();
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
= App\Models\User {#6215
id: "9c549870-805a-4555-bd72-86ba982a3c04",
company_id: "9c54986f-8284-4da9-b826-c7a723de279b",
name: "TEST test",
email: "test@example.com",
email_verified_at: "2024-06-20 08:18:06",
#password: "$2y$12$wboWRmK/9B5uOT28.u/BO..gIlY0Sz75l7kQL8eIGBdRcxB5dGSn2",
#remember_token: null,
created_at: "2024-06-20 08:18:06",
updated_at: "2024-06-20 08:18:06",
deleted_at: null,
}
> $user->company;
= App\Models\Company {#6248
id: "9c54986f-8284-4da9-b826-c7a723de279b",
name: "TEST Administration",
is_master: 1,
fiscal_id: null,
email: null,
phone: null,
mobile: null,
address_line_1: null,
address_line_2: null,
address_post_code: null,
address_city: null,
address_province: null,
created_at: "2024-06-20 08:18:06",
updated_at: "2024-06-20 08:18:06",
deleted_at: null,
}
I have found this previous question, where a comment suggests to preload the relationship by modifying the user model by adding:
protected $with = ['company'];
I tried it, and it seems to work, however it does not seem right to have to eager load it each time, even when it is not needed (the majority of cases).
How can I have the relationships work when accessing the user with $request->user()
without preloading them?
I have seen that it does not work even if I try to refresh()
the entity, or to manually load()
the relation; however, $user->company_id
is correctly set and $company = Company::find($user->company_id)
works, but I really do not see why I cannot use the declared relationship.
In the end, I found out that the problem was caused by a global scope applied to the "company" model.
In fact, I have created a global scope that, if there is a logged user and that user does not have a specific authorization, applies a condition on the company_id
of the entities which the application is querying.
However, it was applied to the Company
model too, and in that case, the implementation was wrong, as the table does not contain a company_id
field (it has an id
), and it generated the wrong query:
select * from "companies" where "companies"."id" = '9c54986f-8284-4da9-b826-c7a723de279b' and "companies"."deleted_at" is null and "company_id" = '9c54986f-8284-4da9-b826-c7a723de279b'
I fixed the global scope implementation to use the correct field in this case, and it now works.
The problem is that the query as generated fails (as in, it does not found any record) but does not seem to generate an error when using SQLite.