Everyone talks about hasMany
and belongsTo
relationships, but there is also hasOne
. It's rarely used, but I want to explain the situation where it should be used.
Example 1: Split Large Table in Two Tables
Imagine you have a DB table with a large number of columns.
A typical example is the users
table. In some projects, you may need to store a lot of information about users, such as their phone numbers, avatars, and more.
If it's an e-shop, you want to store the customer address with all the different details, then separately billing address, and there may be more fields in the future.
Schema::table('users', function (Blueprint $table) { $table->string('phone_number')->nullable(); $table->string('avatar_filename')->nullable(); $table->string('address_line_1')->nullable(); $table->string('address_line_2')->nullable(); $table->string('address_city')->nullable(); $table->string('address_country')->nullable(); $table->string('address_postcode')->nullable(); $table->string('billing_address_line_1')->nullable(); $table->string('billing_address_line_2')->nullable(); $table->string('billing_address_city')->nullable(); $table->string('billing_address_country')->nullable(); $table->string('billing_address_postcode')->nullable();});
What happens if you want to get all the users? For example, you can get all the users in the UserController and show them in the view.
use App\Models\User; class UserController extends Controller{ public function index() { $users = User::all(); return view('users.index', compact('users')); }}
You don't need all those 20-30 fields, but you still get them all, which expands to too much memory and causes performance issues.
Of course, one way to deal with that is to provide a selection of what fields you need.
use App\Models\User; class UserController extends Controller{ public function index() { $users = User::select(['id', 'name', 'email'])->get(); return view('users.index', compact('users')); }}
But it's not convenient to manually specify 5-10 fields every time.
So, you may want to separate the "main" fields used often (like profile fields) from the "secondary" fields used only sometimes in invoicing or reports.
In this case, you may want to remove those fields from the primary users
table and create a separate table, user_profiles,
with a foreign key to the users
table.
Schema::create('user_profiles', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); $table->string('address_line_1')->nullable(); $table->string('address_line_2')->nullable(); $table->string('address_city')->nullable(); $table->string('address_country')->nullable(); $table->string('address_postcode')->nullable(); $table->string('billing_address_line_1')->nullable(); $table->string('billing_address_line_2')->nullable(); $table->string('billing_address_city')->nullable(); $table->string('billing_address_country')->nullable(); $table->string('billing_address_postcode')->nullable(); $table->timestamps();});
So, in the UserProfile
Model, you will have a belongsTo
relationship:
app/Models/UserProfile.php:
use Illuminate\Database\Eloquent\Relations\BelongsTo; class UserProfile extends Model{ public function user(): BelongsTo { return $this->belongsTo(User::class); }}
But from the User
Model perspective, it will not be a hasMany
relationship. Users won't have many profiles; they have only one profile each. So, you will have a hasOne
relationship.
app/Models/User.php:
use Illuminate\Database\Eloquent\Relations\HasOne; class User extends Authenticatable{ // ... public function profile(): HasOne { return $this->hasOne(UserProfile::class); }}
Then, everywhere you need in the Controller, getting all the users will take only the main fields you actually use most often. But if you need those profile fields, you may take them specifically by eagerly loading the profile relationship.
use App\Models\User; class UserController extends Controller{ public function index() { $users = User::with('profile')->get(); return view('users.index', compact('users')); }}
In other words, the hasOne
relationship is used to separate or split a big table of fields into main fields and, separately, more rarely used fields.
Example 2: Only One Team Owner
For the second example, imagine you have an application with Teams. Of course, some users should be team owners.
So, in the teams
table, we can have an owner_id
field, which is constrained to the users
table.
database/migrations/xxx_create_teams_table.php:
Schema::create('teams', function (Blueprint $table) { $table->id(); $table->string('name'); $table->foreignId('owner_id') ->nullable() ->constrained('users'); $table->timestamps();});
And the User belongs to a Team.
database/migrations/xxx_add_team_id_to_users_table.php:
Schema::table('users', function (Blueprint $table) { $table->foreignId('team_id') ->nullable() ->after('password') ->constrained();});
app/Models/User.php:
use Illuminate\Database\Eloquent\Relations\BelongsTo; class User extends Authenticatable{ protected $fillable = [ 'name', 'email', 'password', 'team_id', ]; // ... public function team(): BelongsTo { return $this->belongsTo(Team::class); }}
Now, how do you get the team owned by a user? We can add a hasOne
relationship and set the foreign key to the owner_id
.
app/Models/User.php:
use Illuminate\Database\Eloquent\Relations\HasOne; class User extends Authenticatable{ protected $fillable = [ 'name', 'email', 'password', 'team_id', ]; // ... public function team(): BelongsTo { return $this->belongsTo(Team::class); } public function ownedTeam(): HasOne { return $this->hasOne(Team::class, 'owner_id'); }}
When this relationship is called, it will return the Team Model or null.
So, in this case, we're using a hasOne
relationship if there's a limitation of strictly only one related record.
No comments or questions yet...