راه‌حل ذخیره وابستگی ها مدل‌های جدید - لاراول

معمولا بیشتر اوقات به سورس کدهای نوشته شده لاراول که نگاه میکنیم میبینیم که معمولا وابستگی های یک مدل بعد از این که مدل ذخیره میشه به صورت جدا درون دیتابیس ذخیره میشه مثل زیر:

public function store(Request $request)
{
    /** @var User $user*/
    $user  = User::query()->create($request->except('phones'));
    $user->Phones()->createMany($request->get('phones'));
    
    //
    // Other Model's relations store operation will come here
    //
    
    return redirect()->route('user.edit', $user->getKey() );
}

توی مثال بالا ما در هر مرحله یک کار رو انجام دادیم. ابتدا کاربر رو ساختیم و بعدش در هر مرحله وایستگی ها رو ایجاد کردیم.

اما یک مسله‌ای که وجود داره اینه که اگر ما بخوایم مقادیر رو قبل از ذخیره نرمال سازی کنیم و تمام مقادیر رو به یک فرمت خاص درون دیتابیس ذخیره کنیم کارهایی که باید توی کنترلر انجام بدیم زیاد میشه و تمیزی کد رو مقداری کم میکنه. برای حل این مشکل ما معمولا از ویژگی توی لاراول استفاده میکنیم که به نام Mutator شناخته میشن. که با استفاده از mutatorها کد ما به صورت زیر درمیاد.

class User extends Authenticatable
{
    ...

    public function Phones()
    {
        return $this->hasMany(Phone::class);
    }


    /**
     * mutator for user phones
     *
     * @param array|mixed $value
     * @return void
     */
    public function setPhonesAttribute($value)
    {
        $normalized_phones = $this->normalizePhones($value);
        $this->Phones()->delete();
        $this->Phones()->createMany($normalized_phones);
    }
   ....
} 

خوب توی کد بالا ما یک mutator برای شماره تلفن های کاربر ایجاد کردیم که مسولیت ذخیره سازی شماره تلفن هایی که برای کاربر ارسال میشن رو به عهده میگیره. خوب ما اینجا اول شماره تلفن ها رو نرمال سازی کردیم، بعدش شماره های قبلی کاربر رو پاک کردیم که شماره های اضافی قبلی که کاربر داشته پاک بشن و شماره های جدید رو بعدش ذخیره میکنیم.

اما توی این کد یک مشکل وجود داره. بیاین مثال زیر رو در نظر بگیریم:

داده های که ما توی درخواست داریم به صورت زیره:

[
    'name'=>'...',
    'email'=>'...',
    'password'=>'...',
    '...',
    'phones'=>[
        '...',
        '...',
        '...'
    ],
    '...'
];

و متد ذخیره کردن مدل کنترلر ما هم به صورت زیره:

/**
 * create new user
 *
 * @param Request $request
 * @return string
 */
public function store(Request $request)
{
    /** @var User $user*/
    $user  = User::query()->create($request->all());
    return redirect()->route('user.edit',$user->getKey());
}

اما اینجا ما به یک مشکل بر میخوریم و اونم اینه که زمانی که ما اقدام به پر کردن مقادیر phones برای کاربر میکنیم، mutator ما فراخوانی میشه و باعث میشه که شماره تلفن های کاربر ما قبل از ذخیره شدن خود کار ذخیره بشن و باعث ایجاد خطای کلید خارجی در پایگاه داده میشه. برای حل این مشکل ما میتونیم از observerها استفاده کنیم و به صورت زیر mutator خودمون رو تغییر میدیم.

/**
 * accessor for user phones
 *
 * @param array|mixed $value
 * @return void
 */
public function setPhonesAttribute($value)
{
    $normalized_phones = $this->normalizePhones($value);
    if ($this->exists){
        $this->Phones()->delete();
        $this->Phones()->createMany($normalized_phones);
    } else {
        self::created(function (self $self) use ($normalized_phones){
            $slef->Phones()->createMany($normalized_phones);
        });
    }    
}

خوب ما توی mutator خودمون از متغیر exists مدل استفاده کردیم. این متغیر زمانی مقدار true داره که مدل ما توی دیتابیس وجود داشته باشه ( رکورد معادل مدل درون جدول مدل وجود داشته باشه).

خوب اگر که مدل ما توی دیتابیس وجود داشت که مثل شماره تلفن های کاربر رو ذخیره می‌کنیم و اگر وجود نداشته باشه یک observer برای مدل تنظیم می‌کنیم که زمانی که مدل ذخیره شد، observer ما فراخونی میشه و شماره تلفن های ما ذخیره می‌شه.

اینجوری دیگه مشکل ذخیره کردن شماره تلفن‌های کاربر قبل از ذخیره سازی خود کاربر حل میشه. از طرف دیگه از شلوغی کدهای توی کنترلر کم می‌شه و خوانایی کد بالا میره. از طرفی هم اگر ما از چند جای مختلف کاربر جدید درست کنیم از کد زدن اضافی پرهیز کردیم.