آنچه در این مقاله میخوانید [پنهانسازی]
اگر می خواهید منطق جانبی اعمال روی مدل ها را به شکلی تمیز و قابل نگهداری مدیریت کنید، بهترین گزینه کار با Observer Pattern در لاراول است. با کمک آن می توانید به رویدادهای چرخه حیات مدل واکنش نشان دهید، کدهای تکراری را حذف کنید و وابستگی ها را کاهش دهید. در این راهنما مراحل راه اندازی، نکات مهم، مثال های کاربردی و شیوه تست را به صورت عملی می بینید تا بدون پیچیدگی آن را در پروژه خود اجرا کنید.
چرا از الگوی ناظر در پروژه های لاراول استفاده کنیم
وقتی منطق مرتبط با ایجاد، ویرایش یا حذف یک مدل در جاهای مختلف پخش باشد، نگهداری سخت می شود. ناظرها کمک می کنند این منطق در یک نقطه متمرکز شود تا هم خوانایی بهتر شود و هم ریسک تکرار یا تناقض کاهش یابد. علاوه بر این، با جدا کردن عوارض جانبی از کنترلرها، تست پذیری افزایش پیدا می کند.
مزیت دیگر این رویکرد کاهش کوپلینگ است. به جای آنکه کنترلر بداند هنگام ساخت یک رکورد چه کارهایی باید انجام شود، فقط عملیات اصلی را انجام می دهد و ناظرها بقیه وظایف مانند ثبت لاگ، ارسال اعلان یا تازه سازی کش را مدیریت می کنند. نتیجه این کار معماری تمیزتر و توسعه سریع تر است.
آشنایی با چرخه حیات مدل و رویدادهای مهم
ناظرها روی رویدادهای چرخه حیات مدل های Eloquent سوار می شوند. هر رویداد لحظه ای از زندگی یک رکورد را نشان می دهد و جایی مناسب برای اجرای منطق جانبی است. شناخت این رویدادها کمک می کند دقیقا در زمان مناسب وارد عمل شوید.
- retrieved: بلافاصله بعد از واکشی مدل از دیتابیس
- creating و created: قبل و بعد از ایجاد رکورد
- updating و updated: قبل و بعد از بروزرسانی رکورد
- saving و saved: قبل و بعد از عملیات ذخیره کلی
- deleting و deleted: قبل و بعد از حذف رکورد
- restoring و restored: قبل و بعد از بازیابی رکورد نرم حذف شده
- forceDeleted: بعد از حذف دائمی
انتخاب رویداد درست اهمیت دارد. مثلا اگر می خواهید مقدار پیش فرضی را قبل از ایجاد ست کنید از creating استفاده کنید، اما اگر نیاز دارید بعد از ساخت رکورد وظایف غیر حیاتی مثل ارسال ایمیل اجرا شود، created بهتر است و حتی می توانید آن را به صف بفرستید.
راه اندازی Observer قدم به قدم
برای فعال کردن ناظرها، چند گام ساده کافی است. مراحل زیر شما را از ایجاد کلاس تا اتصال آن به مدل پیش می برد.
- ساخت کلاس ناظر با تکیه به ابزار خط فرمان:
php artisan make:observer UserObserver --model=Userاین دستور کلاسی می سازد که متدهای مربوط به رویدادهای مدل کاربر را می توانید در آن پیاده سازی کنید.
- پیاده سازی متدهای مورد نیاز در کلاس ناظر. هر متد همنام یک رویداد است و شی مدل را دریافت می کند.
- ثبت ناظر در سرویس پرووایدر برنامه تا لاراول بداند برای رویدادهای این مدل از کدام ناظر استفاده کند.
- اجرای تست دستی یا واحد تا مطمئن شوید منطق در زمان مناسب اجرا می شود.
مثال عملی برای مدل کاربر
فرض کنید می خواهید هنگام ساخت کاربر ایمیل خوش آمد ارسال کنید، کش نمایه را خنثی کنید و تغییر ایمیل را در بروزرسانی رصد کنید. یک پیاده سازی ساده به شکل زیر است.
namespace App\Observers;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use App\Jobs\SendWelcomeEmail;
class UserObserver
{
public function creating(User $user): void
{
// پاک سازی یا استانداردسازی قبل از ساخت
$user->username = strtolower($user->username);
}
public function created(User $user): void
{
// کارهای غیر حیاتی را به صف بسپارید
SendWelcomeEmail::dispatch($user->id);
// پاک کردن کش مرتبط
Cache::forget("user.{$user->id}");
Log::info('User created', ['id' => $user->id]);
}
public function updating(User $user): void
{
// فقط وقتی ایمیل تغییر کرده کاری انجام دهید
if ($user->isDirty('email')) {
Log::warning('User email changed', [
'id' => $user->id,
'old' => $user->getOriginal('email'),
'new' => $user->email,
]);
}
}
public function deleted(User $user): void
{
// پس از حذف کاربر کش را پاک کنید
Cache::forget("user.{$user->id}");
}
}
برای ثبت ناظر، در متد boot سرویس پرووایدر برنامه آن را به مدل متصل کنید.
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Models\User;
use App\Observers\UserObserver;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
User::observe(UserObserver::class);
}
}
از این لحظه هر بار که کاربر ایجاد، بروزرسانی یا حذف شود، متدهای متناظر اجرا می شوند. اگر چند مدل دارید که به منطق مشترک نیاز دارند، می توانید همان ناظر را برای هر دو مدل ثبت کنید.
نکات مهم و تله های رایج
- خطر حلقه بی نهایت: اگر داخل متد updating دوباره save را فراخوانی کنید ممکن است رویداد دوباره تریگر شود. تا حد امکان تغییرات را در creating یا updating اعمال کنید و از متدهایی مثل isDirty و wasChanged برای کنترل رفتار کمک بگیرید.
- کارهای سنگین را به صف بسپارید: ارسال ایمیل، پردازش تصویر یا فراخوانی سرویس های بیرونی را در رویدادهای پس از ذخیره مثل created یا updated با Job های صف انجام دهید تا پاسخ دهی کند نشود.
- تراکنش ها را فراموش نکنید: اگر چند عملیات مرتبط دارید، از DB::transaction استفاده کنید یا منطق حساس را طوری بنویسید که در صورت شکست یکی از گام ها، داده در وضعیت ناسازگار نماند.
- استفاده حداقلی از سرویس های بیرونی: وابستگی های سنگین را تزریق نکنید و در صورت نیاز از فاسادهایی که قابل فیک شدن در تست هستند استفاده کنید تا تست ها پایدار بماند.
- مراقب ترتیب رویدادها باشید: اگر داده باید قبل از ذخیره ویرایش شود از creating یا updating استفاده کنید، اما اگر نتیجه عملیات ذخیره برای شما مهم است سراغ created یا updated بروید.
- از withoutEvents در مواقع ضروری: اگر نیاز است تغییری بدون تریگر شدن رویدادها ذخیره شود، قطعه منطق را داخل Model::withoutEvents اجرا کنید. با احتیاط استفاده کنید تا منطق تجاری حیاتی دور زده نشود.
موارد استفاده متداول در پروژه های واقعی
برای اینکه بهتر ایده بگیرید، چند سناریوی رایج را مرور می کنیم. اجرای این موارد در ناظرها باعث می شود کنترلرها سبک و واضح باقی بمانند.
- ثبت لاگ های ممیزی برای ایجاد، بروزرسانی و حذف رکوردها
- پاک سازی و به روز کردن کش بعد از تغییر داده های کلیدی
- ارسال اعلان ایمیلی یا پوش به کاربران پس از رویدادهای مهم
- هماهنگ کردن شمارنده ها مثل تعداد دیدگاه ها یا لایک ها
- تبدیل و استانداردسازی ورودی ها مانند تولید اسلاگ و نرمال سازی متن
- استفاده از صف برای کارهای پرهزینه که به نتیجه فوری نیاز ندارند
نمونه ای از تازه سازی کش برای مدل پست را ببینید:
class PostObserver
{
public function saved(\App\Models\Post $post): void
{
Cache::forget("post.{$post->id}");
Cache::forget('homepage.posts');
}
}
مقایسه کوتاه با رویدادها و لیسنرها در لاراول
گاهی میان ناظرهای مدل و رویدادهای سفارشی مردد می شوید. جدول زیر تصمیم گیری را ساده تر می کند.
| روش | چه زمانی مناسب است |
|---|---|
| Observer مدل | وقتی منطق مستقیما به چرخه حیات مدل وابسته است و باید کنار آن نگهداری شود. |
| رویداد و لیسنر سفارشی | وقتی می خواهید اتفاقی دامنه محور را در کل سیستم اعلام کنید و چند بخش نامرتبط واکنش نشان دهند. |
| کد داخل کنترلر | برای منطق ساده و یکباره. در صورت رشد منطق بهتر است به ناظر یا رویداد مهاجرت کند. |
به طور خلاصه، اگر رویداد دقیقا همزمان با ایجاد یا بروزرسانی رکورد رخ می دهد، ناظر مناسب تر است. اگر رویداد معنای دامنه ای دارد و ممکن است با منابع مختلفی تحریک شود، رویداد سفارشی بهتر است.
تست و نگهداری Observer ها
برای اطمینان از پایداری، تست واحد بنویسید. از فیک های صف، ایمیل یا استوریج استفاده کنید تا بدون وابستگی بیرونی منطق را بررسی کنید. در مثال زیر بررسی می کنیم که با ایجاد کاربر، جاب خوش آمد به صف رفته است.
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use App\Jobs\SendWelcomeEmail;
use App\Models\User;
class UserObserverTest extends TestCase
{
use RefreshDatabase;
public function test_welcome_job_is_dispatched_on_user_created(): void
{
Queue::fake();
User::factory()->create();
Queue::assertPushed(SendWelcomeEmail::class);
}
}
در نگهداری، اصول تک مسئولیتی را رعایت کنید تا هر ناظر تنها یک وظیفه روشن داشته باشد. اگر یک ناظر بزرگ و شلوغ شد، آن را به چند ناظر تخصصی یا سرویس های کوچک تر بشکنید و از تزریق وابستگی ها در سطح سرویس استفاده کنید. همچنین نامگذاری دقیق متدها و لاگ مناسب عیب یابی را ساده می کند.
نکته های تکمیلی برای کارایی بهتر
اگر تعداد رویدادها زیاد است، هزینه غیر ضروری به برنامه تحمیل نکنید. فقط متدهای لازم را در ناظر نگه دارید و از چک های سریع در ابتدای متد استفاده کنید تا در صورت بی اثر بودن تغییرات، ادامه اجرا متوقف شود. به عنوان مثال وقتی هیچ فیلد مهمی تغییر نکرده است، کاری انجام ندهید.
در سیستم های شلوغ، کش کردن خواندن های پر تکرار و پاکسازی هدفمند کش در رویدادهای ذخیره، کارایی را بالا می برد. همچنین بهتر است کارهای با تاخیرپذیری بالا را به صف بسپارید تا زمان پاسخ درخواست های اصلی کوتاه بماند.
اگر لازم است در حوزه های مختلف از ناظر مشابه استفاده کنید، ثبت چندباره همان ناظر برای مدل های متفاوت مجاز است. فقط مراقب باشید منطق وابسته به نام مدل یا فیلدهای خاص را با شرط گذاری مناسب جدا کنید تا رفتار ناخواسته ایجاد نشود.
کاربرد عبارت کلیدی و ارتباط با پروژه های واقعی
برای تیم هایی که روی کد تمیز و قابلیت نگهداری تاکید دارند، کار با Observer Pattern در لاراول راهی استاندارد برای سامان دهی منطق جانبی است. این الگو شما را مجبور می کند مرز بین منطق هسته ای و عوارض جانبی را شفاف کنید، که نتیجه آن تست های قابل اعتمادتر و کاهش باگ های ناشی از تکرار کد است.
اگر پروژه شما بزرگ می شود و چند سرویس یا ماژول باید به تغییرات داده واکنش نشان دهند، استفاده درست از ناظرها در کنار رویدادهای دامنه ای ترکیبی قدرتمند می سازد. با این ترکیب هم وابستگی های نزدیک به مدل را مدیریت می کنید و هم سیگنال های سطح دامنه را در سراسر سیستم پخش می کنید.
جمع بندی
ناظرهای مدل روشی ساده و موثر برای مدیریت منطق وابسته به رویدادهای دیتابیس هستند. با تعریف یک کلاس متمرکز، هم از تکرار کد جلوگیری می شود و هم تست پذیری و خوانایی افزایش می یابد. برای کارهای سنگین از صف استفاده کنید، از حلقه های ناخواسته دوری کنید و تنها رویدادهای لازم را پیاده سازی کنید. با این اصول، بهره گیری از Observer Pattern در لاراول تجربه ای تمیز، ایمن و مقیاس پذیر خواهد بود.






