در پروژههای واقعی Laravel، اطلاعات معمولاً در چندین جدول مختلف ذخیره میشوند و این جدولها با یکدیگر ارتباط دارند.
برای مثال، فرض کنید در یک سیستم کاربران را در جدول users و اطلاعات پروفایل آنها را در جدول profiles ذخیره کردهایم.
ممکن است ساختار دیتابیس به این شکل باشد:
users
↓
profiles
هر کاربر یک پروفایل دارد و هر پروفایل نیز متعلق به یک کاربر است.
این نوع ارتباط را One to One یا رابطه یک به یک مینامیم.
Laravel و Eloquent امکان تعریف چنین رابطهای را بسیار ساده فراهم میکنند.
در این مقاله یاد میگیریم:
- One to One چیست؟
- چه زمانی باید از رابطه یک به یک استفاده کنیم؟
hasOne()چیست؟belongsTo()چیست؟- Foreign Key کجا قرار میگیرد؟
- چگونه Migration رابطه را ایجاد کنیم؟
- چگونه داده مرتبط را دریافت کنیم؟
- چگونه داده مرتبط را ایجاد و ویرایش کنیم؟
- چگونه Relationship را با
with()بارگذاری کنیم؟ - چگونه بر اساس Relationship جستجو کنیم؟
- اشتباهات رایج در One to One چیست؟
# One to One در Laravel چیست؟
One to One نوعی Relationship در Eloquent است که در آن هر رکورد از یک Model با حداکثر یک رکورد از Model دیگر ارتباط دارد.
یک مثال ساده:
User
↓
Profile
یک User دارای یک Profile است.
و:
Profile
↓
User
هر Profile متعلق به یک User است.
بنابراین رابطه دوطرفه است:
User 1 ───── 1 Profile
در Laravel برای تعریف این رابطه معمولاً از دو متد استفاده میکنیم:
hasOne()
و:
belongsTo()
# یک مثال واقعی برای One to One
فرض کنید یک سیستم مدیریت کاربران داریم.
جدول users:
users
----------------
id
name
email
password
و جدول profiles:
profiles
----------------
id
user_id
phone
address
avatar
bio
در این ساختار:
users.id
↓
profiles.user_id
هر User یک Profile دارد.
بنابراین:
User
hasOne
↓
Profile
و از سمت Profile:
Profile
belongsTo
↓
User
# چه زمانی از One to One استفاده کنیم؟
رابطه One to One معمولاً زمانی مناسب است که اطلاعات یک Entity را به دلایل ساختاری، امنیتی، منطقی یا معماری در جدول جداگانه نگه میداریم.
مثلاً:
User و Profile
users
profiles
User و User Setting
users
user_settings
Employee و Employee Detail
employees
employee_details
Company و Company Setting
companies
company_settings
البته اینکه دو مجموعه اطلاعات حتماً باید در دو جدول باشند، به طراحی دیتابیس و نیاز پروژه بستگی دارد.
One to One صرفاً یک ابزار برای تعریف ارتباط است.
# ساختار دیتابیس برای One to One
یکی از مهمترین نکات در One to One این است که بدانیم Foreign Key کجا قرار میگیرد.
در مثال User و Profile، میتوانیم Foreign Key را در جدول profiles قرار دهیم:
profiles
----------------
id
user_id
phone
address
avatar
اینجا:
user_id
به User مربوط است.
بهصورت مفهومی:
users.id
│
│
▼
profiles.user_id
# ایجاد Migration برای One to One
فرض کنید جدول users از قبل وجود دارد.
میتوانیم Migration مربوط به Profile را ایجاد کنیم:
php artisan make:model Profile -m
در Migration:
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->string('phone')->nullable();
$table->string('address')->nullable();
$table->string('avatar')->nullable();
$table->text('bio')->nullable();
$table->timestamps();
});
سپس:
php artisan migrate
را اجرا میکنیم.
# چرا cascadeOnDelete استفاده میکنیم؟
در مثال بالا:
->cascadeOnDelete()
مشخص میکند اگر User مربوطه حذف شود، رکورد Profile مرتبط نیز توسط دیتابیس حذف شود.
این رفتار برای برخی سناریوهای One to One منطقی است؛ اما استفاده از آن باید بر اساس نیاز واقعی سیستم انجام شود.
برای دادههایی که نباید با حذف User از بین بروند، نباید بدون بررسی از Cascade Delete استفاده کرد.
# تعریف hasOne در Laravel
حالا باید Relationship را در Model User تعریف کنیم.
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
}
}
این کد به Eloquent میگوید:
> هر User یک Profile دارد.
حالا میتوانیم از Relationship استفاده کنیم.
# دریافت Profile از User
ابتدا User را دریافت میکنیم:
$user = User::findOrFail(1);
سپس:
$profile = $user->profile;
Profile مربوط به User در اختیار ما قرار میگیرد.
مثلاً:
echo $user->profile->phone;
یا:
echo $user->profile->address;
# تعریف belongsTo در Model مربوط به Profile
Relationship از سمت Profile نیز باید تعریف شود.
در Profile.php:
class Profile extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
این رابطه میگوید:
> این Profile متعلق به یک User است.
# دریافت User از Profile
حالا میتوانیم:
$profile = Profile::findOrFail(1);
و:
$user = $profile->user;
را استفاده کنیم.
مثلاً:
echo $profile->user->name;
در اینجا Eloquent کاربر مرتبط با Profile را پیدا میکند.
# تفاوت hasOne و belongsTo چیست؟
این یکی از مهمترین نکات One to One است.
فرض کنید:
users
profiles
داریم و profiles.user_id کلید خارجی است.
در Model User:
return $this->hasOne(Profile::class);
چون User دارای یک Profile است.
اما در Model Profile:
return $this->belongsTo(User::class);
چون Profile متعلق به یک User است.
بهصورت خلاصه:
User
└── hasOne → Profile
Profile
└── belongsTo → User
# Foreign Key چگونه توسط Eloquent پیدا میشود؟
وقتی مینویسیم:
return $this->hasOne(Profile::class);
Laravel بر اساس قراردادهای نامگذاری به دنبال Foreign Key مناسب میگردد.
در این مثال:
user_id
نام طبیعی Foreign Key برای User است.
همچنین اگر Model مربوطه نام متفاوتی داشته باشد، میتوانیم Foreign Key را بهصورت صریح مشخص کنیم.
مثلاً:
return $this->hasOne(Profile::class, 'user_id');
# تغییر نام Foreign Key
فرض کنید جدول Profile بهجای:
user_id
دارای:
owner_id
باشد.
در این شرایط میتوانیم Foreign Key را مشخص کنیم:
public function profile()
{
return $this->hasOne(Profile::class, 'owner_id');
}
در رابطه معکوس نیز میتوان Foreign Key مناسب را مشخص کرد:
public function user()
{
return $this->belongsTo(User::class, 'owner_id');
}
بنابراین اگر ساختار دیتابیس شما از قراردادهای پیشفرض Laravel پیروی نمیکند، بهتر است کلیدهای رابطه را صریحاً مشخص کنید.
# One to One با Primary Key متفاوت
در حالت معمول، Eloquent از Primary Key و Foreign Key استاندارد استفاده میکند.
اما اگر ساختار دیتابیس متفاوت باشد، میتوان کلیدهای مربوط به Relationship را بهصورت صریح مشخص کرد.
برای مثال:
return $this->hasOne(
Profile::class,
'user_id',
'id'
);
پارامترها بهترتیب مفهومهایی مانند Foreign Key و Local Key را مشخص میکنند.
این قابلیت زمانی مفید است که دیتابیس شما از ساختار استاندارد Laravel استفاده نمیکند.
# ایجاد Profile برای User
فرض کنید User را داریم:
$user = User::findOrFail(1);
میتوانیم Profile را ایجاد کنیم.
یکی از روشهای ساده:
$profile = new Profile();
$profile->phone = '09120000000';
$profile->address = 'Tehran';
$user->profile()->save($profile);
در این حالت Eloquent ارتباط لازم را برای ذخیره Profile مدیریت میکند.
# استفاده از create برای Relationship
اگر Mass Assignment را در Model بهدرستی تنظیم کرده باشیم، میتوانیم از create() نیز استفاده کنیم.
در Profile:
protected $fillable = [
'phone',
'address',
'avatar',
'bio',
];
سپس:
$user->profile()->create([
'phone' => '09120000000',
'address' => 'Tehran',
'bio' => 'Web Developer',
]);
Eloquent Profile را برای User مربوطه ایجاد میکند.
# ویرایش Profile
اگر Profile قبلاً وجود داشته باشد:
$user = User::findOrFail(1);
$user->profile->update([
'phone' => '09121111111',
]);
یا:
$profile = $user->profile;
$profile->phone = '09121111111';
$profile->save();
هر دو روش بسته به سناریوی شما قابل استفاده هستند.
# بررسی وجود Profile
ممکن است همه Userها Profile نداشته باشند.
بنابراین بهتر است قبل از دسترسی مستقیم به ویژگیهای Profile، وضعیت آن را در نظر بگیریم.
مثلاً:
if ($user->profile) {
echo $user->profile->phone;
}
اگر Profile وجود نداشته باشد، Relationship مقدار null خواهد داشت.
# ساخت User و Profile بهصورت همزمان
در برخی پروژهها هنگام ثبتنام کاربر میخواهیم Profile را نیز ایجاد کنیم.
مثلاً:
$user = User::create([
'name' => 'Ali',
'email' => 'ali@example.com',
'password' => bcrypt('password'),
]);
$user->profile()->create([
'phone' => '09120000000',
]);
در این حالت ابتدا User ساخته میشود و سپس Profile مرتبط ایجاد میشود.
در پروژههای واقعی، اطلاعات ورودی باید قبل از ذخیره Validation شوند.
# استفاده از updateOrCreate
گاهی نمیدانیم Profile قبلاً ایجاد شده یا خیر.
در چنین شرایطی میتوان از روشهایی مانند updateOrCreate() استفاده کرد.
مثلاً:
Profile::updateOrCreate(
['user_id' => $user->id],
[
'phone' => '09120000000',
'address' => 'Tehran',
]
);
این روش میتواند در سناریوهایی که میخواهیم رکورد موجود را بهروزرسانی کنیم یا در صورت نبودن ایجاد کنیم، مفید باشد.
# Eager Loading در One to One
فرض کنید میخواهیم چند User را همراه با Profile آنها دریافت کنیم.
بهجای اینکه برای هر User جداگانه Profile را بارگذاری کنیم، میتوانیم از Eager Loading استفاده کنیم:
$users = User::with('profile')->get();
سپس:
foreach ($users as $user) {
echo $user->name;
if ($user->profile) {
echo $user->profile->phone;
}
}
این روش در صفحات لیستی و گزارشها اهمیت زیادی دارد.
# Lazy Loading در One to One
در حالت Lazy Loading:
$user = User::find(1);
$profile = $user->profile;
Relationship زمانی که به آن دسترسی پیدا میکنیم بارگذاری میشود.
این روش ساده است، اما در زمانی که تعداد زیادی User را پردازش میکنیم، باید مراقب تعداد Queryها باشیم.
در چنین شرایطی:
User::with('profile')->get();
میتواند انتخاب مناسبتری باشد.
# with برای دریافت Profile
برای دریافت User همراه با Profile:
$user = User::with('profile')->findOrFail(1);
حالا:
$user->profile;
از Relationship بارگذاریشده استفاده میکند.
# withDefault در One to One
گاهی ممکن است User دارای Profile نباشد، اما بخواهیم بهجای null یک Model پیشفرض داشته باشیم.
در برخی سناریوها میتوان از withDefault() استفاده کرد.
برای مثال در یک رابطه belongsTo:
public function profile()
{
return $this->hasOne(Profile::class);
}
در سناریوهای مناسب میتوان از تنظیمات Default Model استفاده کرد تا نبودن رکورد مرتبط باعث خطای دسترسی به Property نشود.
البته بهتر است قبل از استفاده، مشخص باشد که Profile واقعاً اختیاری است و مقدار پیشفرض با منطق کسبوکار سیستم سازگار است.
# جستجوی User بر اساس Profile
یکی از کاربردهای Relationship این است که بر اساس دادههای Model مرتبط جستجو کنیم.
فرض کنید میخواهیم Userهایی را پیدا کنیم که Profile آنها دارای شماره تلفن مشخصی است.
میتوانیم از whereHas() استفاده کنیم:
$users = User::whereHas('profile', function ($query) {
$query->where('phone', 'like', '0912%');
})->get();
در این حالت Userهایی دریافت میشوند که Profile آنها با شرط مشخصشده مطابقت دارد.
# with و whereHas چه تفاوتی دارند؟
این دو متد کاربرد یکسانی ندارند.
with() برای بارگذاری Relationship استفاده میشود:
User::with('profile')->get();
اما whereHas() برای فیلتر کردن Modelها بر اساس Relationship کاربرد دارد:
User::whereHas('profile', function ($query) {
$query->where('phone', 'like', '0912%');
})->get();
اگر هم فیلتر و هم بارگذاری Relationship را نیاز داشته باشیم، میتوان از ابزارهای مناسب Eloquent برای ترکیب این دو نیاز استفاده کرد.
# حذف User و Profile
اگر رابطه دیتابیس با:
->cascadeOnDelete()
تعریف شده باشد، حذف User میتواند باعث حذف Profile مرتبط در سطح دیتابیس شود.
اما اگر چنین محدودیتی تعریف نشده باشد، باید رفتار حذف را خودمان مدیریت کنیم.
برای مثال:
$user->profile()->delete();
$user->delete();
یا از مکانیزمهای مناسب Model Event و ساختار دیتابیس استفاده کنیم.
نکته مهم این است که رفتار Delete باید آگاهانه طراحی شود، مخصوصاً زمانی که Profile شامل اطلاعات مهم یا قابل بازیابی باشد.
# آیا One to One همیشه بهترین انتخاب است؟
خیر.
One to One یک ابزار طراحی دیتابیس است و نباید صرفاً چون Laravel از آن پشتیبانی میکند، هر دو مجموعه اطلاعات را در دو جدول قرار دهیم.
مثلاً اگر Profile فقط شامل دو ستون ساده باشد:
phone
bio
ممکن است در برخی پروژهها قرار دادن این اطلاعات در جدول users منطقیتر باشد.
اما اگر Profile اطلاعات زیادی داشته باشد یا بخواهیم ساختار User را سبکتر نگه داریم، جدول جداگانه میتواند انتخاب مناسبی باشد.
بنابراین تصمیم نهایی به طراحی دیتابیس و نیاز پروژه بستگی دارد.
# تفاوت One to One با One to Many
در One to One:
User
↓
Profile
یک User حداکثر یک Profile دارد.
اما در One to Many:
User
├── Order
├── Order
└── Order
یک User میتواند چند Order داشته باشد.
در Laravel:
One to One
hasOne()
One to Many
hasMany()
در مقاله بعدی کلاستر، رابطه One to Many را بهصورت کامل بررسی خواهیم کرد.
# مثال کامل User و Profile
فرض کنید میخواهیم یک سیستم پروفایل کاربر بسازیم.
Migration
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->unique()
->constrained()
->cascadeOnDelete();
$table->string('phone')->nullable();
$table->string('avatar')->nullable();
$table->text('bio')->nullable();
$table->timestamps();
});
نکته مهم در این مثال استفاده از:
->unique()
روی user_id است.
چرا؟
چون اگر واقعاً میخواهیم رابطه One to One باشد، نباید اجازه دهیم چند Profile برای یک User ایجاد شود.
بدون Unique Constraint، از نظر دیتابیس ممکن است چند رکورد با:
user_id = 1
وجود داشته باشد و در نتیجه ساختار واقعی دیتابیس دیگر One to One خالص نباشد.
Model User
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
}
}
Model Profile
class Profile extends Model
{
protected $fillable = [
'phone',
'avatar',
'bio',
];
public function user()
{
return $this->belongsTo(User::class);
}
}
ایجاد Profile
$user = User::findOrFail(1);
$user->profile()->create([
'phone' => '09120000000',
'bio' => 'Laravel Developer',
]);
دریافت Profile
$user = User::with('profile')->findOrFail(1);
echo $user->profile?->phone;
استفاده از Null-safe Operator یعنی:
?->
باعث میشود اگر Profile وجود نداشت، دسترسی به Property باعث خطای معمول null نشود.
دریافت User از Profile
$profile = Profile::findOrFail(1);
echo $profile->user->name;
ویرایش Profile
$profile = $user->profile;
$profile->update([
'bio' => 'Senior Laravel Developer',
]);
حذف Profile
$user->profile()->delete();
و سپس در صورت نیاز User:
$user->delete();
البته اگر Cascade Delete در دیتابیس فعال باشد، حذف Profile ممکن است بهصورت خودکار همراه حذف User انجام شود.
# نکات مهم امنیتی
Relationship بهخودی خود مسئول امنیت دسترسی به دادهها نیست.
فرض کنید کاربر شماره ۱۰ را داریم و Profile کاربر ۲۰ را درخواست میکند.
اینکه Eloquent بتواند Profile را پیدا کند، به این معنی نیست که کاربر فعلی اجازه مشاهده آن را دارد.
در سیستمهای واقعی باید علاوه بر Relationship، Authorization نیز بررسی شود.
مثلاً باید مشخص کنیم:
> آیا کاربر فعلی اجازه مشاهده یا ویرایش این Profile را دارد؟
این موضوع در مقالات مربوط به Authentication و Authorization در ادامه کلاستر بهصورت تخصصی بررسی خواهد شد.
# اشتباهات رایج در One to One
۱. فراموش کردن Unique روی Foreign Key
اگر رابطه واقعاً One to One است، معمولاً باید در سطح دیتابیس نیز این موضوع تضمین شود.
مثلاً:
$table->foreignId('user_id')
->unique()
->constrained();
۲. اشتباه گرفتن hasOne و belongsTo
اگر Foreign Key در جدول Profile قرار دارد:
profiles.user_id
معمولاً:
User → hasOne → Profile
Profile → belongsTo → User
خواهیم داشت.
۳. فقط تعریف Relationship بدون ساختار دیتابیس مناسب
اینکه در Model بنویسیم:
hasOne(Profile::class)
بهتنهایی ساختار One to One را در دیتابیس ایجاد نمیکند.
Migration و Foreign Key نیز باید بهدرستی طراحی شوند.
۴. دریافت Relationship در یک حلقه بدون توجه به Queryها
اگر تعداد زیادی User داشته باشیم:
$users = User::all();
foreach ($users as $user) {
echo $user->profile?->phone;
}
باید تعداد Queryها را در نظر بگیریم.
در چنین شرایطی معمولاً بهتر است:
$users = User::with('profile')->get();
را بررسی کنیم.
۵. حذف بدون توجه به وابستگی دادهها
قبل از استفاده از:
cascadeOnDelete()
باید مشخص کنیم آیا حذف User واقعاً باید Profile را نیز حذف کند یا خیر.
# جمعبندی
One to One در Laravel یکی از سادهترین و در عین حال کاربردیترین Relationshipهای Eloquent است.
در این رابطه، هر رکورد از Model اول با حداکثر یک رکورد از Model دوم مرتبط است.
برای مثال:
User 1 ───── 1 Profile
در Laravel معمولاً از:
hasOne()
برای سمت اول و:
belongsTo()
برای رابطه معکوس استفاده میکنیم.
یک پیادهسازی معمول شامل موارد زیر است:
// User
public function profile()
{
return $this->hasOne(Profile::class);
}
و:
// Profile
public function user()
{
return $this->belongsTo(User::class);
}
همچنین اگر رابطه واقعاً یکبهیک باشد، بهتر است در دیتابیس نیز با Unique بودن Foreign Key این محدودیت را تضمین کنیم:
$table->foreignId('user_id')
->unique()
->constrained();
در پروژههای واقعی، علاوه بر تعریف Relationship باید به مواردی مانند Eager Loading، تعداد Queryها، Foreign Key، حذف دادهها، Validation و Authorization نیز توجه کنیم.
# سوالات متداول
One to One در Laravel چیست؟
One to One رابطهای است که در آن هر رکورد از یک Model با حداکثر یک رکورد از Model دیگر ارتباط دارد؛ مانند User و Profile.
hasOne در Laravel چیست؟
hasOne() مشخص میکند که یک Model دارای یک رکورد مرتبط از Model دیگر است.
مثلاً:
public function profile()
{
return $this->hasOne(Profile::class);
}
belongsTo در Laravel چیست؟
belongsTo() رابطه معکوس را مشخص میکند. مثلاً یک Profile متعلق به یک User است.
Foreign Key در One to One کجا قرار میگیرد؟
معمولاً Foreign Key در جدولی قرار میگیرد که به Model والد تعلق دارد. در مثال User و Profile، معمولاً profiles.user_id به users.id اشاره میکند.
آیا Foreign Key باید Unique باشد؟
اگر رابطه واقعاً One to One است، Unique بودن Foreign Key در دیتابیس کمک میکند تضمین شود که برای یک رکورد والد بیش از یک رکورد فرزند ایجاد نشود.
تفاوت One to One و One to Many چیست؟
در One to One هر رکورد حداکثر یک رکورد مرتبط دارد، اما در One to Many یک رکورد میتواند چند رکورد مرتبط داشته باشد.
آیا برای One to One باید از Eager Loading استفاده کنیم؟
همیشه الزامی نیست، اما زمانی که چندین Model را همراه با Relationship آنها دریافت میکنید، with() میتواند از Queryهای اضافی جلوگیری کند.
مثلاً:
User::with('profile')->get();
آیا Relationship خودش Foreign Key ایجاد میکند؟
خیر. Relationship در Model فقط نحوه ارتباط Eloquent را مشخص میکند. Foreign Key و محدودیتهای دیتابیس باید در Migration تعریف شوند.





