totten/lurkerlite
LurkerLite is a lightweight Laravel package for logging and inspecting HTTP requests. It captures incoming traffic for debugging and monitoring with minimal setup and overhead, making it a handy alternative when full debugging suites are overkill. Review payloads, headers, and responses during devel...
Lurker is not a Laravel package — it is a framework-agnostic resource-tracking library. It introspects a SQL schema, derives the foreign-key graph between tables, and lets you walk that graph from a root record (find every row that refers to it, and recursively everything that refers to those). The lite build is the dependency-light variant (plain PDO rather than a full ORM/DBAL stack), which is why it shows up inside larger CMS projects rather than standalone apps.
Minimal setup:
composer require totten/lurkerlite
First three things to read, in order — the docs are thin, so the code is the documentation:
composer.json → the autoload block, to confirm the exact namespace (Lurker\... vs. a global Lurker class). Guessing this is the #1 reason a first attempt fatals.README.md → the canonical short example of constructing the tracker and passing it a connection.tests/ → the real API contract. Copy a test that builds a small two-table fixture; it will show you how schema discovery is triggered and what the traversal returns.Smoke test outside Laravel first:
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE team (id INTEGER PRIMARY KEY)');
$pdo->exec('CREATE TABLE player (id INTEGER PRIMARY KEY, team_id INTEGER REFERENCES team(id))');
$lurker = new Lurker(/* connection or schema, per README */);
// 1. Prove the graph is visible before you trust it
print_r($lurker->getReferrers(['team', 'id' => 1])); // expect: player.team_id
Then wire it into Laravel in a service provider and bind it as a singleton (see below). First real use case: "give me everything that would break / everything I'd have to delete if I removed row X" — impact analysis and GDPR-style data export are the two things this library is genuinely good at, and they are painful to hand-roll.
1. Bind the raw PDO, not the Eloquent connection. Lurker wants a PDO handle and does its own introspection. Laravel's connection wrapper will confuse it.
// app/Providers/LurkerServiceProvider.php
public function register(): void
{
$this->app->singleton(Lurker::class, function ($app) {
return new Lurker($app['db']->connection()->getPdo());
});
}
Make it a singleton so schema introspection is paid once per request, and forgetInstance() in tests when you rebuild the database.
2. Cascade delete by walking referrers, children first. The traversal gives you the dependency order; reverse it for deletes.
DB::transaction(function () use ($lurker, $root) {
$graph = $lurker->getReferrers($root); // table => [ids], dependency-ordered
foreach (array_reverse($graph, true) as $table => $ids) {
DB::table($table)->whereIn('id', $ids)->delete();
}
DB::table($root['table'])->where('id', $root['id'])->delete();
});
Keep the whole thing inside one transaction and delete in reverse topological order — doing it the other way round trips FK constraints halfway through and leaves a half-deleted graph.
3. Data export / subject-access requests. Same graph, different verb: serialize table => rows to JSON instead of deleting. This is the pattern where Lurker earns its keep, because the alternative is a hand-maintained list of forty tables that goes stale the moment someone adds a migration.
4. Listeners/callbacks for auditing. Rather than pulling the whole graph into memory, register a listener that fires per discovered record and writes to an audit log or emits an event. This keeps memory flat on wide graphs and gives you a natural place to enforce per-table authorization:
$lurker->addListener('*', function ($record) use ($user) {
abort_unless($user->can('view', $record), 403);
});
5. Queue the heavy traversals. A graph walk is N queries per table per level. For anything beyond a handful of hops, dispatch a job and stream results to a file/table rather than trying to return it from a controller.
6. Guard it behind your own interface. Because the package is tiny and unmaintained-looking, define App\Contracts\ResourceGraph with referrers() / dependents() and adapt Lurker behind it. Swapping to a hand-written recursive-CTE implementation later then costs you one class, not a codebase-wide refactor.
7. Test fixtures. Build the schema in sqlite::memory: in a test setUp() with real REFERENCES clauses — this both tests your integration and documents which edges Lurker is expected to infer.
user_id columns with no ->constrained(). Lurker will silently skip those edges, and a "deep delete" will leave orphans that no error message warns you about. Audit information_schema for FK coverage before you trust any destructive use. The same blind spot applies to polymorphic (commentable_id/commentable_type) and pivot tables with composite keys.getReferrers() for a known row in a scratch database first. "The library deleted the wrong thing" is almost always "the library never saw that edge."migrate, or bind it as scoped rather than singleton under Octane.ON DELETE CASCADE double-deletes. If your schema already cascades, Lurker's explicit child deletes are redundant but harmless — except when listeners/audit hooks fire, in which case you log rows that the DB was about to remove anyway. Decide which layer owns deletion and disable the other.deleted_at. A "cascade" through soft-deleted children will hard-delete them. Translate to update(['deleted_at' => now()]) explicitly if that's your model.WHERE fk IN (...) per level; unindexed FKs turn a 3-hop walk into a table-scan storm. This is the single biggest performance lever.fetchAll() first.foo_id conventions, snake_case tables. UUID/ULID keys and nonstandard PK names are where you should expect to hand-hold it or override.composer.json, vendor the tests you rely on, and don't put it on the critical path of anything you can't afford to replace. It is a good reference implementation of schema-graph traversal even if you end up not shipping it — reading its introspection code is a fast way to learn how to do this yourself with information_schema and a recursive CTE.How can I help you explore Laravel packages today?