برنامه نویسی

درک ناظران 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 ، متوجه خواهید شد که ناظران به یک الگوی ارزشمند برای سازماندهی کد شما تبدیل می شوند و مدلهای شما ضمن رسیدگی به عوارض جانبی تغییر داده ها به روشی تمیز و قابل آزمایش ، بر مسئولیت های اصلی آنها متمرکز شده اند.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا