garlic/event-source
garlic/event-source is a Laravel/PHP event sourcing package for persisting domain changes as immutable events, rebuilding aggregates, and projecting read models. It helps teams add auditability and decoupled workflows to their applications.
Architecture fit. The package name (event-source-bundle) and the garlicservices org point to a Symfony bundle, not a Laravel package. Laravel does not consume Symfony bundles — it consumes service providers, facades, and config-publishable packages. There is no evidence of a composer.json extra.laravel.providers entry, a GarlicEventSourceServiceProvider, or a config stub. Assuming the underlying SSE logic isn't cleanly separated from the DI container wiring (typical for bundles of that era), a Laravel team would be integrating the framework glue rather than the protocol implementation. Since SSE on top of Symfony HttpFoundation is roughly a StreamedResponse with correct headers, the deliverable here is thin: the package is unlikely to encode much more than header conventions, a heartbeat loop, and an event formatter — all of which a mid-level engineer can reproduce in well under a day.
Integration feasibility. Low, and falling. The 2017-09-07 release predates Laravel 5.6 and lands in the Symfony 2.8/3.x era. Modern Laravel (10/11/12) pins symfony/* to 6.x/7.x, so Composer resolution will almost certainly fail on transitive constraints before any code is loaded. There is no Laravel package discovery, no documented config surface, and — with 0 stars and 0 dependents — no issue history to consult when it breaks. Treat "integration" as "vendoring source into app/Support/Sse/ and taking ownership of it."
Technical risk. High across every axis:
| Risk | Assessment |
|---|---|
| Abandonment | 8+ years without a release; no security disclosure channel |
| Ecosystem drift | Symfony 2/3 constraints vs. Laravel's Symfony 6/7 pinning |
| PHP compatibility | Written for PHP 5.6/7.0; unverified on 8.1–8.4 (deprecations, each(), dynamic properties, untyped internals) |
| Validation | Zero dependents means zero production signal; no confidence in edge-case behavior |
| License/lineage | Must confirm a permissive license and that the code is original before vendoring |
The only genuinely low-risk part is the domain: SSE is a small, stable, well-specified protocol (text/event-stream framing, id:, event:, retry:, Last-Event-ID replay). That reduces the value of a dependency and raises the cost of a stale one.
Key questions for the technical spike (1–2 days, timeboxed):
composer.json PHP and symfony/* constraints, and does the code run under PHP 8.3 without deprecation notices?retry: hints, and Last-Event-ID resume — or only naive write-and-flush?If Q1 yields a clean, container-free class under a permissive license, a vendored copy is defensible. Otherwise the answer is no-go.
Stack fit — recommended alternative. Do not adopt the bundle. Implement SSE natively, which in Laravel is a response()->stream() closure (or StreamedResponse directly) emitting text/event-stream with Cache-Control: no-cache, X-Accel-Buffering: no, and a periodic heartbeat comment (:ping\n\n) every 15–25s. Pair it with a fan-out source: Redis pub/sub for multi-node deployments, or a DB cursor poll for simple cases. If we want a maintained, purpose-built component, Mercure (standalone hub, Symfony-adjacent but framework-agnostic) or a managed service (Ably/Pusher) is the realistic comparison — not this package.
Migration path. There is no installed base to migrate from. If a prototype already vendors the bundle, the path is: extract the route/controller boundaries, reimplement the writer behind the same route contract, delete the vendored tree, and keep the client-side EventSource code untouched — the browser API is identical, which makes the swap low-drama once endpoints are stable.
Compatibility checklist before any code is written. Confirm PHP 8.2+ support, Laravel 10/11 target, absence of symfony/* conflicts, PSR-4 autoloading that we control, and a composer.json we can safely pin in CI. For any vendored code, add it to our dependency-review and static-analysis (PHPStan/Larastan) surface so it isn't a blind spot.
Sequencing.
SseController + SseStream writer + Redis pub/sub bridge + heartbeats; unit-test the framing (event id, multi-line data, retry:).proxy_buffering off; gzip off; ALB/NLB idle timeout raised above heartbeat interval; response buffering disabled at every hop.Last-Event-ID, exponential backoff) validated end-to-end.Maintenance. Ownership transfers entirely to us either way — the question is whether we own a small in-house module or a vendored 2017 bundle we did not write. The former is a maintenance task; the latter is archaeology. On-call engineers debugging an SSE disconnect at 02:00 will be reading our own code in one scenario and unfamiliar, abandoned Symfony DI code in the other.
Support. None available. Zero dependents means no Stack Overflow corpus, no CVE feed, no upgrade path. This is the decisive operational fact: we would be the sole maintainer of a package we have no relationship with.
Scaling. This is the real operational cost of SSE on PHP-FPM and it exists regardless of package choice — the package does not solve it. Each open connection occupies a PHP-FPM worker for its full lifetime. With a pool of 50 children, node #1 tops out near 50 concurrent streams before the entire application stalls, not just SSE. Mitigations, in order of preference: dedicated SSE worker pool / separate service so streams cannot starve request traffic; Laravel Octane (Swoole/RoadRunner) for coroutine-based concurrency; Redis pub/sub fan-out so N app nodes don't each need a DB cursor; and hard server-side max_execution_time guards with graceful close-and-reconnect. Budget for the capacity model explicitly — SSE is a connection-count problem, and connection count is where the cost lands.
Failure modes to design against.
X-Accel-Buffering: no and disable compression.Last-Event-ID replay isn't implemented — clients silently miss messages and this surfaces as data-correctness bugs, not infra alerts.Ramp-up. Low for the in-house path — response()->stream() is a familiar Laravel idiom and the SSE spec is small; one engineer, one sprint including tests. The bundle path has negative ramp-up efficiency: time is spent reverse-engineering 2017 Symfony conventions and patching PHP 8 incompatibilities before the first byte is streamed, with no guarantee of reaching production.
Recommendation. Reject adoption; build the ~100–200 line SSE module in-house, or evaluate Mercure as a maintained alternative. The bundle's only conceivable role is as a reference read for header handling and heartbeat patterns during the spike — treat it as documentation, not a dependency.
How can I help you explore Laravel packages today?