@@ -629,6 +629,86 @@ represented by a PHP callable instead of a string::
629
629
// disables FastCGI buffering in nginx only for this response
630
630
$response->headers->set('X-Accel-Buffering', 'no');
631
631
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
+
632
712
.. _component-http-foundation-serving-files :
633
713
634
714
Serving Files
@@ -866,3 +946,4 @@ Learn More
866
946
.. _`JSON Hijacking` : https://haacked.com/archive/2009/06/25/json-hijacking.aspx/
867
947
.. _OWASP guidelines : https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside
868
948
.. _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