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

Httpkernel Adapter Laravel Package

php-pm/httpkernel-adapter

HttpKernel adapter for PHP-PM, letting Symfony and other HttpKernel applications run on a persistent worker process. It bootstraps the kernel once per worker and dispatches requests efficiently, reducing per-request overhead and improving performance.

View on GitHub
Deep Wiki
Context7

Getting Started

The adapter is inert on its own — it only provides the HttpKernel bridge plus the Symfony/Laravel bootstraps that PHP-PM's worker pool needs. Install it next to the core package, and keep the majors aligned (httpkernel-adapter 2.x ↔ php-pm/php-pm 2.x):

composer require --dev php-pm/php-pm php-pm/httpkernel-adapter

Where to look first

  1. vendor/php-pm/httpkernel-adapter/README.md — the supported bootstrap names and any options specific to the adapter.
  2. vendor/php-pm/httpkernel-adapter/src/ — the bridge class (this is what converts a PPM request into a Symfony\Component\HttpFoundation\Request, calls $kernel->handle(), and converts the Response back) and the bootstrap classes that build the kernel once per worker.
  3. vendor/php-pm/php-pm/ — the CLI, the master/worker loop, and the config reference. ppm start --help is the authoritative list of flags for your installed version.
  4. Your own public/index.php — PPM does not execute it. Whatever it does (dotenv, error handler, container customisation) must be reproduced in a custom bootstrap.

First run — point the CLI at a bootstrap and a port, and hit it with curl:

# Symfony
./vendor/bin/ppm start --bootstrap=symfony  --app-env=prod    --workers=4 --port=8080
# Laravel
./vendor/bin/ppm start --bootstrap=laravel  --app-env=production --workers=4 --port=8080

curl -v http://127.0.0.1:8080/

Compare that request against php -S 127.0.0.1:8081 -t public (or FPM). The gap you see — typically the autoloader warm-up, container compilation/cache loading, and framework bootstrapping — is the entire value proposition.

First use case to validate: a read-mostly JSON API on Symfony or Laravel where every request pays 15–40 ms of bootstrap before your controller runs. Get one endpoint working under PPM, then immediately run the same request 200 times in a loop and watch memory_get_usage() in a log line. If memory climbs monotonically, you have a state leak and that is the real work ahead of you.

Implementation Patterns

Commit a config file, not a wall of CLI flags. Put the runtime shape in the project root so local and CI runs match production:

{
  "bridge": "HttpKernel",
  "bootstrap": "PHPPM\\Bootstraps\\Symfony",
  "host": "127.0.0.1",
  "port": "8080",
  "workers": 4,
  "appenv": "prod",
  "debug": false
}

Keep a second variant (e.g. ppm-dev.json) with debug: true and workers: 1, and pass it with --config when you need request-level tracing. Don't let debug leak into production configs.

Use a custom bootstrap as the single wiring point. This is the main extension point in the package and the place to replicate public/index.php:

class AppBootstrap extends \PHPPM\Bootstraps\Symfony
{
    public function getApplication()
    {
        $kernel = parent::getApplication();
        // error handler, timezone, custom container parameters,
        // extra cache warmers — everything index.php used to do
        return $kernel;
    }

    public function getConfig($config = []) { /* defaults per environment */ }
}

Then --bootstrap="App\Ppm\AppBootstrap". Subclassing rather than reimplementing keeps the adapter's kernel construction and cache-directory handling intact.

Choose a kernel lifecycle deliberately. Two valid patterns:

  • Reuse one kernel across requests (the default, fastest). Requires the app to be genuinely reset-clean: no request data in singletons, no stale Doctrine identity map, no accumulated event listeners.
  • Rebuild or reboot the kernel per request inside your bootstrap/getApplication() for legacy apps where auditing every singleton is unrealistic. You lose part of the speed-up but keep correctness — usually still faster than FPM.

Keep the app stateless at the service layer. Move request-scoped data out of singletons and into the Request or a scoped service. For Doctrine specifically, hook kernel.terminate (or a middleware) and call $em->clear() plus a connection ping()/reconnect on failure. Set MySQL wait_timeout comfortably above worker idle time. For Monolog, prefer handlers that open/close per write (StreamHandler with closeOnClose, or a Redis/Syslog handler) over long-lived RotatingFileHandler file handles.

