The plugin system allows you to extend PdoDb functionality with custom macros, scopes, event listeners, and more. This enables community-driven extensions and makes it easy to add reusable functionality.
Plugins provide a clean way to package and distribute extensions to PdoDb. A plugin can:
- Register QueryBuilder macros for reusable query methods
- Register global scopes that are automatically applied to queries
- Register event listeners for monitoring and logging
- Configure any plugin-specific settings
All plugins must implement the PluginInterface:
use tommyknocker\pdodb\plugin\PluginInterface;
use tommyknocker\pdodb\PdoDb;
use tommyknocker\pdodb\query\QueryBuilder;
class MyCustomPlugin implements PluginInterface
{
public function register(PdoDb $db): void
{
// Register macros, scopes, event listeners, etc.
QueryBuilder::macro('customMethod', function (QueryBuilder $query) {
// Your custom logic
return $query;
});
}
public function getName(): string
{
return 'my-custom-plugin';
}
}For convenience, you can extend AbstractPlugin which provides a default implementation:
use tommyknocker\pdodb\plugin\AbstractPlugin;
use tommyknocker\pdodb\PdoDb;
use tommyknocker\pdodb\query\QueryBuilder;
class MyCustomPlugin extends AbstractPlugin
{
public function register(PdoDb $db): void
{
QueryBuilder::macro('customMethod', function (QueryBuilder $query) {
return $query;
});
}
// getName() defaults to class name, override if needed
public function getName(): string
{
return 'my-custom-plugin';
}
}Register a plugin with your PdoDb instance:
use tommyknocker\pdodb\PdoDb;
$db = new PdoDb('mysql', $config);
// Register plugin
$db->registerPlugin(new MyCustomPlugin());
// Plugin's register() method is called immediately
// Now you can use the plugin's featuresPlugins can register QueryBuilder macros:
class ProductMacrosPlugin extends AbstractPlugin
{
public function register(PdoDb $db): void
{
QueryBuilder::macro('active', function (QueryBuilder $query) {
return $query->where('status', 'active');
});
QueryBuilder::macro('wherePrice', function (QueryBuilder $query, string $operator, float $price) {
return $query->where('price', $price, $operator);
});
QueryBuilder::macro('recent', function (QueryBuilder $query, int $days = 7) {
$date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
return $query->where('created_at', $date, '>=');
});
}
public function getName(): string
{
return 'product-macros';
}
}
$db->registerPlugin(new ProductMacrosPlugin());
// Use macros
$products = $db->find()
->table('products')
->active()
->wherePrice('>', 100.00)
->recent(30)
->get();Plugins can register global scopes that are automatically applied to all queries:
class SoftDeletePlugin extends AbstractPlugin
{
public function register(PdoDb $db): void
{
// Automatically filter out deleted records
$db->addScope('notDeleted', function (QueryBuilder $query) {
return $query->whereNull('deleted_at');
});
}
public function getName(): string
{
return 'soft-delete';
}
}
$db->registerPlugin(new SoftDeletePlugin());
// All queries automatically exclude deleted records
$users = $db->find()->table('users')->get();Plugins can listen to database events:
use tommyknocker\pdodb\events\QueryExecutedEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
class QueryMonitorPlugin extends AbstractPlugin
{
public function register(PdoDb $db): void
{
$dispatcher = $db->getEventDispatcher();
if ($dispatcher !== null) {
$dispatcher->addListener(
QueryExecutedEvent::class,
function (QueryExecutedEvent $event) {
// Log slow queries
if ($event->getExecutionTime() > 0.1) {
error_log("Slow query: " . $event->getSql());
}
}
);
}
}
public function getName(): string
{
return 'query-monitor';
}
}
// Set up event dispatcher
$db->setEventDispatcher(new EventDispatcher());
$db->registerPlugin(new QueryMonitorPlugin());A plugin can combine macros, scopes, and event listeners:
class ECommercePlugin extends AbstractPlugin
{
public function register(PdoDb $db): void
{
// Macros
QueryBuilder::macro('featured', function (QueryBuilder $query) {
return $query->where('status', 'featured');
});
QueryBuilder::macro('inCategory', function (QueryBuilder $query, int $categoryId) {
return $query->where('category_id', $categoryId);
});
// Scopes
$db->addScope('available', function (QueryBuilder $query) {
return $query->where('status', 'active')
->whereNull('deleted_at');
});
// Event listeners
$dispatcher = $db->getEventDispatcher();
if ($dispatcher !== null) {
$dispatcher->addListener(
QueryExecutedEvent::class,
function (QueryExecutedEvent $event) {
// Custom logic
}
);
}
}
public function getName(): string
{
return 'ecommerce';
}
}if ($db->hasPlugin('my-custom-plugin')) {
// Plugin is registered
}$plugin = $db->getPlugin('my-custom-plugin');
if ($plugin !== null) {
echo "Plugin: " . $plugin->getName();
}$plugins = $db->getPlugins();
foreach ($plugins as $name => $plugin) {
echo "Registered: $name\n";
}$db->unregisterPlugin('my-custom-plugin');Note: Unregistering a plugin removes it from the registry but does not undo any changes it made (macros, scopes, and event listeners remain registered).
- Use descriptive, unique names for your plugins
- Use kebab-case (e.g.,
my-custom-plugin) - Avoid generic names that might conflict
- Keep plugins focused on a single feature or domain
- Use dependency injection when needed
- Make plugins configurable if needed
- Handle missing dependencies gracefully (e.g., event dispatcher)
- Validate inputs before registering macros or scopes
- Document any requirements or dependencies
- Test plugins in isolation
- Test plugin interactions with PdoDb
- Test plugin registration and unregistration
use tommyknocker\pdodb\plugin\AbstractPlugin;
use tommyknocker\pdodb\PdoDb;
use tommyknocker\pdodb\query\QueryBuilder;
use tommyknocker\pdodb\events\QueryExecutedEvent;
class CompletePlugin extends AbstractPlugin
{
private array $config;
public function __construct(array $config = [])
{
$this->config = $config;
}
public function register(PdoDb $db): void
{
// Register macros
QueryBuilder::macro('active', function (QueryBuilder $query) {
return $query->where('status', 'active');
});
// Register scopes
if ($this->config['enableSoftDelete'] ?? false) {
$db->addScope('notDeleted', function (QueryBuilder $query) {
return $query->whereNull('deleted_at');
});
}
// Register event listeners
$dispatcher = $db->getEventDispatcher();
if ($dispatcher !== null && ($this->config['enableLogging'] ?? false)) {
$dispatcher->addListener(
QueryExecutedEvent::class,
function (QueryExecutedEvent $event) {
// Log queries
}
);
}
}
public function getName(): string
{
return 'complete-plugin';
}
}
// Usage
$plugin = new CompletePlugin([
'enableSoftDelete' => true,
'enableLogging' => true,
]);
$db->registerPlugin($plugin);- Query Builder Macros - More about macros
- Query Scopes - More about scopes
- Examples - Working examples