Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Trigger Mapping Laravel Package

talleu/trigger-mapping

Trigger Mapping is a Laravel package for defining and managing database trigger mappings with a clean, fluent API. It helps you connect database events to application logic, keeping trigger definitions organized, readable, and maintainable.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle wires database-level triggers back into Symfony services, so your first job is proving the loop end-to-end on one table before scaling out.

1. Install and register

composer require talleu/trigger-mapping
// config/bundles.php
return [
    // ...
    Talleu\TriggerMappingBundle\TriggerMappingBundle::class => ['all' => true],
];

2. Look at the bundle's own README first, then bin/console list trigger — command names are the fastest way to understand the workflow (generate, consume, etc.). Run bin/console debug:config trigger_mapping to confirm the bundle sees your config.

3. Define your first mapping. The typical shape is a YAML config binding an entity/table + operation to a PHP class:

# config/packages/trigger_mapping.yaml
trigger_mapping:
    triggers:
        App\Entity\Order:
            insert: App\Trigger\OrderCreatedTrigger
            update: App\Trigger\OrderUpdatedTrigger
#[TriggerMapper(entity: 'orders', operation: 'insert')]
final class OrderCreatedTrigger implements TriggerInterface
{
    public function __construct(private MailerInterface $mailer) {}

    public function handle(array $row): void
    {
        // $row contains the affected row's columns at time of trigger
        $this->mailer->send(/* ... */);
    }
}

4. Generate the SQL triggers — this is the step that actually creates CREATE TRIGGER statements in your schema. Run the bundle's generate command after any migration that alters a mapped table.

5. Start a consumer to poll the event table and dispatch to your classes:

bin/console trigger-mapping:consume   # confirm exact name via `list trigger-mapping`

First compelling use case: reacting to rows inserted directly in SQL (seeders, admin tools, external services, DBA patches) — the exact case Symfony's Doctrine event listeners can never see. Once that works, you'll know whether the bundle fits your stack.

Implementation Patterns

One trigger class per operation, not per entity. An OrderCreatedTrigger, OrderUpdatedTrigger, OrderCancelledTrigger trio is easier to test, deploy, and reason about than a single OrderTrigger switching on $row['operation']. Your mapping config then acts as a routing table you can read at a glance.

Delegation pattern: thin handler, real work in a message. The consumer is a CLI process that should be fast and restartable. Do the minimal amount of work inside handle() — construct a command and dispatch it on the Messenger bus:

public function handle(array $row): void
{
    $this->bus->dispatch(new SyncCustomerToErp($row['id']));
}

This gives you retries, dead-letter handling, and worker scaling for free, and keeps the trigger consumer's code tiny if the package ever needs to be swapped out.

Idempotency keys from the event row. Triggers can fire more than once in failure scenarios (consumer crash after dispatch but before ack). Derive a deterministic key from the event row's primary key + timestamp column and store a "processed" marker, or use it as a message deduplication key. Do not rely on the trigger firing exactly once.

Integration point for audit logs / search indexing. These are the natural fits: you want to write to an audit_log table or push rows to Elasticsearch regardless of which application mutated the source row. Triggers are the only reliable hook for cross-application data changes.

Fan-out with a small internal router. When you need multiple side effects for one operation, one trigger class that dispatches N messages scales better than N SQL triggers, because you keep N=1 polling load and one payload.

Testing: map your trigger classes normally, then call handle() directly in a unit test with a fake $row array. There is no reason to involve MySQL in unit tests; reserve integration tests for "SQL insert → row appears in the event table → consumer picks it up" as a single end-to-end smoke test.

Deployment / CI: add the "generate triggers" command to your deploy pipeline right after doctrine:migrations:migrate. If you forget, the mapping config changes but the database keeps firing the old PHP handler — a very confusing failure mode.

Gotchas and Tips

Polling, not push — so latency is a design parameter. The consumer polls the event table on an interval. If you need sub-second reactions, this is the wrong tool; if you need "within a minute" reactions to external DB writes, it's ideal. Document the expected latency for consumers of your feature.

Supervise the consumer like any daemon. Run it under supervisor / systemd / Messenger's messenger:consume-style supervision. It will die on unhandled exceptions; without a supervisor, your triggers silently stop being processed. Add an alert on "event table row count growing without bound".

Regenerate triggers whenever the mapped table's schema changes. Adding or renaming a column in a mapped table can silently break the trigger's INSERT INTO trigger_event (...) payload. Diff SHOW CREATE TRIGGER before/after migrations in staging if you touch a mapped table.

Watch out for trigger recursion. If your handler writes back to a table that itself has triggers, you can create feedback loops. Keep handler writes to a different table, or add a guard flag / "origin" marker in the payload.

MySQL-only realistically. The bundle relies on specific trigger semantics (NEW/OLD row references). If your project uses Postgres or supports multiple RDBMS, treat this as a MySQL-only decision and isolate it behind a service interface so you can swap in PG LISTEN/NOTIFY or Debezium CDC later.

No transaction atomicity with the original change. By the time your PHP runs, the originating transaction has long since committed (or not — in which case you'll never see the event). Handlers must tolerate rows that no longer exist, or that have already changed again. Always SELECT for the current state; never trust the payload as authoritative.

Payload serialization limits. Event payloads are DB columns (usually JSON/TEXT). Large TEXT columns, blobs, or JSON with non-UTF8 bytes can fail to serialize. Stick to scalar columns and let the handler re-fetch the full row if needed.

Debugging: query the event table directly.

bin/console dbal:run-sql "SELECT * FROM trigger_event ORDER BY id DESC LIMIT 20"

If the table is empty after an insert, the trigger was never installed (or you're inserting into a different schema). If it's full but nothing happens, the consumer isn't running or is crashing — check its logs before touching the handler code.

Config cache quirks. Mapping changes require cache:clear in prod; the trigger generation reads the compiled container. A "works locally, doesn't work on deploy" issue is almost always stale cache or missed generation step.

Maturity caution (93 stars, 0 dependents). Treat the bundle as a thin opinionated wrapper rather than a hardened queue. Build your own observability around the event table (row counts, processing lag, dead-letter table), and keep trigger handler interfaces narrow so a future swap for a CDC solution (Debezium, PG logical replication, Symfony Messenger + Doctrine listeners) is a one-file change rather than a rewrite.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource