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

Laminas Developer Tools Laravel Package

laminas/laminas-developer-tools

Laminas Developer Tools provides a developer toolbar and profiling collectors for Laminas MVC applications. It helps inspect requests, database queries, logs, performance metrics, and configuration during development, with integration for the Laminas ecosystem.

Deep Wiki
Context7

Getting Started

composer require --dev laminas/laminas-developer-tools

Do not add Laminas\DeveloperTools to config/modules.config.php (or application.config.php). Put it in the development-only module list so composer install --no-dev deployments can never autoload it:

// config/development.config.php
return [
    'modules' => [
        'Laminas\DeveloperTools',
    ],
    'module_listener_options' => [
        'config_glob_paths' => [
            'config/autoload/{,*.}{global,local}.php',
        ],
    ],
];

If the project uses laminas/laminas-development-mode, run composer development-enable, then composer development-status to confirm development.config.php is active. Clear any config cache (data/cache/module-config-cache.* or your equivalent) after enabling.

Where to look first:

  1. The package's own config/module.config.php — this is the authoritative list of collectors, toolbar entries, the profiler storage adapter, and the laminas-developer-tools route. Read it before overriding anything; the docs are thin and the config is the real API.
  2. src/Collector/ — each built-in collector is a small, readable template for writing your own.
  3. view/laminas-developer-tools/ — toolbar and report templates you can override via template_map.

First use case: open any HTML page in dev, click the Db entry in the bottom toolbar, and you get query count and cumulative query time for that request. Click through to the stored report (/laminas-developer-tools/profiler?…) to see the timeline, the full query list with parameters, and the event/config/session panels. That single workflow — "this page took 800 ms, why?" — is 90% of the package's day-to-day value.

Implementation Patterns

Environment gating is the primary pattern. The module should only ever be loaded in development. Treat the toolbar as a developer-only feature, and treat the report route as an internal endpoint that requires auth if it is reachable from a shared staging environment.

Tune the toolbar rather than accepting defaults. The toolbar is configured under laminas-developer-tools.toolbar with enabled, autoHide/auto_hide, position, and an entries map keyed by collector name (request, time, memory, db, event, config, mail, session). Turn off collectors you do not use in a given project — every collector runs on every request and the memory/time cost is real on query-heavy pages.

DB profiling. The DbCollector is driven by laminas-db's own Profiler attached to the adapter. Adapters resolved through the service manager are the happy path; an adapter you instantiate by hand, or a second named adapter, may not be instrumented unless you attach a profiler to it (or register it with the collector's adapter configuration). If query counts look suspiciously low, check that the adapter is the instrumented one before blaming the collector.

Custom collectors are the extension point. Implement Laminas\DeveloperTools\Collector\CollectorInterface (getName(), getLabel(), getDescription(), collect(MvcEvent $event)), register it as a service, and wire it into config:

final class CacheCollector implements CollectorInterface
{
    private array $stats = [];

    public function getName(): string { return 'cache'; }
    public function getLabel(): string { return 'Cache'; }
    public function getDescription(): string { return 'Cache hits/misses'; }

    public function collect(MvcEvent $event): void
    {
        $this->stats = $event->getApplication()
            ->getServiceManager()
            ->get('My\Cache\Stats')
            ->snapshot();
    }

    public function getStats(): array { return $this->stats; }
}
// module.config.php
'service_manager' => [
    'factories' => [
        CacheCollector::class => Laminas\ServiceManager\Factory\InvokableFactory::class,
    ],
],
'laminas-developer-tools' => [
    'collectors' => [
        'cache' => CacheCollector::class,
    ],
    'toolbar' => [
        'entries' => [
            'cache' => ['label' => 'Cache', 'tooltip' => 'Cache stats'],
        ],
    ],
],

Copy TimeCollector or MemoryCollector as the template for the toolbar entry contract; that is faster and more reliable than guessing method names. For the detail panel in the report, add or override a template under the same view namespace.

Use the EventManager for timing without writing a collector. If you only need to see how long a service or listener takes, expose it as an event and let the EventCollector surface it, or attach your own listener on MvcEvent::EVENT_FINISH that writes into a custom collector. This keeps instrumentation out of your domain code.

Custom storage. The profiler persists reports through a StorageInterface implementation (file storage by default). If you run multiple app servers or containerized dev environments, point the storage at shared storage, or accept that the report link only works on the node that served the request. Override the profiler.storage class in config if you need Redis/S3-backed profiles.

Read the report as a workflow, not a dashboard. Typical triage order: Time (where did the request go) → Db (N+1 and slow queries) → Memory (peak) → Event (listener fan-out) → Config (is the value you think is set actually set). The config panel catches more "why is this behaving differently" bugs than the profiler panels do.

Gotchas and Tips

  • It only injects into text/html responses. JSON APIs, file downloads, redirects, streamed responses, and many error pages get no toolbar. This is the most common "it's not working" report. Check response content type before debugging anything else.
  • The report route is a normal, reachable route by default (/laminas-developer-tools/...). It dumps SQL, config, session, and mail metadata. Never let the module load in production; if staging is publicly reachable, protect the route.
  • Response listener ordering matters. The toolbar is appended late in the finish/render cycle. Another listener that replaces the response content, or code that calls setContent() on EVENT_FINISH after the toolbar listener, silently removes it.
  • Full-page/view caching leaks the toolbar and stale report IDs. If a page is cached, a cached HTML response can be served with another request's toolbar markup. Disable caching for dev, or strip the toolbar block before storing cached output.
  • CLI and queue workers have no toolbar but do run listeners. Profiling in long-running workers accumulates memory and does nothing useful. Guard on PHP_SAPI === 'cli' or keep the module out of those entry points entirely.
  • Storage grows unbounded. File storage writes a profile per request. On a busy dev box or a shared staging box, that directory will fill with thousands of JSON/PHP files and the report listing gets slow. Set a TTL/cleanup, or clear the directory periodically.
  • composer install --no-dev is the failure mode to design against. If the module is referenced from the main modules.config.php and the package is not installed, you get a class-not-found fatal on every request. The dev-config pattern above prevents this; verify it once with a clean --no-dev install.
  • Memory numbers are PHP-side only. memory_get_peak_usage tells you nothing about the DB, the web server, or OPcache. Do not chase a memory "regression" that is really just a larger config or a warmer autoloader between two requests.
  • The DbCollector only understands laminas-db. Doctrine DBAL/ORM, PDO used directly, or a custom query layer will not appear. Write a collector that reads your ORM's SQL logger or a Doctrine\DBAL\Logging\SQLLogger implementation.
  • forward() and sub-requests produce multiple profiler tokens. The toolbar may show only one of them; use the report list to find the others.
  • Toolbar CSS conflicts. It is a fixed-position overlay with a high z-index. If your app has its own sticky footer or a full-screen modal, the toolbar can cover controls. Use the position/auto-hide config rather than fighting it with CSS.
  • Don't run it alongside Xdebug's profiler casually. Both add substantial overhead and both distort the timing you are trying to measure. Use one at a time, and remember that the numbers are relative, not absolute.
  • Overriding templates via template_map is safer than editing vendor. Report and toolbar templates are ordinary view scripts in the module namespace; map your own versions in view_manager.template_map so composer update does not wipe your changes.
  • Extension point checklist for a new collector: service registration → collectors entry → toolbar.entries entry → optional template map. Missing any one of these gives you a collector that runs (or doesn't) with no visible symptom.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource