link0/profiler
link0/profiler is a Laravel/PHP profiler package for inspecting application performance and debugging during development. It helps surface request timings, database queries, and runtime details so you can identify bottlenecks and optimize your app.
The package is a thin profiling wrapper: it starts/stops an XHProf (or uprofiler) run, collects the raw profile data, and hands it to a persistence layer (file, DB, etc.) so you can inspect it later. Your first step is confirming the runtime dependency exists, because nothing in the package works without it.
xhprof/uprofiler, so verify one is available:
php -m | grep -i xhprof # or uprofiler
On modern PHP you'll almost certainly need tideways_xhprof (the maintained fork) rather than the abandoned PECL xhprof. Decide that before you composer require link0/profiler.composer require link0/profiler
start()/stop() (or enable()/disable()) method names — the API is small and stable, so a few minutes in the source saves guesswork.A minimal script to prove the pipeline end-to-end:
$profiler = new \Link0\Profiler\Profiler($config);
$profiler->start('checkout-flow');
// ... work under examination ...
$profiler->stop(); // persists the run via the configured adapter
If that produces a readable profile, you're past the hard part; everything else is selection and workflow.
Sample, don't blanket-profile. Profiling has real overhead and profile artifacts are large. The durable pattern is to profile selectively: a query-string flag, a header, or a profile environment toggle that only activates on staging. Keep the call site identical across environments and let config decide whether data is actually written.
Middleware for web requests. The most common integration is a middleware that starts the profiler on request entry and stops it before the response leaves, tagging the run with route, method, and a correlation ID. This gives you per-request profiles without editing controllers. Guard it so /health, assets, and admin routes are excluded.
Storage as a swappable concern. The package's value is the persistence abstraction — pick a file adapter locally and a DB/Redis adapter for shared or team environments. Treat the storage choice as deployment config, not code. Wrap the adapter in your own factory so you can add retention/cleanup logic without touching vendor code.
Name your runs meaningfully. Use run labels that encode context — route, user tier, feature flag, commit SHA. Profiles are useless in aggregate if every run is named profile. A convention like {route}:{sha}:{flag} makes later comparison possible.
Profile targeted blocks for algorithm work. For a suspected N+1 or hot loop, wrap just that block:
$profiler->start('cart-recalc');
$this->recalculateCart($cart);
$profiler->stop();
This keeps artifacts small and makes the diff between "before" and "after" obvious.
Workflow: capture → compare → verify. Capture a baseline profile, make one change, capture again, and diff. Keep the persistence layer long-lived enough to compare across a release window, then prune aggressively. If the package doesn't ship a diff tool, export the raw profile and use the standard XHProf UI / a flamegraph tool against the same format.
CI gate (optional but powerful). Point the file adapter at a CI artifact directory, run a smoke test that exercises a critical path, and publish the profile alongside test results. It won't gate on wall-clock (too noisy), but it makes regressions visible in review.
Extension mismatch is the #1 failure. xhprof, uprofiler, and tideways_xhprof produce similar but not identical output and have different function names (xhprof_enable vs tideways_xhprof_enable). The package description mentions XHProf or uprofiler — confirm which driver path it actually selects on your PHP version, and don't mix a tideways_xhprof install with the XHProf driver. A silent no-op (no profile file, no error) is the usual symptom.
"Enabled" is not "profiled." Many libraries silently skip when no extension is present. Add an explicit assertion in your bootstrap that the profiler reports an available driver, so a missing extension fails loudly on staging instead of quietly producing empty reports.
Watch overhead and memory. XHProf holds a call tree in memory; deep recursion or millions of calls can balloon the process. Cap it with the extension's own options (e.g. ignore functions, max depth) and avoid profiling hot loops in production. Treat any always-on production profiling as a deliberate, sampled decision — not a default.
Storage grows fast. File adapters never clean up. Plan a retention policy from day one: a cron/Artisan command that deletes artifacts older than N days, or a TTL on the DB/Redis adapter. Also watch permissions — the web/PHP-FPM user must be able to write the storage directory.
Naming and nesting discipline. If your code starts and stops the profiler recursively, ensure the package's stop() semantics match your expectation (last-stop wins vs. stack). Otherwise nested runs collapse into one confusing artifact. Keep a single owner of start/stop per request where possible.
Laravel config cache. If a config file is published, remember php artisan config:cache freezes profiling settings. Toggling profiling via env vars in a cached-config deploy will appear to do nothing until you clear and rebuild the cache. Prefer a runtime gate (middleware check) over env-only toggles.
Don't ship the viewer to production. The report UI and raw profile endpoints can leak internal paths, SQL, and call graphs. Restrict to local/staging, behind auth, or gate on an environment check.
Extension points are thin — own the glue. This is a small, single-purpose library. Put your own factory/binding around the profiler and storage adapters so upgrades don't ripple through call sites, and so adding a new backend or a sampling policy is a one-file change. Pin the version in composer.json; a package this small with 14 stars won't have a large maintenance buffer, so be prepared to fork or vendor it if a PHP upgrade breaks the driver.
How can I help you explore Laravel packages today?