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

Symfony Bridge Laravel Package

bref/symfony-bridge

Bref runtime bridge for running Symfony on AWS Lambda. Install with Composer, extend BrefKernel in your Kernel, and deploy with a warm cache to reduce cold-start time. Avoid filesystem writes in production.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the bridge

composer require bref/symfony-bridge

# if you are on Bref 3, make sure Bref itself is recent enough (see gotchas)
composer require bref/bref:^3.0.13

This pulls in bref/bref and bref/monolog-bridge as dependencies. On a fresh Symfony Flex app it also registers Bref\SymfonyBridge\BrefSymfonyBridgeBundle in config/bundles.php.

2. Swap the base Kernel — the only required code change

// src/Kernel.php
namespace App;

use Bref\SymfonyBridge\BrefKernel;              // + added
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
// use Symfony\Component\HttpKernel\Kernel as BaseKernel;  // - removed

class Kernel extends BrefKernel                 // was: extends BaseKernel
{
    use MicroKernelTrait;
    // ...
}

That is it: BrefKernel redirects Symfony's cache/log directories to a writable location and symlinks the cache you prebuilt at deploy time, so a read-only /var/task doesn't blow up on boot.

3. Look at these three files first

File Why
serverless.yml handler, layers (${bref:layer.php-83-fpm} vs -php-83), BREF_LOOP_MAX
src/Kernel.php getWritableCacheDirectories() is your main tuning knob
your deploy script cache:clear --env=prod must run before serverless deploy

4. First real use case — a warm-cache HTTP app

# deploy.sh
composer install --classmap-authoritative --no-dev --no-scripts
bin/console cache:clear --env=prod      # builds + warms var/cache/prod
echo "<?php return [];" > .env.local.php  # disable Dotenv at runtime
serverless deploy

Without the warmup step the first request on every new Lambda pays 1–20 s of container compilation. With it, cold starts are dominated by autoload + boot, not cache building.

5. Sanity check locally — BrefKernel behaves like a normal kernel outside Lambda, so bin/console, PHPUnit and serverless invoke local all keep working unchanged.


Implementation Patterns

Pattern: HTTP through PHP-FPM (default, safest)

functions:
  app:
    handler: public/index.php
    layers:
      - ${bref:layer.php-83-fpm}

Use this unless you are deliberately avoiding FPM. You keep the standard public/index.php front controller and the bridge only handles cache wiring. PHP-FPM reaps per-request temp files itself, so the upload-cleanup behaviour added in 3.0.3 is a no-op here.

Pattern: Kernel-as-handler (no FPM, kept-alive process)

functions:
  app:
    handler: App\Kernel              # resolved via Symfony Runtime
    layers:
      - ${bref:layer.php-83}         # note: no -fpm
    environment:
      BREF_LOOP_MAX: 100             # recycle the process every 100 requests

Bref invokes App\Kernel::handle() in a loop. Fewer processes, lower cold-start count, but services survive between invocations — see the state-leak gotcha below. This is also the mode where uploaded files used to pile up in /tmp; since 3.0.3 the bridge deletes the previous request's uploads at the start of the next one.

Pattern: Event handlers as Symfony services

SQS/Kinesis/S3 events with Symfony Messenger:

functions:
  sqsHandler:
    handler: App\Service\MyConsumer      # a *class* name, not a file
    layers:
      - ${bref:layer.php-83}
// config/services.yaml
services:
  App\Service\MyConsumer:
    public: true            # REQUIRED — the runtime fetches it from the container

The runtime boots the kernel from public/index.php and pulls the service out of the container. This is the cleanest way to reuse your existing Messenger handlers, repositories and Doctrine connections on Lambda.

Pattern: Custom bootstrap (no public/index.php)

<?php // kernel.php
require_once __DIR__.'/vendor/autoload_runtime.php';

