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

Tracker Bundle Laravel Package

kronhyx/tracker-bundle

kronhyx/tracker-bundle is a PHP package for adding activity tracking to Laravel applications. It provides a configurable tracker bundle for recording events and application activity, helping developers integrate simple tracking and logging into their projects.

View on GitHub
Deep Wiki
Context7

Getting Started

First, verify the framework. Despite being pitched alongside Laravel packages, kronhyx/tracker-bundle is a Symfony bundle (Kronhyx/TrackerBundle). It is registered in config/bundles.php and wired through Symfony's DI container and event dispatcher, and its "track entity changes" capability almost certainly hangs off Doctrine ORM lifecycle events (prePersist, preUpdate, onFlush, postFlush). In a Laravel codebase there is no drop-in install; read Getting Started as "how to evaluate and what your minimum path looks like," and see Implementation Patterns for the Laravel translation.

If you are on Symfony + Doctrine (the only native fit):

  1. Read the source before installing. It is a small package — an afternoon. Confirm:
    • composer.json require block: PHP floor, symfony/* floor, doctrine/orm floor.
    • Where the subscriber attaches (doctrine.event_subscriber / doctrine.event_listener in services.yaml).
    • The storage target (same DB table? files? bus?) and whether a schema/migration is shipped.
    • Whether the write is synchronous inside the flush or deferred to postFlush / a message.
  2. Install: composer require kronhyx/tracker-bundle, register Kronhyx\TrackerBundle\TrackerBundle::class => ['all' => true] in config/bundles.php.
  3. Look first at the bundle's Resources/config/services.yaml (or config/) — this is where the entity list, storage driver, and event wiring are configured. That file is the real API surface.
  4. First use case: one non-critical entity as a canary. Pick something like a Note or SupportTicket entity with few fields and no relations. Do not start with User, Order, or anything payment-adjacent — audit tables built on entities containing PII create a second data store with its own retention and erasure obligations.
  5. Create a row, update a field, delete the row. Then query the audit table directly and confirm: actor captured, old→new diff correct, timestamps sensible, deletes recorded. Then deliberately break it (throw inside the audit write) and decide whether the business write should roll back — see Gotchas.

If you are on Laravel: treat this repository as prior art, not a dependency. Skim the Doctrine onFlush diff logic to understand the shape of the problem, then implement with spatie/laravel-activitylog (activity-feed oriented) or owen-it/laravel-auditing (audit-log oriented), or a ~150-line set of Eloquent observers if your needs are narrow. A first use case in Laravel: php artisan make:observer OrderObserver --model=Order and log getOriginal() vs getChanges() into an audits table.

Implementation Patterns

Pattern 1 — Symfony: global doctrine subscriber with an opt-in entity allowlist. Do not enable tracking for every entity. Configure an explicit list (or an interface, e.g. TrackableInterface, implemented by entities that should be audited). Blanket tracking on a mature schema means an audit table that clones your whole database at 5–10× write volume.

# config/packages/tracker.yaml  (verify key names against the bundle source)
tracker:
    enabled: true
    entities:
        - App\Entity\SupportTicket
        - App\Entity\Account
    ignore_fields: ['updatedAt', 'updated_at', 'version']

Pattern 2 — Symfony: transaction-aware write ordering. The classic Doctrine pitfall is writing audit rows during onFlush while the entity diff is still mutable. Compute the diff in onFlush (that's where $uow->getEntityChangeSet() is valid), then persist the audit rows in onFlush via $uow->computeChangeSet() on the audit entity — or defer to postFlush / a message for lower coupling. Whether this bundle does either is the single most important thing your source read should establish.

Pattern 3 — Symfony: actor context from a request-scoped provider, with an explicit fallback. Audit rows need user_id. In HTTP this comes from TokenStorageInterface. In a messenger:consume worker or a console command it is null, and a null actor is materially different from system. Push actor context onto the queue message (e.g. a StampsAuditActor message stamp) rather than trying to read the token in the consumer.

Pattern 4 — Laravel translation: Eloquent observer + queued write.

// app/Observers/AuditObserver.php
public function updated($model): void
{
    $changes = collect($model->getChanges())
        ->except(['updated_at'])
        ->map(fn ($new, $field) => [
            'field' => $field,
            'old'   => $model->getOriginal($field),
            'new'   => $new,
        ])->values();

    if ($changes->isEmpty()) {
        return;
    }

    AuditLog::create([   // or dispatch(new WriteAuditLog(...)) if async is acceptable
        'auditable_type' => $model::class,
        'auditable_id'   => $model->getKey(),
        'actor_id'       => auth()->id(),          // null in jobs/commands — be explicit
        'changes'        => $changes,
        'created_at'     => now(),
    ]);
}

Register per-model (Order::observe(AuditObserver::class)) rather than globally; global observation in Laravel is a footgun for seeding, imports, and migrations.

Pattern 5 — Read side: build the timeline query you actually want. The package captures writes; the value is in the detail-page timeline. Design the read query first (where auditable_type/auditable_id order by id desc limit 50, indexed on (auditable_type, auditable_id, id)). If the bundle ships no query API, you will write it yourself — budget for that.

Workflow tip: put tracking behind a feature flag per entity. Roll out one entity at a time over a couple of weeks and compare row counts against write counts (SELECT COUNT(*) FROM audits WHERE ... vs. your app's mutation logs). Gaps you can't explain are the failure mode that matters for a compliance-adjacent subsystem.

Gotchas and Tips

  • Framework mismatch is the top-line gotcha. For a Laravel codebase this is a port project, not an install: Doctrine UnitOfWork diffing → Eloquent getChanges()/getOriginal(), EventSubscriber → observers, services.yaml → service provider bindings. Estimate 2–3 engineer-days for a focused spike; if that estimate is uncomfortable, you are better served by a maintained alternative.
  • Zero stars, zero dependents = zero evidence. No issue history means no known-bug list, no upgrade guidance, and no community answer when a relation diff silently records as null. Plan to fork under your own namespace and own it; the MIT license permits it, but the cost lands on your team. Assume it has been tested against exactly one Symfony/Doctrine combination.
  • Silent drop is worse than no logging. If the audit write fails and the business write commits, your audit trail has gaps you don't know about — which is more dangerous during incident review than having no trail at all. Deliberately test: throw an exception inside the audit path in a test suite and assert the behaviour you want. Pick a posture — hard-fail the transaction (availability cost) or write to a dead-letter table (correctness cost) — and make it explicit.
  • flush() inside a listener will bite you. Calling $em->flush() from within onFlush recurses. Use $uow->computeChangeSet(), or defer to postFlush/KernelEvents::TERMINATE/a message. If the bundle flushes in onFlush, that's a red flag.
  • Collections and relations. Doctrine's getEntityChangeSet() only sees owning-side scalar changes. OneToMany additions/removals and inverse-side updates typically do not appear. Verify against your domain: an Order gaining OrderLines is often the most interesting audit event and the most likely to be missed.
  • Ignore the noise fields. Exclude updated_at, version/@Version columns, and any last_seen_at-style telemetry columns, or you will get an audit row for every touch and your timeline becomes useless. Note that Doctrine's @Version bump will otherwise appear as a "change."
  • Deserialization drift. Storing ['status' => 'draft'] as JSON means historical rows break when you rename or remove a field. Prefer storing raw old/new values keyed by field name and rendering in the UI, not pre-rendered strings. Version your audit payload shape (schema_version: 1) — it costs nothing now.
  • PII / GDPR. Audit tables become a second copy of personal data. Redact at write time (ignore_fields on email, phone, national_id, password_hash) rather than relying on a future erasure job. "Right to erasure" must now also delete from the audit table — decide whether that destroys your audit-integrity story before you build it.
  • Storage growth outpaces source tables. Budget retention and partitioning up front: an audit table typically grows several-fold faster than the tables it tracks. For MySQL, plan range partitioning by month; for Postgres, pg_partman. If the bundle has no retention hook, add a scheduled prune job.
  • Actor misattribution in async contexts. TokenStorage is empty in workers, console commands, and onKernelTerminate after the session closes. Every audit row written from a queue will show null unless you explicitly carry the actor. This is the most common "why does the audit log look wrong" bug.
  • Extension points to look for. A storage/driver interface (to swap the audit table for a separate store or a message bus), a serializer, and an ignore-list of fields. If those are hard-coded rather than injected, the practical path is to fork — you cannot scale a synchronous per-write insert on a hot table without an async escape hatch.
  • Debugging tip: start with the SQL, not the PHP. Enable the Doctrine SQL logger, perform one update, and read the generated INSERT into the audit table. That tells you in ten seconds what the actor, diff, and transaction boundary actually are — faster than reading the bundle source.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package