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 |
creating | Before insert | Yes (return false) |
created | After insert | No |
updating | Before update | Yes (return false) |
updated | After update | No |
deleting | Before delete | Yes (return false) |
deleted | After delete | No |
saving | Before save (create/update) | Yes (return false) |
saved | After save (create/update) | No |
restoring | Before restore (soft delete) | Yes (return false) |
restored | After 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 focused | One observer per concern (audit, notifications) |
| Avoid heavy operations | Use queues for emails, notifications |
| Handle exceptions | Wrap risky operations in try-catch |
| Return early | Cancel operations by returning false in "before" events |
| Dependency injection | Pass 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());