Courses

Structuring Databases in Laravel 11

HasOne Relationship: 2 Examples

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...