benjamin-rqt / data-watcher-bundle
Symfony bundle for database anomaly detection with email notifications
Package info
github.com/BenjaminRqt/data-watcher-bundle
Type:symfony-bundle
pkg:composer/benjamin-rqt/data-watcher-bundle
0.0.1
2026-06-03 14:53 UTC
Requires
- php: >=8.2
- doctrine/dbal: ^3.0|^4.0
- doctrine/orm: ^2.15|^3.0
- dragonmantank/cron-expression: ^3.0
- symfony/framework-bundle: ^6.3|^7.0|^8.0
- symfony/mailer: ^6.3|^7.0|^8.0
- symfony/messenger: ^6.3|^7.0|^8.0
- symfony/scheduler: ^6.3|^7.0|^8.0
- symfony/twig-bundle: ^6.3|^7.0|^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- phpstan/phpstan: ^2.1
- symfony/phpunit-bridge: ^6.3|^7.0|^8.0
This package is not auto-updated.
Last update: 2026-06-04 13:16:34 UTC
README
Symfony bundle for database anomaly detection with email notifications and execution history.
Requirements
- PHP >= 8.2
- Symfony 6.3, 7.0 or 8.0
Symfony components
- framework-bundle
- messenger
- mailer
- twig-bundle
- scheduler
Doctrine
- doctrine/orm ^2.15 or ^3.0
- doctrine/dbal ^3.0 or ^4.0
External dependencies
- dragonmantank/cron-expression (required for cron-based scheduler triggers)
Installation
1. Declare the bundle in composer.json
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/BenjaminRqt/data-watcher-bundle"
}
]
}
2. Install
composer require benjamin-rqt/data-watcher-bundle
3. Register the bundle (if Flex doesn't do it automatically)
// config/bundles.php return [ // ... BenjaminRqt\DataWatcherBundle\DataWatcherBundle::class => ['all' => true], ];
4. Create the bundle configuration
# config/packages/data_watcher.yaml data_watcher: from_email: 'datawatcher@myapp.com' recipients: - 'admin@myapp.com' app_name: 'My Application' # Optional, used in email subjects and templates
5. Configure Messenger for the scheduler
# config/packages/messenger.yaml framework: messenger: transports: scheduler_data_watcher: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' routing: BenjaminRqt\DataWatcherBundle\Scheduler\Message\RunCheckMessage: scheduler_data_watcher
6. Environment variables (.env.local)
MAILER_DSN=smtp://user:password@smtp.myserver.com:587 MESSENGER_TRANSPORT_DSN=doctrine://default
7. Apply migrations
php bin/console doctrine:migrations:migrate
Add a check in your application
Create a class in your app (not in the bundle):
With raw SQL
<?php namespace App\DataWatcher; use BenjaminRqt\DataWatcherBundle\Check\AbstractSqlCheck; final class MyCheck extends AbstractSqlCheck { public function getName(): string { return 'my.sql.check'; } public function getDescription(): string { return 'Detects X'; } public function getSchedule(): string { return '0 9 * * 1-5'; } // Mon-Fri at 9:00 AM protected function getSql(): string { return "SELECT id, name FROM my_table WHERE problem = 1"; } }
With Doctrine ORM
use BenjaminRqt\DataWatcherBundle\Check\AbstractDoctrineCheck; use Doctrine\ORM\Query; final class MyDoctrineCheck extends AbstractDoctrineCheck { public function getName(): string { return 'my.doctrine.check'; } public function getDescription(): string { return 'Detects Y via ORM'; } public function getSchedule(): string { return '@daily'; } protected function buildQuery(): Query { return $this->em->getRepository(User::class) ->createQueryBuilder('u') ->select('u.id') ->where('u.email LIKE :s') ->setParameter('s', '%admin%') ->getQuery(); } }
With a callable
use BenjaminRqt\DataWatcherBundle\Check\AbstractCallableCheck; final class MyCallableCheck extends AbstractCallableCheck { public function __construct(private readonly MyCustomCallableHandler $callableHandler) {} public function getName(): string { return 'my.callable.check'; } public function getDescription(): string { return 'Detects Z via callable'; } public function getSchedule(): string { return '@daily'; } protected function getCallable(): ArrayCallableInterface { return $this->callableHandler; } }
Then declare the handler containing the callable and the detection logic:
use BenjaminRqt\DataWatcherBundle\Check\ArrayCallableInterface; final class CallableHandler implements ArrayCallableInterface { public function __invoke(): array { return [ ['id' => 42], ]; } }
Other possible configurations
Increase the number of anomalies to trigger an alert
public function getMaxAnomalies(): int { return 5; // 1 by default, here at least 5 results are needed to send an alert }
Specific recipients for a check
public function getRecipients(): array { return ['manager@myapp.com']; // overrides global config }
Available commands
# List all registered checks php bin/console data-watcher:run --list # Test without email or history php bin/console data-watcher:run my.check --dry-run # Run a check (email + history) php bin/console data-watcher:run my.check # Run all checks php bin/console data-watcher:run
Start the worker (scheduler)
composer require symfony/scheduler
# Development php bin/console messenger:consume scheduler_data_watcher -vv # Production (with Supervisor or systemd) php bin/console messenger:consume scheduler_data_watcher --time-limit=3600
No configuration is required for the scheduler, it automatically detects CRON expressions defined in checks and executes them at the right time.
Quick CRON expressions
| Expression | Meaning |
|---|---|
0 8 * * * |
Every day at 8:00 AM |
*/30 * * * * |
Every 30 minutes |
0 9 * * 1-5 |
Mon–Fri at 9:00 AM |
@daily |
Once a day |
@hourly |
Once an hour |