return function (array $context) {
    return new App\Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
functions:
  sqsHandler:
    handler: kernel.php:App\Service\MyConsumer   # file:class

Note the file:class syntax — without a colon, the whole string is treated as a class name.

Pattern: File uploads without filling /tmp

In runtime (non-FPM) mode, Symfony's UploadedFile objects land in sys_get_temp_dir() = /tmp. The bridge only sweeps the previous request's uploads when the next request begins, so a single invocation that writes hundreds of megabytes can still exhaust /tmp (512 MB default) before anything cleans it.

// config/services.yaml
parameters:
    # keep request-scoped uploads off the Lambda disk entirely
    app.upload_target: '%env(S3_UPLOAD_BUCKET)%'

Prefer presigned direct-to-S3 uploads for anything non-trivial and treat UploadedFile handling as suitable only for small payloads (API Gateway caps the request body at ~10 MB anyway). If you do keep local uploads, move them out of /tmp before the request ends — move_uploaded_file into a directory you control still counts against the same 512 MB.

Pattern: Deploy pipeline with an explicit ordering

  1. composer install --no-dev --classmap-authoritative --no-scripts
  2. bin/console cache:clear --env=prod (clear + warmup in one)
  3. Write .env.local.php returning [] so Dotenv is skipped and Lambda env vars win
  4. serverless deploy

Keep step 2 late — anything that changes the container config after warmup invalidates it.

Pattern: Logging that plays nicely with CloudWatch

Add the bundle explicitly if Flex didn't:

// config/bundles.php
Bref\SymfonyBridge\BrefSymfonyBridgeBundle::class => ['all' => true],

With bref/monolog-bridge ^2.0 every log line is prefixed with the Lambda invocation ID, which makes CloudWatch Logs Insights queries per-request possible.

Pattern: Shrinking the deployment package

Override the writable directories so the pools/ directory is symlinked instead of copied:

protected function getWritableCacheDirectories(): array
{
    return [];   // only safe if you never touch a filesystem cache pool
}

Gotchas and Tips

Flex will not register the bundle on upgrade. composer update bref/symfony-bridge skips Flex recipes. Existing apps keep Monolog's default JSON logs silently. Add BrefSymfonyBridgeBundle to config/bundles.php by hand. BrefBundle still works but is deprecated.

A service handler must be public: true. Private services are removed from the container and the runtime will fail to resolve them at invocation time — with a confusing error in CloudWatch, not at deploy time.

Never write to var/cache at runtime. /var/task is read-only. Anything using cache.adapter.filesystem as a pool must move to cache.adapter.array (per-invocation) or a real backend (Redis/DynamoDB). If you truly need per-Lambda scratch space, /tmp is writable but dies with the container.

Env vars are baked into the compiled container. Symfony resolves %env()% at compile time for many settings. Change a Lambda env var after deploy and the container will not rebuild — you get stale config. Re-deploy, or keep runtime-only values out of the compile step.

State leaks in kept-alive mode. With handler: App\Kernel and BREF_LOOP_MAX, the container is not rebooted per request the way FPM is. Never store request data on a service property or a static; inject RequestStack instead. Tune BREF_LOOP_MAX down if you see memory creep.

/tmp leaks on 3.0.2 and below. Prior to 3.0.3, uploaded files were never deleted in runtime (non-FPM) mode: they accumulated across invocations until /tmp filled, at which point everything writing to /tmp failed — Monolog's stream handler included, so the failure looked like a logging problem rather than an upload problem. Upgrade to 3.0.3. The tell-tale signature is a No space left on device / Disk quota exceeded in CloudWatch after a period of working fine.

3.0.3 requires Bref 3.0.13 on the Bref 3 line. Before Bref 3.0.13, Bref's own cleanup missed every upload on PHP 8.4 and up, so the bridge's deletion had nothing to delete and /tmp still leaked on 8.4 runtimes. composer require bref/bref:^3.0.13 — the bridge's constraint will not force this for you if your lock file is already solving.

Cleanup is deferred, not immediate. Uploads are removed when the next request starts, not at the end of the current one. So a burst of traffic on a long-lived instance still temporarily holds two requests' worth of files in /tmp, and a single oversized upload is never cleaned mid-request. Size your handlers accordingly and prefer direct-to-S3 uploads.

Empty .env.local.php disables Dotenv entirely. That is the point — but it means any value you relied on from .env locally must be set as a Lambda environment variable (or in serverless.yml environment: / SSM parameters).

Cold starts are normal and frequent. AWS recycles containers several times a day and scales out aggressively. A warm cache removes the worst of it; the rest is autoload size. --classmap-authoritative combined with composer dump-autoload --optimize is worth the effort.

Version pinning matters around Symfony and Bref releases. 3.0.1 exists specifically to fix a Symfony 8.1 breaking change, 3.0.3 fixes the /tmp upload leak and bumps the minimum Bref to 3.0.13, and 3.0.x is the Bref 3 line while 2.0.x is the Bref 3-preview line. Match the bridge major to your Bref major and keep both minors current.

Reverting log format is a dependency change, not config. Require bref/monolog-bridge: ^1.0 to drop the request-ID prefix; there is no flag on the formatter bundle.

Debugging a handler resolution failure: run serverless invoke local -f sqsHandler — it reproduces the container boot path locally. If that works but production fails, the usual cause is a warm vs. cold cache directory mismatch or a private service.

Debugging a /tmp exhaustion: the bridge does not log its cleanup. Inspect ls -la /tmp from a debug handler or add a temporary error_log() in a request listener to see what is accumulating — Doctrine proxies, Monolog streams and UploadedFile temp names all show up there.

Extension points are narrow by design. BrefKernel::getWritableCacheDirectories() is effectively the only hook; everything else (runtime bootstrap, log formatting) is configured through serverless.yml handlers/layers or by registering the bundle. Don't subclass further without a strong reason — the bridge is deliberately thin and tracks Symfony internals closely.

Deploying into a subdirectory is supported since 1.0.3; older versions produced broken cache paths when the app wasn't at the package root.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel