@@ -629,6 +629,86 @@ represented by a PHP callable instead of a string::
629629 // disables FastCGI buffering in nginx only for this response
630630 $response->headers->set('X-Accel-Buffering', 'no');
631631
632+ Streaming a JSON Response
633+ ~~~~~~~~~~~~~~~~~~~~~~~~~
634+
635+ .. versionadded :: 6.3
636+
637+ The :class: `Symfony\\ Component\\ HttpFoundation\\ StreamedJsonResponse ` class was
638+ introduced in Symfony 6.3.
639+
640+ The :class: `Symfony\\ Component\\ HttpFoundation\\ StreamedJsonResponse ` allows to
641+ stream large JSON responses using PHP generators to keep the used resources low.
642+
643+ The class constructor expects an array which represents the JSON structure and
644+ includes the list of contents to stream. In addition to PHP generators, which are
645+ recommended to minimize memory usage, it also supports any kind of PHP Traversable
646+ containing JSON serializable data::
647+
648+ use Symfony\Component\HttpFoundation\StreamedJsonResponse;
649+
650+ // any method or function returning a PHP Generator
651+ function loadArticles(): \Generator {
652+ yield ['title' => 'Article 1'];
653+ yield ['title' => 'Article 2'];
654+ yield ['title' => 'Article 3'];
655+ };
656+
657+ $response = new StreamedJsonResponse(
658+ // JSON structure with generators in which will be streamed as a list
659+ [
660+ '_embedded' => [
661+ 'articles' => loadArticles(),
662+ ],
663+ ],
664+ );
665+
666+ When loading data via Doctrine, you can use the ``toIterable() `` method to
667+ fetch results row by row and minimize resources consumption.
668+ See the `Doctrine Batch processing `_ documentation for more::
669+
670+ public function __invoke(): Response
671+ {
672+ return new StreamedJsonResponse(
673+ [
674+ '_embedded' => [
675+ 'articles' => $this->loadArticles(),
676+ ],
677+ ],
678+ );
679+ }
680+
681+ public function loadArticles(): \Generator
682+ {
683+ // get the $entityManager somehow (e.g. via constructor injection)
684+ $entityManager = ...
685+
686+ $queryBuilder = $entityManager->createQueryBuilder();
687+ $queryBuilder->from(Article::class, 'article');
688+ $queryBuilder->select('article.id')
689+ ->addSelect('article.title')
690+ ->addSelect('article.description');
691+
692+ return $queryBuilder->getQuery()->toIterable();
693+ }
694+
695+ If you return a lot of data, consider calling the :phpfunction: `flush ` function
696+ after some specific item count to send the contents to the browser::
697+
698+ public function loadArticles(): \Generator
699+ {
700+ // ...
701+
702+ $count = 0;
703+ foreach ($queryBuilder->getQuery()->toIterable() as $article) {
704+ yield $article;
705+
706+ if (0 === ++$count % 100) {
707+ flush();
708+ }
709+ }
710+ }
711+
632712.. _component-http-foundation-serving-files :
633713
634714Serving Files
@@ -866,3 +946,4 @@ Learn More
866946.. _`JSON Hijacking` : https://haacked.com/archive/2009/06/25/json-hijacking.aspx/
867947.. _OWASP guidelines : https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside
868948.. _RFC 8674 : https://tools.ietf.org/html/rfc8674
949+ .. _Doctrine Batch processing : https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/batch-processing.html#iterating-results
0 commit comments