درک ناظران Laravel: یک راهنمای کامل برای دستیابی به رویداد مدل خودکار

ناظران Laravel یک روش زیبا برای گوش دادن به رویدادهای مدل فصیح و اجرای کدی به طور خودکار در صورت بروز اقدامات خاص در مدل های شما ارائه می دهند. به ناظران به عنوان شنوندگان تخصصی رویداد که مدل های شما را تماشا می کنند و در برابر تغییراتی مانند ایجاد ، به روزرسانی یا حذف واکنش نشان می دهند ، فکر کنید. این الگوی به شما کمک می کند مدلهای خود را تمیز نگه دارید در حالی که منطق کسب و کار مرتبط را در کلاسهای اختصاصی متمرکز کنید.
ناظران لاراول چیست و چرا باید از آنها استفاده کنید؟
تصور کنید که یک برنامه تجارت الکترونیکی دارید که در آن هر بار که کاربر سفارش را سفارش دهد ، باید یک ایمیل تأیید ارسال کنید ، سطح موجودی را به روز کنید و معامله را وارد کنید. بدون ناظران ، ممکن است این منطق را در کنترل کننده های خود پراکنده کنید و کد خود را برای حفظ و آزمایش سخت تر کنید. ناظران با فراهم کردن یک مکان متمرکز برای عوارض جانبی مرتبط با مدل ، این مشکل را حل می کنند.
ناظران به ویژه قدرتمند هستند زیرا به طور خودکار بر اساس وقایع مدل فصیح تحریک می شوند. لاراول این وقایع را در طول چرخه چرخه مدل آتش می زند: creating
با created
با updating
با updated
با saving
با saved
با deleting
با deleted
با restoring
وت restored
بشر هر رویداد لحظه ای متفاوت در سفر مدل شما از طریق برنامه را نشان می دهد.
اولین ناظر خود را ایجاد کنید
بیایید با یک مثال عملی شروع کنیم. فرض کنید شما در حال ساختن یک برنامه وبلاگ هستید که می خواهید به طور خودکار برای ارسال پست ها به صورت خودکار تولید کنید و هنگام انتشار مقاله اعلان ها را ارسال کنید. در اینجا آمده است که چگونه می توانید یک ناظر را برای خود ایجاد کنید Post
مدل:
namespace App\Observers;
use App\Models\Post;
use App\Notifications\PostPublishedNotification;
use Illuminate\Support\Str;
class PostObserver
{
public function creating(Post $post): void
{
if (empty($post->slug)) {
$post->slug = Str::slug($post->title);
// Ensure slug uniqueness
$originalSlug = $post->slug;
$counter = 1;
while (Post::where('slug', $post->slug)->exists()) {
$post->slug = $originalSlug . '-' . $counter;
$counter++;
}
}
}
/**
* Handle the Post "created" event.
* This runs after the model has been successfully saved
*/
public function created(Post $post): void
{
// Log the creation for analytics
logger('New post created', [
'post_id' => $post->id,
'title' => $post->title,
'author_id' => $post->user_id
]);
}
/**
* Handle the Post "updated" event.
*/
public function updated(Post $post): void
{
// Check if the post was just published
if ($post->wasChanged('published_at') && $post->published_at !== null) {
// Send notification to subscribers
$post->author->notify(new PostPublishedNotification($post));
// Update search index or cache
$this->updateSearchIndex($post);
}
}
/**
* Handle the Post "deleting" event.
* This runs before the model is deleted
*/
public function deleting(Post $post): void
{
// Clean up related data before deletion
$post->comments()->delete();
$post->tags()->detach();
// Remove from search index
$this->removeFromSearchIndex($post);
}
/**
* Handle the Post "deleted" event.
*/
public function deleted(Post $post): void
{
// Log the deletion for audit purposes
logger('Post deleted', [
'post_id' => $post->id,
'title' => $post->title,
'deleted_by' => auth()->id()
]);
}
/**
* Helper method to update search index
*/
private function updateSearchIndex(Post $post): void
{
// Implementation would depend on your search solution
// This could be Elasticsearch, Algolia, etc.
}
/**
* Helper method to remove from search index
*/
private function removeFromSearchIndex(Post $post): void
{
// Remove from search index implementation
}
}
برای تولید این ناظر با استفاده از صنعتگر ، شما اجرا می کنید:
php artisan make:observer PostObserver --model=Post
این دستور کلاس Observer را با خرد کردن روش برای همه وقایع فصیح مشترک ایجاد می کند.
ثبت ناظران
پس از ایجاد Observer خود ، باید آن را در Laravel ثبت کنید. متداول ترین رویکرد ثبت ناظران در شما است AppServiceProvider
یا یک ارائه دهنده خدمات اختصاصی ایجاد کنید. در اینجا نحوه انجام آن در خود آمده است AppServiceProvider
:
namespace App\Providers;
use App\Models\Post;
use App\Models\User;
use App\Models\Order;
use App\Observers\PostObserver;
use App\Observers\UserObserver;
use App\Observers\OrderObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Register model observers
Post::observe(PostObserver::class);
User::observe(UserObserver::class);
Order::observe(OrderObserver::class);
}
}
برای برنامه های بزرگتر ، ممکن است بخواهید یک اختصاصی ایجاد کنید ObserverServiceProvider
:
namespace App\Providers;
use App\Models\Post;
use App\Models\User;
use App\Models\Order;
use App\Observers\PostObserver;
use App\Observers\UserObserver;
use App\Observers\OrderObserver;
use Illuminate\Support\ServiceProvider;
class ObserverServiceProvider extends ServiceProvider
{
/**
* The model observers for your application.
*
* @var array
*/
protected $observers = [
Post::class => PostObserver::class,
User::class => UserObserver::class,
Order::class => OrderObserver::class,
];
/**
* Register the observers.
*/
public function boot(): void
{
foreach ($this->observers as $model => $observer) {
$model::observe($observer);
}
}
}
فراموش نکنید که این ارائه دهنده خدمات را در خود ثبت کنید config/app.php
پرونده:
'providers' => [
// Other providers...
App\Providers\ObserverServiceProvider::class,
],
مثال در دنیای واقعی: پردازش سفارش تجارت الکترونیکی
بیایید یک مثال پیچیده تر را کشف کنیم که قدرت ناظران را در یک زمینه تجارت الکترونیکی نشان می دهد. این OrderObserver
کل چرخه عمر یک سفارش را کنترل می کند:
namespace App\Observers;
use App\Models\Order;
use App\Services\InventoryService;
use App\Services\EmailService;
use App\Services\PaymentService;
use App\Notifications\OrderConfirmationNotification;
use App\Notifications\OrderShippedNotification;
use App\Jobs\ProcessRefund;
class OrderObserver
{
protected $inventoryService;
protected $emailService;
protected $paymentService;
public function __construct(
InventoryService $inventoryService,
EmailService $emailService,
PaymentService $paymentService
) {
$this->inventoryService = $inventoryService;
$this->emailService = $emailService;
$this->paymentService = $paymentService;
}
/**
* Handle the Order "created" event.
* This fires after a new order is successfully created
*/
public function created(Order $order): void
{
// Reserve inventory for the order items
foreach ($order->items as $item) {
$this->inventoryService->reserve($item->product_id, $item->quantity);
}
// Send order confirmation email
$order->customer->notify(new OrderConfirmationNotification($order));
// Create audit log
activity()
->performedOn($order)
->log('Order created with total: ' . $order->total_amount);
}
/**
* Handle the Order "updated" event.
* This allows us to react to status changes
*/
public function updated(Order $order): void
{
// Check if order status changed to 'shipped'
if ($order->wasChanged('status') && $order->status === 'shipped') {
$this->handleOrderShipped($order);
}
// Check if order was cancelled
if ($order->wasChanged('status') && $order->status === 'cancelled') {
$this->handleOrderCancelled($order);
}
// Check if payment status changed to 'failed'
if ($order->wasChanged('payment_status') && $order->payment_status === 'failed') {
$this->handlePaymentFailed($order);
}
}
/**
* Handle the Order "deleting" event.
* Clean up before order deletion
*/
public function deleting(Order $order): void
{
// Release reserved inventory
foreach ($order->items as $item) {
$this->inventoryService->release($item->product_id, $item->quantity);
}
// Process refund if payment was successful
if ($order->payment_status === 'completed') {
ProcessRefund::dispatch($order);
}
}
/**
* Handle order shipped logic
*/
private function handleOrderShipped(Order $order): void
{
// Send shipping notification
$order->customer->notify(new OrderShippedNotification($order));
// Update inventory (convert reservation to actual sale)
foreach ($order->items as $item) {
$this->inventoryService->confirmSale($item->product_id, $item->quantity);
}
// Log shipping event
activity()
->performedOn($order)
->log('Order shipped with tracking number: ' . $order->tracking_number);
}
/**
* Handle order cancellation logic
*/
private function handleOrderCancelled(Order $order): void
{
// Release inventory reservations
foreach ($order->items as $item) {
$this->inventoryService->release($item->product_id, $item->quantity);
}
// Process refund if needed
if (in_array($order->payment_status, ['completed', 'processing'])) {
ProcessRefund::dispatch($order);
}
// Send cancellation email
$this->emailService->sendOrderCancellationEmail($order);
}
/**
* Handle payment failure
*/
private function handlePaymentFailed(Order $order): void
{
// Release inventory since payment failed
foreach ($order->items as $item) {
$this->inventoryService->release($item->product_id, $item->quantity);
}
// Update order status
$order->update(['status' => 'payment_failed']);
// Notify customer
$this->emailService->sendPaymentFailedEmail($order);
}
}
تکنیک های پیشرفته Observer
منطق ناظر مشروط
بعضی اوقات می خواهید روشهای ناظر فقط در شرایط خاصی اجرا شود. در اینجا نحوه اجرای منطق شرطی آورده شده است:
namespace App\Observers;
use App\Models\User;
use App\Services\CacheService;
use App\Jobs\SendWelcomeEmail;
class UserObserver
{
/**
* Handle the User "created" event.
*/
public function created(User $user): void
{
// Only send welcome email for verified users
if ($user->email_verified_at !== null) {
SendWelcomeEmail::dispatch($user);
}
// Update user statistics cache
CacheService::increment('total_users');
}
/**
* Handle the User "updated" event.
*/
public function updated(User $user): void
{
// Clear user cache when profile is updated
if ($user->wasChanged(['name', 'email', 'avatar'])) {
cache()->forget("user.{$user->id}");
}
// Handle email verification
if ($user->wasChanged('email_verified_at') && $user->email_verified_at !== null) {
$this->handleEmailVerified($user);
}
// Handle subscription changes
if ($user->wasChanged('subscription_tier')) {
$this->handleSubscriptionChange($user);
}
}
/**
* Handle email verification completion
*/
private function handleEmailVerified(User $user): void
{
// Send welcome email now that email is verified
SendWelcomeEmail::dispatch($user);
// Unlock premium features trial
$user->update([
'trial_ends_at' => now()->addDays(14)
]);
// Log verification for analytics
activity()
->performedOn($user)
->log('Email verified');
}
/**
* Handle subscription tier changes
*/
private function handleSubscriptionChange(User $user): void
{
$oldTier = $user->getOriginal('subscription_tier');
$newTier = $user->subscription_tier;
// Log the change
activity()
->performedOn($user)
->log("Subscription changed from {$oldTier} to {$newTier}");
// Update user permissions based on new tier
$user->syncPermissions($this->getPermissionsForTier($newTier));
// Clear cached user data
cache()->tags(['user', $user->id])->flush();
}
/**
* Get permissions for subscription tier
*/
private function getPermissionsForTier(string $tier): array
{
return match ($tier) {
'basic' => ['read_posts', 'create_comments'],
'premium' => ['read_posts', 'create_comments', 'create_posts'],
'enterprise' => ['read_posts', 'create_comments', 'create_posts', 'manage_users'],
default => ['read_posts'],
};
}
}
کار با وابستگی های ناظر
ناظران می توانند از تزریق وابستگی درست مانند کنترل کننده ها و سایر کلاس های لاراول استفاده کنند. این باعث می شود آنها بسیار قابل آزمایش و انعطاف پذیر باشند:
namespace App\Observers;
use App\Models\Product;
use App\Services\SearchService;
use App\Services\CacheService;
use App\Services\ImageOptimizationService;
use Illuminate\Contracts\Queue\ShouldQueue;
class ProductObserver implements ShouldQueue
{
protected $searchService;
protected $cacheService;
protected $imageService;
public function __construct(
SearchService $searchService,
CacheService $cacheService,
ImageOptimizationService $imageService
) {
$this->searchService = $searchService;
$this->cacheService = $cacheService;
$this->imageService = $imageService;
}
/**
* Handle the Product "created" event.
*/
public function created(Product $product): void
{
// Add to search index
$this->searchService->index($product);
// Optimize product images
if ($product->images->isNotEmpty()) {
foreach ($product->images as $image) {
$this->imageService->optimize($image->path);
}
}
// Clear category cache
$this->cacheService->clearCategoryCache($product->category_id);
}
/**
* Handle the Product "updated" event.
*/
public function updated(Product $product): void
{
// Update search index if searchable fields changed
if ($product->wasChanged(['name', 'description', 'tags'])) {
$this->searchService->update($product);
}
// Clear related caches
if ($product->wasChanged(['price', 'stock_quantity', 'is_active'])) {
$this->cacheService->clearProductCache($product->id);
$this->cacheService->clearCategoryCache($product->category_id);
}
}
/**
* Handle the Product "deleted" event.
*/
public function deleted(Product $product): void
{
// Remove from search index
$this->searchService->remove($product->id);
// Clear all related caches
$this->cacheService->clearProductCache($product->id);
$this->cacheService->clearCategoryCache($product->category_id);
}
}
بهترین شیوه ها و مشکلات رایج
ملاحظات عملکرد
ناظران به طور پیش فرض همزمان اجرا می شوند ، به این معنی که در صورت انجام عملیات سنگین می توانند برنامه شما را کاهش دهند. برای کارهای وقت گیر ، صف آنها را در نظر بگیرید:
namespace App\Observers;
use App\Models\Order;
use App\Jobs\ProcessOrderAnalytics;
use App\Jobs\UpdateRecommendations;
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderObserver implements ShouldQueue
{
/**
* Handle the Order "created" event.
*/
public function created(Order $order): void
{
// Quick operations can run immediately
$order->update(['order_number' => $this->generateOrderNumber()]);
// Heavy operations should be queued
ProcessOrderAnalytics::dispatch($order);
UpdateRecommendations::dispatch($order->customer);
}
private function generateOrderNumber(): string
{
return 'ORD-' . now()->format('Ymd') . '-' . str_pad(Order::count() + 1, 4, '0', STR_PAD_LEFT);
}
}
اجتناب از حلقه های بی نهایت
هنگام به روزرسانی مدل ها در ناظران مراقب باشید ، زیرا این می تواند باعث ایجاد حوادث ناظر اضافی و ایجاد حلقه های بی نهایت شود:
namespace App\Observers;
use App\Models\User;
class UserObserver
{
/**
* Handle the User "updated" event.
*/
public function updated(User $user): void
{
// BAD: This could create an infinite loop
// $user->update(['last_activity' => now()]);
// GOOD: Use saveQuietly or update database directly
$user->saveQuietly();
// OR use query builder to avoid triggering events
User::where('id', $user->id)->update(['last_activity' => now()]);
}
}
اگر نیاز به به روزرسانی مدل در یک ناظر دارید ، از آن استفاده کنید saveQuietly()
روش یا نمایش داده های مستقیم پایگاه داده برای جلوگیری از ایجاد حوادث اضافی.
رسیدگی به خطا
همیشه استفاده از خطای مناسب را در ناظران خود اجرا کنید تا از شکستن جریان اصلی برنامه جلوگیری کنید:
namespace App\Observers;
use App\Models\Post;
use Exception;
use Illuminate\Support\Facades\Log;
class PostObserver
{
/**
* Handle the Post "created" event.
*/
public function created(Post $post): void
{
try {
// Attempt to perform observer logic
$this->updateSearchIndex($post);
$this->sendNotifications($post);
} catch (Exception $e) {
// Log the error but don't break the main flow
Log::error('Post observer failed', [
'post_id' => $post->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// Optionally, queue a retry job
// RetryPostProcessing::dispatch($post)->delay(now()->addMinutes(5));
}
}
}
پایان
ناظران لاراول روشی قدرتمند و ظریف برای پاسخ به رویدادهای مدل در برنامه شما ارائه می دهند. آنها به شما کمک می کنند تا با متمرکز کردن عوارض جانبی مرتبط با مدل و منطق تجارت ، کد تمیز و سازمان یافته را حفظ کنید. با درک انواع مختلف رویداد ، اجرای رسیدگی به خطای مناسب و پیروی از بهترین شیوه های مربوط به عملکرد و آزمایش ، می توانید از ناظران برای ساختن برنامه های LARAVEL قابل حفظ و قوی تر استفاده کنید.
به یاد داشته باشید که ناظران فقط یک ابزار در ابزار Laravel شما هستند. در صورت نیاز به پاسخ دادن به وقایع مدل به طور مداوم در برنامه خود ، از آنها استفاده کنید ، اما احساس نمی کنید که از آنها برای هر قطعه ای از منطق مربوط به مدل استفاده کنید. گاهی اوقات یک تماس روش ساده در کنترلر یا کلاس خدمات شما ممکن است مناسب تر باشد ، به خصوص برای عملیات یک طرفه یا منطق کسب و کار پیچیده که به طور مستقیم به چرخه عمر مدل مربوط نمی شود.
با ادامه ساخت برنامه های LARAVEL ، متوجه خواهید شد که ناظران به یک الگوی ارزشمند برای سازماندهی کد شما تبدیل می شوند و مدلهای شما ضمن رسیدگی به عوارض جانبی تغییر داده ها به روشی تمیز و قابل آزمایش ، بر مسئولیت های اصلی آنها متمرکز شده اند.