A simple, lightweight Application Performance Monitoring (APM) library for PDO that provides detailed insights into your database operations through an event-driven architecture.
- Lightweight - Minimal overhead with no external dependencies
- Zero Configuration - Drop-in replacement for standard PDO with no configuration required
- Query Profiling - Track execution time, row counts, and parameters for all database operations
- Event-Driven Architecture - Subscribe to specific database events using a clean observer pattern
- Transaction Tracking - Monitor transaction lifecycle (begin, commit, rollback)
- Error Tracking - Capture failed queries with full context including parameters
Install via Composer:
composer require peoplepath/pdo-apm- PHP >= 8.2
- PDO extension
<?php
use PeoplePath\PdoApm\PDO;
use PeoplePath\PdoApm\Event;
use PeoplePath\PdoApm\Subscriber;
// Create PDO instance (drop-in replacement for standard PDO)
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
// Create and attach a subscriber
$profiler = new class implements
Subscriber\ExecutionStartsSubscriber,
Subscriber\ExecutionSucceededSubscriber
{
private float $startTime;
public function executionStarts(Event\ExecutionStartsEvent $event): void {
$this->startTime = microtime(true);
echo "Executing: {$event->query}\n";
}
public function executionSucceeded(Event\ExecutionSucceededEvent $event): void {
$duration = microtime(true) - $this->startTime;
echo "Completed in {$duration}s, {$event->rowCount} rows affected\n";
}
};
$pdo->addSubscriber($profiler);
// Use PDO normally - all operations are automatically tracked
$pdo->exec('CREATE TABLE users (id INT, name VARCHAR(255))');
$stmt = $pdo->prepare('INSERT INTO users VALUES (?, ?)');
$stmt->execute([1, 'Alice']);PDO APM emits the following events during database operations:
| Event | Triggered When | Properties |
|---|---|---|
ExecutionStartsEvent |
Query execution begins | query |
ExecutionSucceededEvent |
Query completes successfully | rowCount, params |
ExecutionFailedEvent |
Query fails | exception, params |
PrepareEvent |
Statement is prepared | query |
| Event | Triggered When | Properties |
|---|---|---|
TransactionBeginEvent |
Transaction starts | - |
TransactionCommitEvent |
Transaction commits | - |
TransactionRollbackEvent |
Transaction rolls back | - |
To receive events, create a subscriber by implementing one or more subscriber interfaces:
use PeoplePath\PdoApm\Subscriber;
use PeoplePath\PdoApm\Event;
class MySubscriber implements
Subscriber\ExecutionStartsSubscriber,
Subscriber\ExecutionSucceededSubscriber,
Subscriber\ExecutionFailedSubscriber,
Subscriber\PrepareSubscriber,
Subscriber\TransactionBeginSubscriber,
Subscriber\TransactionCommitSubscriber,
Subscriber\TransactionRollbackSubscriber
{
public function executionStarts(Event\ExecutionStartsEvent $event): void {
// Called when query execution starts
}
public function executionSucceeded(Event\ExecutionSucceededEvent $event): void {
// Called when query succeeds
// Access: $event->rowCount, $event->params
}
public function executionFailed(Event\ExecutionFailedEvent $event): void {
// Called when query fails
// Access: $event->exception, $event->params
}
public function prepare(Event\PrepareEvent $event): void {
// Called when statement is prepared
// Access: $event->query
}
public function transactionBegin(Event\TransactionBeginEvent $event): void {
// Called when transaction begins
}
public function transactionCommit(Event\TransactionCommitEvent $event): void {
// Called when transaction commits
}
public function transactionRollback(Event\TransactionRollbackEvent $event): void {
// Called when transaction rolls back
}
}You only need to implement the subscriber interfaces for events you're interested in.
use PeoplePath\PdoApm\PDO;
use PeoplePath\PdoApm\Event;
use PeoplePath\PdoApm\Subscriber;
class QueryLogger implements
Subscriber\ExecutionStartsSubscriber,
Subscriber\ExecutionSucceededSubscriber
{
private array $queries = [];
private ?float $startTime = null;
public function executionStarts(Event\ExecutionStartsEvent $event): void {
$this->startTime = microtime(true);
}
public function executionSucceeded(Event\ExecutionSucceededEvent $event): void {
$this->queries[] = [
'duration' => microtime(true) - $this->startTime,
'rows' => $event->rowCount,
'params' => $event->params,
];
}
public function getQueries(): array {
return $this->queries;
}
}
$pdo = new PDO('sqlite::memory:');
$logger = new QueryLogger();
$pdo->addSubscriber($logger);
// Execute queries
$pdo->exec('CREATE TABLE test (id INT)');
$stmt = $pdo->prepare('INSERT INTO test VALUES (?)');
$stmt->execute([1]);
// Review profiling data
print_r($logger->getQueries());use PeoplePath\PdoApm\PDO;
use PeoplePath\PdoApm\Event;
use PeoplePath\PdoApm\Subscriber;
class SlowQueryDetector implements
Subscriber\ExecutionStartsSubscriber,
Subscriber\ExecutionSucceededSubscriber
{
private float $startTime;
private string $currentQuery;
public function __construct(
private float $thresholdSeconds = 1.0
) {}
public function executionStarts(Event\ExecutionStartsEvent $event): void {
$this->startTime = microtime(true);
$this->currentQuery = $event->query;
}
public function executionSucceeded(Event\ExecutionSucceededEvent $event): void {
$duration = microtime(true) - $this->startTime;
if ($duration > $this->thresholdSeconds) {
error_log(sprintf(
"Slow query detected (%.2fs): %s",
$duration,
$this->currentQuery
));
}
}
}
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$pdo->addSubscriber(new SlowQueryDetector(thresholdSeconds: 0.5));use PeoplePath\PdoApm\PDO;
use PeoplePath\PdoApm\Event;
use PeoplePath\PdoApm\Subscriber;
class ErrorTracker implements Subscriber\ExecutionFailedSubscriber
{
public function executionFailed(Event\ExecutionFailedEvent $event): void {
error_log(sprintf(
"Query failed: %s\nParameters: %s\nError: %s",
$event->exception->getMessage(),
json_encode($event->params),
$event->exception->getTraceAsString()
));
// Send to error tracking service
// $this->sentryClient->captureException($event->exception);
}
}
$pdo = new PDO('sqlite::memory:');
$pdo->addSubscriber(new ErrorTracker());use PeoplePath\PdoApm\PDO;
use PeoplePath\PdoApm\Event;
use PeoplePath\PdoApm\Subscriber;
class TransactionMonitor implements
Subscriber\TransactionBeginSubscriber,
Subscriber\TransactionCommitSubscriber,
Subscriber\TransactionRollbackSubscriber
{
private int $activeTransactions = 0;
public function transactionBegin(Event\TransactionBeginEvent $event): void {
$this->activeTransactions++;
echo "Transaction started (active: {$this->activeTransactions})\n";
}
public function transactionCommit(Event\TransactionCommitEvent $event): void {
$this->activeTransactions--;
echo "Transaction committed\n";
}
public function transactionRollback(Event\TransactionRollbackEvent $event): void {
$this->activeTransactions--;
echo "Transaction rolled back\n";
}
}
$pdo = new PDO('sqlite::memory:');
$pdo->addSubscriber(new TransactionMonitor());
$pdo->beginTransaction();
// ... queries ...
$pdo->commit();See examples/query-profiler.php for a fully-featured query profiling implementation with detailed reporting.
Run the example:
php examples/query-profiler.phpExtends \PDO with event notification capabilities.
addSubscriber(Subscriber $subscriber): void- Register an event subscribernotifySubscribers(Event $event): void- Manually notify all subscribers of an event
All standard PDO methods work exactly as expected.
string $query- The SQL query being executed
int $rowCount- Number of rows affected?array $params- Parameters passed to the query (if any)
PDOException $exception- The exception that was thrown?array $params- Parameters passed to the query (if any)
string $query- The SQL query being prepared
Contributions are welcome! Please feel free to submit a Pull Request.
This library is licensed under the MIT License. See LICENSE for details.
- Ondřej Ešler - ondrej.esler@peoplepath.com
- Performance Monitoring - Track query execution times in production
- Development Debugging - Log all queries during development
- Query Optimization - Identify slow queries and N+1 problems
- Error Tracking - Capture and report database errors
- Audit Logging - Track all database operations for compliance
- Testing - Verify database interactions in unit tests
- Metrics Collection - Gather database statistics for dashboards
- Non-invasive: Drop-in replacement for PDO with zero configuration
- Flexible: Subscribe only to events you need
- Performant: Minimal overhead when no subscribers are attached
- Type-safe: Full PHP 8.4+ type hints for better IDE support
- Well-tested: Comprehensive test suite ensures reliability
- Framework-agnostic: Works with any PHP application or framework
Made with ❤️ by PeoplePath