Put nginx in front. PPM is an application server, not a web server: terminate TLS, serve public/ assets with try_files, and proxy only dynamic requests. Set trusted_proxies in your framework config so getClientIp(), getSchemeAndHttpHost() and generated absolute URLs are right behind the proxy — by default the worker sees the request as plain HTTP to 127.0.0.1.

Deploy pattern: build caches (cache:warmup, config/route cache, compiled container) during the image build or release step, then restart workers so they pick up new code. Long-running workers never see a deploy otherwise. In Docker/Supervisor/systemd, run PPM as a single foreground master with --workers = 2×CPU as a starting point, healthcheck a cheap endpoint, and ensure the stop signal is passed through so workers drain.

Keep FPM/built-in server as the default local path and run PPM as a second, opt-in target. State leaks are invisible under FPM and only surface after request 50 in a persistent worker, so having the "clean" server to diff against is what makes debugging tractable.

Gotchas and Tips

  • Superglobals are lies. $_GET, $_POST, $_FILES, $_SESSION are not populated the way a web SAPI populates them. Anything reading them directly (older libraries, hand-rolled code) silently sees empty or stale data. Read from the Request object exclusively.
  • php_sapi_name() is cli. PHP_SAPI === 'cli' checks — extremely common in bin/console-shared code — will take the console branch inside the worker. Guard on something other than SAPI.
  • Web-SAPI functions are missing or wrong. fastcgi_finish_request(), apache_request_headers() and friends may not exist; header(), setcookie() and http_response_code() are meaningless. Always return a Response and let the bridge emit headers.
  • File uploads are the classic trap. There is no real multipart SAPI upload, so is_uploaded_file() is false and the temp file has a PPM-managed lifetime. Move/validate the upload inside the request; don't defer it to a queued job that expects the temp path to still exist.
  • No streaming. Responses are buffered and flushed at the end, so SSE, chunked progress, flush()-driven output and huge downloads behave badly. Bypass PPM for those routes (nginx X-Accel-Redirect, or a different stack).
  • dd(), die(), exit() and fatals kill the worker. The master respawns it, so the user-facing symptom is a latency spike and a cold cache, not a stack trace. Teach the team to use dump()/logs, and grep the codebase for die( before rollout.
  • Memory only goes up. Workers are persistent: static caches, debug listeners, Monolog buffers and Doctrine's identity map all accumulate. Instrument memory_get_usage(true) per N requests, and cap worker lifetime (restart after N requests) if the adapter/PPM version supports it — otherwise schedule periodic restarts.
  • APP_DEBUG=true is poison here. The profiler, data collectors and debug listeners hold references and grow unboundedly. Run PPM in prod mode only; use the dev config with workers: 1 for a single debugging session, never for load.
  • Xdebug off, opcache CLI on. Without opcache.enable_cli=1 you throw away most of the benefit; with Xdebug loaded you'll be slower than FPM.
  • Boot-time values go stale. date() in config, getenv()/dotenv reads, setlocale(), ini_set(), putenv() and timezone changes all persist across every subsequent request in that worker. Changing .env needs a worker restart, not a cache clear.
  • Sessions and locks. File-based PHP sessions plus multiple workers means concurrent requests from one user serialise on the session file lock. Use Redis/DB sessions, or cookie-only sessions, before scaling workers.
  • Debugging a hung or leaking worker: run with the verbose/debug CLI flag to get request-level output on the worker's stdout, strace -p a specific worker PID for stuck I/O (usually a DB socket), and reproduce with a single-worker config so request logs stay ordered. A "request the same URL 200× and diff the bodies" smoke test catches most cross-request leakage cheaply.
  • Version and maintenance risk: last release mid-2023, zero dependents, and a hard coupling to the core PPM major. Pin both packages in composer.json before relying on them, and be honest in the assessment that PHP-PM's ecosystem momentum is low — FrankenPHP, RoadRunner and Swoole solve the same problem with more current maintenance. This adapter is still a good way to get a measurable win on an existing Symfony/Laravel app without changing the framework code, but budget for the state-leak audit; that, not the install, is the real cost.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core