bentools/doctrine-watcher
Monitor Doctrine entity inserts and updates with a lightweight event subscriber. Watch specific classes and properties, get a changeset with old/new values or additions/removals, and run callbacks to react to changes (e.g., email or roles updates).
composer require bentools/doctrine-watcher:0.2.*
AppServiceProvider):
use BenTools\DoctrineWatcher\Watcher\DoctrineWatcher;
public function boot()
{
$em = $this->app->make('doctrine.orm.entity_manager');
$watcher = new DoctrineWatcher();
$em->getEventManager()->addEventSubscriber($watcher);
}
$watcher->watch(
App\Entity\User::class,
'email',
function ($changeset, $operationType, $user) {
if ($changeset->hasChanges()) {
logger()->info("Email changed from {$changeset->getOldValue()} to {$changeset->getNewValue()}");
}
}
);
Track changes to sensitive fields (e.g., User::roles) and log them:
$watcher->watch(
App\Entity\User::class,
'roles',
function ($changeset, $operationType, $user) {
if ($changeset->hasAdditions()) {
AuditLog::create([
'entity' => 'User',
'field' => 'roles',
'old_value' => $changeset->getOldValue(),
'new_value' => $changeset->getAdditions(),
'user_id' => $user->getId(),
]);
}
}
);
Register the Watcher:
EventManager once (e.g., in a service provider).$watcher = new DoctrineWatcher(['trigger_on_persist' => true]);
$em->getEventManager()->addEventSubscriber($watcher);
Define Watches:
watch() for each entity/property pair.User property watches in one place).
$watcher->watch(User::class, 'email', $emailCallback);
$watcher->watch(User::class, 'status', $statusCallback);
Handle Callbacks:
UserChangeHandler).
$watcher->watch(
User::class,
'email',
[new UserChangeHandler(), 'handleEmailChange']
);
Configure Per-Watch:
trigger_on_persist) per property:
$watcher->watch(
User::class,
'email',
$callback,
['trigger_on_persist' => true, 'trigger_when_no_changes' => false]
);
Laravel-Specific:
$this->app->singleton(DoctrineWatcher::class, function ($app) {
$watcher = new DoctrineWatcher();
$em = $app->make('doctrine.orm.entity_manager');
$em->getEventManager()->addEventSubscriber($watcher);
return $watcher;
});
config/doctrine-watcher.php:
'watches' => [
App\Entity\User::class => [
'email' => ['callback' => 'App\Services\UserChangeHandler@logEmailChange'],
'roles' => ['callback' => 'App\Services\UserChangeHandler@logRolesChange'],
],
],
boot():
$watcher = $this->app->make(DoctrineWatcher::class);
foreach (config('doctrine-watcher.watches') as $entity => $properties) {
foreach ($properties as $property => $config) {
$watcher->watch($entity, $property, $config['callback']);
}
}
Testing:
Doctrine\Common\EventManager mocks to test callbacks:
$eventManager = $this->createMock(EventManager::class);
$watcher = new DoctrineWatcher();
$eventManager->expects($this->once())
->method('addEventSubscriber')
->with($watcher);
postPersist/postUpdate:
$em = $this->getEntityManager();
$user = new User();
$user->setEmail('new@example.com');
$em->persist($user);
$em->flush(); // Triggers watcher callbacks
Performance:
$watcher->disable(); // Temporarily pause all watches
// Bulk operations...
$watcher->enable();
$watcher->unwatch(User::class, 'email');
Event Timing:
persist()/flush(), so entities are already in the database. Avoid relying on pre-save state.Property Types:
array_push/array_pop). For complex collections, implement custom logic:
$watcher->watch(
User::class,
'roles',
function ($changeset, $operationType, $user) {
if ($changeset->hasChanges()) {
$oldRoles = $changeset->getOldValue() ?? [];
$newRoles = $changeset->getNewValue() ?? [];
// Custom diff logic...
}
}
);
serialize()/unserialize() for deep comparison if needed.Configuration Overrides:
trigger_on_persist) can be set globally or per-watch. Per-watch settings override globals.trigger_when_no_changes defaults to false. Forgetting this may cause unnecessary callback invocations.EntityManager Scope:
EntityManager instances (e.g., in Symfony), register the watcher on each EventManager.User::profile if profile is lazy-loaded).PHP 8+ Issues:
$changeset and $operationType:
function ($changeset, string $operationType, User $user)
Callback Not Triggering:
EventManager:
$listeners = $em->getEventManager()->getListeners();
// Look for 'postPersist'/'postUpdate' entries with your watcher.
App\Entity\User, not User).INSERT/UPDATE) matches your watcher’s config (e.g., trigger_on_persist).Changeset Data Issues:
INSERT operations, getOldValue() may return null. Handle gracefully:
$oldValue = $changeset->getOldValue() ?? 'N/A';
hasAdditions()/hasRemovals() for arrays, not hasChanges():
if ($changeset->hasAdditions()) {
$added = $changeset->getAdditions(); // New items only
}
Performance Bottlenecks:
microtime() in callbacks to identify lag:
$start = microtime(true);
// Callback logic...
logger()->debug("Callback took " . (microtime(true) - $start) . "s");
How can I help you explore Laravel packages today?