/ Observers

Observers

Observers allow you to listen to model lifecycle events. Hook into creating, created, updating, updated, deleting, and deleted events.


Observer Events Summary

Event When Fired Can Cancel
creatingBefore insertYes (return false)
createdAfter insertNo
updatingBefore updateYes (return false)
updatedAfter updateNo
deletingBefore deleteYes (return false)
deletedAfter deleteNo
savingBefore save (create/update)Yes (return false)
savedAfter save (create/update)No
restoringBefore restore (soft delete)Yes (return false)
restoredAfter restore (soft delete)No

Creating an Observer

use Miko\Database\ORM\Observer;
use Miko\Database\ORM\Model;
use Miko\Log\Logger;
use Miko\Cache\ApcuCache;

class UserObserver extends Observer
{
    /**
     * Called before creating a new user
     * Return false to cancel the operation
     */
    public function creating(Model $model): bool
    {
        // Set default values
        $model->setAttribute('Role', 'user');
        $model->setAttribute('IsActive', true);
        
        // Hash password if plain text
        if ($model->Password && strlen($model->Password) < 60) {
            $model->Password = password_hash($model->Password, PASSWORD_DEFAULT);
        }
        
        return true; // Continue with creation
    }
    
    /**
     * Called after user is created
     */
    public function created(Model $model): void
    {
        // Send welcome email
        EmailService::sendWelcome($model->Email, $model->Name);
        
        // Log the event
        Logger::general("User created: {$model->Id} - {$model->Email}", [], 'INFO');
        
        // Create default profile
        Profile::create([
            'UserId' => $model->Id,
            'Bio' => '',
            'Avatar' => 'default.png'
        ]);
    }
    
    /**
     * Called before updating a user
     */
    public function updating(Model $model): bool
    {
        // Check if email is being changed
        if ($model->isDirty('Email')) {
            // Require email verification
            $model->EmailVerifiedAt = null;
        }
        
        // Hash password if changed
        if ($model->isDirty('Password') && strlen($model->Password) < 60) {
            $model->Password = password_hash($model->Password, PASSWORD_DEFAULT);
        }
        
        return true;
    }
    
    /**
     * Called after user is updated
     */
    public function updated(Model $model): void
    {
        // Clear cache
        ApcuCache::forget("user:{$model->Id}");
        
        // Log changes
        Logger::general("User updated: {$model->Id}", [], 'INFO');
    }
    
    /**
     * Called before deleting a user
     */
    public function deleting(Model $model): bool
    {
        // Prevent deletion of admin users
        if ($model->Role === 'admin') {
            Logger::warning("Attempted to delete admin user: {$model->Id}");
            return false; // Cancel deletion
        }
        
        // Check for active orders
        if ($model->orders()->where('Status', 'pending')->exists()) {
            return false; // Cancel if has pending orders
        }
        
        return true;
    }
    
    /**
     * Called after user is deleted
     */
    public function deleted(Model $model): void
    {
        // Clean up related data
        Profile::where('UserId', $model->Id)->delete();
        
        // Clear all caches
        ApcuCache::forget("user:{$model->Id}");
        ApcuCache::forgetByTag("user_{$model->Id}");
        
        // Log deletion
        Logger::general("User deleted: {$model->Id}", [], 'INFO');
    }
}

Registering Observers

Using ObserverManager

use Miko\Database\ORM\ObserverManager;

// Register with class name
ObserverManager::register(User::class, UserObserver::class);

// Register with instance
ObserverManager::register(User::class, new UserObserver());

// Register multiple observers for same model
ObserverManager::register(User::class, UserObserver::class);
ObserverManager::register(User::class, AuditObserver::class);

In Bootstrap/Startup

// bootstrap.php or AppServiceProvider
function registerObservers(): void
{
    ObserverManager::register(User::class, UserObserver::class);
    ObserverManager::register(Order::class, OrderObserver::class);
    ObserverManager::register(Product::class, ProductObserver::class);
    ObserverManager::register(Payment::class, PaymentObserver::class);
}

registerObservers();

Practical Examples

Audit Observer

class AuditObserver extends Observer
{
    public function creating(Model $model): bool
    {
        $model->CreatedBy = auth()->id();
        return true;
    }
    
    public function updating(Model $model): bool
    {
        $model->UpdatedBy = auth()->id();
        return true;
    }
    
    public function deleted(Model $model): void
    {
        AuditLog::create([
            'Action' => 'delete',
            'ModelType' => get_class($model),
            'ModelId' => $model->Id,
            'UserId' => auth()->id(),
            'OldValues' => json_encode($model->toArray()),
            'CreatedAt' => date('Y-m-d H:i:s')
        ]);
    }
}

Order Observer

class OrderObserver extends Observer
{
    public function creating(Model $model): bool
    {
        // Generate order number
        $model->OrderNumber = 'ORD-' . date('Ymd') . '-' . rand(1000, 9999);
        $model->Status = 'pending';
        return true;
    }
    
    public function created(Model $model): void
    {
        // Send order confirmation
        EmailService::sendOrderConfirmation($model);
        
        // Notify admin
        NotificationService::notifyAdmin("New order: {$model->OrderNumber}");
    }
    
    public function updating(Model $model): bool
    {
        // Track status changes
        if ($model->isDirty('Status')) {
            $model->StatusChangedAt = date('Y-m-d H:i:s');
        }
        return true;
    }
    
    public function updated(Model $model): void
    {
        // Send status update notification
        if ($model->wasChanged('Status')) {
            EmailService::sendOrderStatusUpdate($model);
        }
    }
}

Slug Observer

class SlugObserver extends Observer
{
    public function creating(Model $model): bool
    {
        if (empty($model->Slug) && !empty($model->Title)) {
            $model->Slug = $this->generateSlug($model->Title);
        }
        return true;
    }
    
    public function updating(Model $model): bool
    {
        if ($model->isDirty('Title') && empty($model->Slug)) {
            $model->Slug = $this->generateSlug($model->Title);
        }
        return true;
    }
    
    private function generateSlug(string $title): string
    {
        $slug = strtolower(trim($title));
        $slug = preg_replace('/[^a-z0-9-]/', '-', $slug);
        $slug = preg_replace('/-+/', '-', $slug);
        return trim($slug, '-');
    }
}

Removing Observers

// Remove specific observer
ObserverManager::unregister(User::class, UserObserver::class);

// Remove all observers for a model
ObserverManager::flush(User::class);

// Remove all observers
ObserverManager::flushAll();

Observer Manager Methods

Method Description
register($model, $observer)Register observer for model
unregister($model, $observer)Remove specific observer
flush($model)Remove all observers for model
flushAll()Remove all observers
fire($event, $model)Manually fire event
getObservers($model)Get registered observers

Best Practices

Practice Description
Keep observers focusedOne observer per concern (audit, notifications)
Avoid heavy operationsUse queues for emails, notifications
Handle exceptionsWrap risky operations in try-catch
Return earlyCancel operations by returning false in "before" events
Dependency injectionPass services to observer constructor
class UserObserver extends Observer
{
    public function created(Model $model): void
    {
        try {
            EmailService::sendWelcome($model->Email);
        } catch (Exception $e) {
            Logger::general('Failed to send welcome email: ' . $e->getMessage());
        }
    }
}

ObserverManager::register(User::class, new UserObserver());