Add JsonStreamResponse for memory-efficient JSON streaming#19367
Open
dereuromark wants to merge 15 commits into5.nextfrom
Open
Add JsonStreamResponse for memory-efficient JSON streaming#19367dereuromark wants to merge 15 commits into5.nextfrom
dereuromark wants to merge 15 commits into5.nextfrom
Conversation
Add new response class for streaming large JSON datasets. This is the foundation for memory-efficient JSON streaming that will work with generators and ResultSets.
…s, and generators
Add assertIsResource() calls after fopen() to ensure PHPStan knows the resource is valid before passing to fclose().
d3733be to
48733c6
Compare
- Changed from string concatenation to echo-based output - Added outputAndFlush() for per-item flushing to client - Added X-Accel-Buffering: no header for nginx compatibility - Smart flush detection to work correctly in test environments - Memory usage now O(1) instead of O(n) for output
ADmad
reviewed
Mar 27, 2026
- Add FORMAT_JSON and FORMAT_NDJSON constants with validation - Change createStreamCallback return type from callable to Closure - Make Log dependency optional with class_exists check - Add tests for invalid format and constants
josbeir
reviewed
Mar 27, 2026
Comment on lines
+460
to
+465
| $level = ob_get_level(); | ||
| if ($level <= 1) { | ||
| if ($level === 1) { | ||
| ob_flush(); | ||
| } | ||
| flush(); |
Contributor
There was a problem hiding this comment.
should we considering adding a threshold to only flush buffers every x lines, ob is pretty cpu intensive and can become a little bottleck for large feeds...
but maybe that is a micro-optimization :-)
protected int $flushThreshold = 50;
// only flush after x n of rows
protected function outputAndFlush(string $data, bool $force = false): void
{
echo $data;
$this->rowsSinceLastFlush++;
if ($force || $this->rowsSinceLastFlush >= $this->flushThreshold) {
$this->flushBuffers();
$this->rowsSinceLastFlush = 0;
}
}
// flush after threshold
protected function flushBuffers(): void
{
.... flush here
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #19356 - adds a new
JsonStreamResponseclass for memory-efficient streaming of large JSON datasets using generators.Key Features
echo+flush()to send data to the client as each item is encoded, keeping memory usage constant regardless of dataset sizerootkey wrapping and full envelope with static metadataConfigure::read('debug')is truewithStreamOptions()returns new instanceMemory Profile
With true streaming, memory usage stays constant regardless of dataset size:
The implementation uses
echo+flush()to send data to the client immediately as each item is encoded, rather than building a complete string in memory first.Architecture
Class Location
Cake\Http\Response\JsonStreamResponseextendingCake\Http\ResponseOptions
rootstring|nullnull{"root": [...]}envelopearray[]dataKeystring'data'formatstring'json''json'or'ndjson'transformcallable|nullnullflagsintDEFAULT_JSON_FLAGSUsage Examples
Error Handling Strategy
{"__streamError": {"message": "...", "index": N}}to maintain valid JSONLog::error()Content-Type Headers
jsonapplication/json; charset=UTF-8ndjsonapplication/x-ndjson; charset=UTF-8Additionally,
X-Accel-Buffering: nois set to prevent nginx proxy buffering.ORM Integration
For true streaming benefits, use unbuffered queries and avoid result formatters:
Note: Result formatters (
map(),combine(), etc.) buffer results internally, which defeats the memory-efficient streaming purpose.