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

Speedfony Bundle Laravel Package

phpfastcgi/speedfony-bundle

Speedfony Bundle integrates PHP FastCGI with Symfony, enabling internal requests to be dispatched directly to PHP-FPM for faster subrequests, parallel processing, and reduced response times. It provides a simple service-based API for adding FastCGI-powered workflows to Symfony apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you install. This is a Symfony bundle, not a Laravel package. There is no service provider, no config/speedfony.php, and nothing that will register itself in bootstrap/app.php. If you are a Laravel developer reading this, the mental model to hold is "Octane, but for Symfony, written in 2016" — persistent workers that keep the compiled container warm between requests. You would reach for it only if you are maintaining a legacy Symfony app (or a Symfony service inside a larger Laravel shop) and want to cut bootstrap overhead without adopting RoadRunner or Swoole.

Minimal steps

  1. Confirm the host app's Symfony version first:
    composer show symfony/framework-bundle
    
    The last tagged release is 2016-12-01, which puts it firmly in the Symfony 2.8 / 3.x era with PHP 5.6–7.0 constraints. Read vendor/phpfastcgi/speedfony-bundle/composer.json before running composer require — do not let Composer "solve" a downgrade of your framework.
  2. Install on a throwaway branch:
    composer require phpfastcgi/speedfony-bundle --dev
    
    Install as a dev dependency first. Move it to require only after you have benchmarked it.
  3. Register in app/AppKernel.php (legacy) or config/bundles.php (Symfony 4+ flexible style):
    // config/bundles.php
    return [
        // ...
        PhpFastCGI\SpeedfonyBundle\SpeedfonyBundle::class => ['all' => true],
    ];
    
    The exact FQCN is the first thing to verify — grep -r "class .*Bundle" vendor/phpfastcgi/speedfony-bundle/src/.
  4. Discover what the bundle actually wires up. There is essentially no documentation beyond the README, so read the source in this order:
    • DependencyInjection/ — the extension class tells you the real config keys and defaults.
    • Command/ — the console commands for start/stop/inspect; run bin/console list after enabling the bundle.
    • Resources/config/services.yml — the actual service graph.
  5. Run bin/console cache:clear && bin/console debug:container --tag=console.command | grep -i speed to see the surface area you got.

First use case: prove the win exists before you build on it.

Pick your heaviest-bootstrapping route (one that pulls in Doctrine metadata, a lot of config, or a large service graph). Measure it twice:

# Baseline through PHP-FPM
ab -n 500 -c 20 https://app.test/heavy-route         # or wrk

# Then start the persistent worker per the bundle's command list
bin/console <speedfony start command>
# Point a second nginx server block / fastcgi_pass at the worker socket
ab -n 500 -c 20 https://app-worker.test/heavy-route

If you cannot demonstrate a meaningful p50/p99 improvement on a stateless GET route, stop there. Persistent workers are only worth their operational cost when bootstrap is a measurable fraction of request time.

Where a Laravel dev will feel at home: the concepts are identical to Octane. php artisan octane:start ≈ the bundle's start command, octane:reload ≈ its restart/reload, and Octane's "sandbox / flush listeners" ≈ whatever flush hooks this bundle does (or, more likely, doesn't) provide. Use that mapping to know what questions to ask of the source code.

Implementation Patterns

Treat the worker as a long-lived process, not a request handler. The whole value proposition is that the container is compiled once. Every pattern below follows from that.

Pattern 1 — Front the worker with nginx, leave PHP-FPM in place. The realistic integration is a sibling upstream, not a replacement:

upstream speedfony_worker {
    server unix:/var/run/speedfony/worker.sock;
}

server {
    listen 443 ssl;
    server_name app.test;

    # Static + anything stateful stays on PHP-FPM
    location ~ ^/(admin|api/webhooks) { fastcgi_pass php-fpm; include fastcgi_params; }

    # Hot, read-heavy, stateless routes go to the worker
    location / {
        fastcgi_pass speedfony_worker;
        include fastcgi_params;
        fastcgi_read_timeout 30s;
    }
}

This gives you a rollback path: if a worker leaks or dies, flip fastcgi_pass back to php-fpm and you are on the boring, correct stack again. Never make the worker the only upstream on day one.

Pattern 2 — Route by statelessness, not by URL prefix. The routes that belong on a persistent worker are the ones with no per-request mutable state: health checks, marketing pages, catalogue reads, cached API GETs. Keep these on PHP-FPM: file uploads, streaming responses, anything that calls exit/die, long-running report exports, and admin actions that mutate global state. Write this split down as a routing table in the repo — it will drift otherwise.

Pattern 3 — Deploy with a supervisor, restart on release. Whatever the bundle's CLI commands are, wrap them:

; /etc/supervisor/conf.d/speedfony.conf
[program:speedfony-worker]
command=/usr/bin/php /var/www/app/bin/console <speedfony start command> --env=prod
autostart=true
autorestart=true
startretries=5
stopwaitsecs=15
numprocs=4
process_name=%(program_name)s_%(process_num)02d
stdout_logfile=/var/log/speedfony/worker.log

Then make the deploy script call the stop/start (or reload) command after cache:clear and before flipping traffic. A worker running an old compiled container after a deploy is the single most common way this setup bites you — the code on disk and the code in memory diverge, and you get errors that vanish on refresh.

Pattern 4 — Push bootstrap cost into the build step. The heavier your container compile, the bigger the win. So make the build do the work:

composer install --no-dev --optimize-autoloader
bin/console cache:warmup --env=prod
bin/console doctrine:ensure-production-settings --env=prod

Then the worker's cold start is mostly just require + unserialize. This is the same reasoning as php artisan config:cache && php artisan route:cache under Octane.

Pattern 5 — Borrow Octane's discipline even though the tooling isn't there. Because a 2016 bundle almost certainly has no RequestReceived/RequestTerminated listener system, you implement the flush yourself. The checklist per request:

  • Reset any static/singleton caches your code owns.
  • Ensure Doctrine's EntityManager is not left in a closed state after a failed flush (re-getManager() if isOpen() is false).
  • Clear the logger's per-request context (request ID, user).
  • Do not let $_SERVER/$_GET/$_POST mutations leak forward.
  • Never store the current request on a shared service.

If you cannot do all of the above, restrict the worker to read-only routes.

Pattern 6 — Benchmark like you mean it. Track p50/p95/p99 and memory RSS, not just req/s:

wrk -t4 -c50 -d60s https://app-worker.test/heavy-route
watch -n5 'ps -o rss=,cmd= -C php | grep speedfony'

RSS climbing monotonically across a 60-second run == a leak. That is your signal to narrow the route set, not to raise numprocs.

Gotchas and Tips

The bus factor is the headline risk. 90 stars, last release December 2016, one maintainer. There is no security response process, no PHP 8 compatibility story, and no Symfony 5/6/7 support. Before you write a line of integration code:

composer show phpfastcgi/speedfony-bundle
composer why-not phpfastcgi/speedfony-bundle 2.0
grep -A5 '"require"' vendor/phpfastcgi/speedfony-bundle/composer.json

If it needs Symfony 2.x, the honest answer is: fork it or don't use it. For a new build, use RoadRunner or FrankenPHP; on Laravel, use Octane with FrankenPHP/Swoole. Keep this bundle for legacy rescue only.

Composer will fight you. A 2016 package pins old symfony/* ranges. Use composer require --dev -W cautiously, or better, vendor it via a VCS repository + path repo so you can patch the constraints yourself:

"repositories": [{ "type": "path", "url": "packages/speedfony-bundle" }]

Container staleness is not a bug, it's the design. The compiled container is reused across requests by definition. Consequence: %env()% and .env values are read once at worker start. Changing .env and expecting the next request to pick it up will silently fail. Always restart workers on env change — same rule as Octane.

Fatal errors take down more than one request. In PHP-FPM a fatal kills one worker that gets respawned; here it kills a process holding N warm requests and the client sees a 502. Set fastcgi_next_upstream (or an nginx fallback) and make sure your supervisor startretries is > 1. Watch for crash-loop restarts in supervisorctl status.

Doctrine is the classic leak. EntityManager closes after an exception; identity map grows unbounded across requests in the same process. Mitigations:

if (!$em->isOpen()) {
    // re-create / reset the manager before handling the next request
}
$em->clear(); // after each request, on a shared worker

If your routes touch Doctrine heavily, this alone may sink the project — another reason to start with read-only, cache-heavy routes.

Debugging gets harder, not easier. Xdebug + a persistent process means breakpoints land on whatever request is currently in flight, and the connection stays open across requests. Disable Xdebug in the worker upstream. Log with a per-request correlation ID that you know is being reset — otherwise you get interleaved log lines from concurrent requests in one process, which looks like a race condition in your code but is just shared log context.

Streaming and fastcgi_finish_request behave differently. File downloads, SSE, and echo-then-flush patterns are the first things to break under persistent FastCGI. Keep those on PHP-FPM.

exit/die and header() after output are dangerous. In a shared process, a stray exit in a library short-circuits the worker loop or the request boundary. Grep your app and vendor code for exit( on routes you plan to move.

Extension points are manual. There is no equivalent of Octane's OperationTerminated listeners. Your hooks are:

  • A kernel event listener (kernel.terminate) to flush state, if the bundle dispatches it per request.
  • A decorator around whatever request-handling service the bundle wires up.
  • Your own thin middleware in front of controllers that resets static caches.

Verify which of these actually fire per-request by adding a counter to var_dump / log and hitting the worker twice.

Config quirks to check first: search the extension class for defaults around worker count, socket path, and max-requests-per-worker. A max_requests style setting is your best friend — it caps the blast radius of any leak by recycling workers periodically. If the bundle doesn't expose one, add it in your fork; it's a ten-line supervisor/tick change for enormous safety.

Have an exit strategy documented in the README of your app. One nginx line (fastcgi_pass php-fpm;), one supervisor stop, and you're off it. If you can't describe that rollback in two sentences, you're not ready to put this in front of production traffic.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings