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

Buffer Laravel Package

itul/buffer

itul/buffer is a lightweight buffering utility for Laravel and PHP applications. It collects items or deferred work and flushes them in controlled batches, helping reduce overhead, smooth background processing, and simplify batching.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and confirm discovery. composer require itul/buffer, then check composer show itul/buffer for the service provider it registers. In Laravel 9+ it should auto-discover; on older versions add the provider to config/app.php. If a config/buffer.php exists after php artisan vendor:publish --tag=buffer-config, skim it before doing anything else — buffer size, flush strategy, and "auto flush" flags are the usual knobs.
  2. Read the source, not just the README. The package is small and its whole job is wrapping the ob_start() / ob_flush() / flush() dance plus whatever header hints the browser and proxy need. Ten minutes in src/ will tell you whether it exposes a facade, a helper, or a middleware, and exactly which INI settings it sets (zlib.output_compression, output_buffering, implicit_flush). That matters more than the docs.
  3. Understand the problem it solves. By default PHP accumulates echo output (the output_buffering INI, plus Laravel's response lifecycle) and ships it in one shot when the request ends. For a 90-second import, the user stares at a spinner. This package forces bytes out incrementally so the browser can paint progress.
  4. First use case: a progress endpoint. The lowest-friction win is a plain-text or NDJSON stream consumed by fetch() + ReadableStream (or an <iframe> for legacy). Sketch:
Route::get('/imports/{import}/progress', function (Import $import) {
    $buffer = app(\Itul\Buffer\Buffer::class); // or the facade/helper it ships

    return response()->stream(function () use ($import, $buffer) {
        while (! $import->fresh()->isFinished()) {
            echo json_encode(['done' => $import->processed, 'total' => $import->total])."\n";
            $buffer->flush();          // however the package names this
            sleep(1);
        }
    }, 200, [
        'Content-Type'  => 'application/x-ndjson',
        'Cache-Control' => 'no-cache, no-transform',
        'X-Accel-Buffering' => 'no',
    ]);
});

Get that working locally under php artisan serve and behind your real Nginx/Apache before you build UI on top of it.

Implementation Patterns

Stream from the web process only when the work is short. Two viable architectures:

  • Synchronous stream — the request does the work and periodically flushes progress. Fine for imports under ~30s and single-user admin screens. Simple, no queue, no cache poll.
  • Job + status stream — dispatch a queued job that writes processed/total/stage to the cache or a imports table row, and have the stream endpoint poll that row and echo a line per tick. This survives max_execution_time, retries, and lets the user close the tab without orphaning work. Prefer this the moment the task can exceed a minute.

Use Laravel's response()->stream() as the outer shell. It sets Transfer-Encoding: chunked and gives you a closure to write into. The package handles the per-iteration flush; don't mix in your own ob_start() calls on top or you'll fight it.

Emit newline-delimited JSON, not HTML fragments. NDJSON is trivial to parse incrementally on the client, plays well with TextDecoderStream, and lets you add fields later without breaking the reader. Send a periodic heartbeat line ({"ping":true}) every few seconds during quiet phases so proxies and load balancers don't reap the connection.

Disable response-compressing middleware for these routes. Laravel's CompressResponse (and any mod_deflate/gzip at the edge) will re-buffer everything you just flushed. Exclude the progress route by path in bootstrap/app.php or via a route group with the middleware removed, and confirm with curl -N that chunks arrive at roughly one-second intervals rather than all at once at the end.

Artisan side. The same package is handy for long console commands — progress bars already flush, but if you're writing custom $this->output->write() loops with \n and want them visible when the command is invoked over HTTP (rare) or piped, the package's flush helper keeps behaviour consistent across SAPI and CLI.

Testing strategy. Don't unit-test the stream. Assert on the state the stream reads (the job, the cache keys) with a normal feature test, and cover the transport with one integration test that boots php artisan serve or hits the staging URL and asserts the first chunk arrives before a sleep. Mocking flush() proves nothing.

Gotchas and Tips

  • Nginx buffers FastCGI responses by default. Sending X-Accel-Buffering: no is usually enough; if not, you're editing fastcgi_buffering off; in the vhost. This is the single most common "it works locally, hangs in prod" cause.
  • zlib.output_compression = On in php.ini silently undoes every flush. So does output_buffering = 4096 plus a package that only flushes its own nested level. Verify with php -i | grep -E 'output_buffering|zlib'.
  • Session locking will deadlock your UI. Any other request from the same browser (the poll that starts the import, an asset, a Livewire roundtrip) blocks on the session file while your stream holds it. Call session_write_close() (or $request->session()->save()) before entering the loop — for the import-triggering request and the stream.
  • Nothing may echo before your headers. A BOM in a PHP file, a stray blank line after ?>, a debug dump() in a middleware, or an appended debugbar/Telescope payload will send headers and break the chunked response. Turn Debugbar and Telescope off for these routes.
  • Wrap the loop in try/finally. If the package opens a buffer and your code throws mid-stream, an unclosed ob_start() swallows the error output and you get a blank 200. Explicitly close buffered levels in finally and log the exception to a file rather than the response.
  • Use connection_aborted() / ignore_user_abort() deliberately. Decide whether closing the tab should stop the work (then check connection_aborted() each tick and bail) or let it run (then ignore_user_abort(true) and don't rely on the request to finish the job — push it to the queue instead).
  • Set set_time_limit(0) and know your web server's fastcgi_read_timeout. sleep() loops plus a 60-second proxy timeout equals a truncated stream with no error. Heartbeats help; a queue-based design sidesteps the whole class of problem.
  • Don't stream large payloads through it. This is a progress channel, not a download mechanism. File exports belong in response()->download() or a signed temporary URL.
  • Extension points to look for in the source: whether flush behaviour is bound as a singleton (so you can swap a no-op implementation in tests), whether the buffer level is configurable, and whether it registers middleware you can opt out of per-route. If the package calls ob_flush() unconditionally, you'll see "failed to flush buffer" notices when nothing is buffered — guard your local dev with while (ob_get_level()) { ob_end_flush(); } if the noise gets annoying.
  • Watch for abandonment. With zero dependents and a Bitbucket-only home, pin the version in composer.json and keep the wrapper confined to one or two classes in your app so you can swap in a plain flush() loop if maintenance stops.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource