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

Performance Bundle Laravel Package

nowo-tech/performance-bundle

A lightweight performance bundle for PHP applications, providing configurable optimizations to reduce overhead and improve response times. Easily integrate caching, resource handling, and runtime efficiency tweaks to keep apps faster and leaner.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, so start by treating it as one: install it with Flex, confirm what the recipe (if any) wires up, and only then look at configuration.

  1. Install and pin. With a single star and zero dependents, do not use ^ constraints blindly — install, inspect, then pin an exact version in composer.json:

    composer require nowo-tech/performance-bundle --dev
    

    The --dev flag is deliberate: collection is a development/diagnostic concern first. Promote to require only once you have a production storage strategy (see below).

  2. Confirm the registration. Flex may not have a recipe for a package this small. Check config/bundles.php for the bundle class and run bin/console debug:container --parameters | grep -i nowo plus bin/console config:dump-reference <alias> to discover the real config alias (it may be nowo_performance, nowo_tech_performance, or similar — verify rather than guess).

  3. Look at the source first, README second. With one star, src/ is the documentation. Read, in this order: the DI extension class (tells you every config key and default), the DependencyInjection/Configuration.php (tells you the config tree and defaults), the event listener/subscriber that does the timing, and the storage class. That is usually a 200–400 line read and it answers 90% of your questions faster than trial and error.

  4. First use case: "which route is slow, and is it me or the database?" Enable the bundle in dev only, hit a handful of endpoints, and look for the profiler panel or the reporting console command (bin/console list nowo / bin/console list performance to discover it). The output you want in week one is a per-route aggregate: request count, mean/p95 duration, and peak memory. That single view is where this class of bundle earns its keep — it is a route-level trend tool, not a profiler replacement.

  5. Verify the runtime contract before building on it: PHP >= 8.2 and Symfony 7/8 are the advertised targets. If you are on Symfony 6.4 LTS, check the composer.json require block on disk — it will fail fast at install time rather than at runtime, which is the good failure mode, but know it up front.

If you are coming from Laravel: this maps to Middleware + a home-grown Telescope-lite. The mental model that transfers cleanly is kernel event listener → timing → persistent row → aggregate query. Everything you'd do with DB::listen() and a requests table, you do here with a listener and a storage service.

Implementation Patterns

Listener-first, storage-second. The bundle's core value is the kernel.request / kernel.response (and usually kernel.terminate) hook that measures elapsed time per _route. Design around that: your job is to make sure those numbers are true and cheap, not to add more instrumentation.

Tiered environments. The most common real-world setup:

  • dev: full collection, human-readable output (profiler toolbar, command output). Overhead is irrelevant here; visibility is everything.
  • test: disabled. Collected rows during a test suite are pure noise and a memory sink. Gate it in config/packages/test/ or via an env-conditional enabled: false.
  • prod: sampling at a low rate (1–10%), written asynchronously, retained for days not months.

Storage selection is the whole design decision. Typical options are Doctrine ORM, PDO, or a file/log sink. Choose based on your write volume:

  • Under ~50 req/s, a Doctrine entity is fine — but give it its own table, its own index on (route, created_at), and its own retention command.
  • Above that, write-behind via Symfony Messenger: the listener pushes a small message (route, duration, status, timestamp, host), a worker persists it in batches. The listener must never block the response.
  • If the bundle exposes a storage interface/ServiceLocator-style extension point, a thin adapter that ships metrics to StatsD/InfluxDB/Prometheus is usually 50 lines and removes the retention problem entirely.

Exclude aggressively. Profiler routes, _wdt/_profiler, health checks, asset paths, /admin polling endpoints, and anything the load balancer hits every second. Otherwise your p50 is dominated by /health and the tool lies to you. Configure this via the bundle's path/route ignore list if it has one; if it doesn't, wrap the collection behind your own listener that early-returns.

Tag for attribution. A duration without a host, tenant, or deploy SHA is not actionable. If the bundle allows extra context (many do via an event or a "context provider" service), add hostname, tenant, and the release version. This is what lets you answer "did the 14:02 deploy make /checkout slower?" instead of "is /checkout slow?"

Feed a regression gate, not just a dashboard. The practical payoff is a CI check: run a fixed set of requests against a seeded database, export the metrics, and fail the build when a route's p95 drifts more than N% from a stored baseline. Treat the bundle's output as a data source; parse the command's JSON output rather than the human table.

Keep it adjacent to, not instead of, a profiler. This bundle gives you trend data across routes and time. It does not give you a call graph. Pair it with Symfony's own profiler (dev) and a real APM/sampling profiler (prod). Use this bundle to find which route regressed, then the profiler to find why.

Laravel analogue, if you're porting the pattern: replace the kernel listener with a TerminateMiddleware that measures microtime(true) deltas at handle() entry and terminate(), write to a route_metrics table, and schedule a prune command. Same shape, same pitfalls.

Gotchas and Tips

Sub-requests double-count. Symfony dispatches kernel.request for every sub-request (fragments, ESI, render(controller: ...)). If the bundle doesn't guard on $event->isMainRequest(), your route timings will be inflated and attributed to the wrong route. Test with a page containing a render(controller:) call before trusting the numbers.

The _route key is null for 404s and static files. Unmatched requests have no route attribute; asset requests served by the web server never reach PHP at all (so they're simply absent — not zero). Don't build a "total request count" dashboard on top of this data and expect it to match your nginx log.

Wall-clock time is not your user's time. kernel.request → kernel.response measures PHP. It excludes queueing in PHP-FPM, network, and client render. Under FastCGI the response is buffered; under kernel.terminate timing can be misleadingly short. Also, don't compare durations across hosts without checking clock sync (chrony/ntp) — a 2-second skew looks exactly like a performance regression.

Long-running workers pollute the stats. If you run RoadRunner, Swoole, FrankenPHP worker mode, or Messenger consumers through the same kernel, those aren't web requests and their durations are meaningless in a route-latency report. Disable collection for those entrypoints explicitly.

Doctrine storage will bite you at month three. Symptoms: a 40M-row table, autovacuum never keeping up, and dashboard queries timing out. Fixes: index (route, created_at DESC), a nightly bin/console prune command (or DELETE ... WHERE created_at < NOW() - INTERVAL '30 days' in a cron/Scheduler job), and partitioning by month if your provider makes that easy. Decide retention on day one, not after the disk alert.

Async writes need idempotency. If you go the Messenger route, a retried message will double-insert. Add a request UUID (or (host, timestamp, route) unique key) so duplicates collapse. Also accept that a killed worker loses a batch — that's fine for metrics, unacceptable if you're billing on these numbers.

Config changes need a cache clear. bin/console cache:clear after editing the bundle config, always. A stale compiled container is the most common "the sampling setting isn't working" cause, and the symptom (it silently behaves like the old config) is confusing.

Pin the version and read the diff before upgrading. One star and zero dependents means there is no crowd to hit the bugs first. Before any bump: read the changelog/UPGRADING file (if present), diff the DI extension and configuration tree, and run your own smoke test. If the repo has no UPGRADING.md and no tagged BC promise, assume breaking changes are possible in a minor release.

Budget the dev overhead. Data collectors that serialize large payloads (stack traces, SQL, dump data) into the profiler can turn a 60ms route into an 800ms route in dev with a large fixture set. If dev feels slow after installing, that's why — it is not a production signal.

Check licence and vendoring hygiene for a single-maintainer package. MIT is fine. But before it goes into a long-lived product, confirm the repository has CI, tests, and a maintainer who responds to issues. If not, the pragmatic move is to vendor the ~10 classes you actually use into src/Performance/ and drop the dependency — you'll sleep better than you will with an abandoned bundle in your kernel.

Extension points to look for (these are the ones worth preserving in any wrapper you write): a storage/persister interface to swap in your own sink, a dispatched event around the collection step so you can enrich with tenant/release context, and the ignore-list config so you can tune noise without forking. If none exist, keep your own code behind a thin interface so a future replacement is a one-file change.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log