1919use ApiPlatform \Hydra \PartialCollectionView ;
2020use ApiPlatform \Metadata \CollectionOperationInterface ;
2121use ApiPlatform \Metadata \Error ;
22+ use ApiPlatform \Metadata \IriConverterInterface ;
2223use ApiPlatform \Metadata \Operation ;
24+ use ApiPlatform \Metadata \Operation \Factory \OperationMetadataFactoryInterface ;
2325use ApiPlatform \Metadata \QueryParameterInterface ;
26+ use ApiPlatform \Metadata \ResourceClassResolverInterface ;
2427use ApiPlatform \Metadata \UrlGeneratorInterface ;
2528use ApiPlatform \Metadata \Util \IriHelper ;
2629use ApiPlatform \State \Pagination \PaginatorInterface ;
2730use ApiPlatform \State \Pagination \PartialPaginatorInterface ;
2831use ApiPlatform \State \ProcessorInterface ;
32+ use ApiPlatform \State \Util \HttpResponseHeadersTrait ;
33+ use ApiPlatform \State \Util \HttpResponseStatusTrait ;
2934use Symfony \Component \HttpFoundation \Response ;
3035use Symfony \Component \HttpFoundation \StreamedResponse ;
3136use Symfony \Component \JsonStreamer \StreamWriterInterface ;
3237use Symfony \Component \TypeInfo \Type ;
3338
39+ /**
40+ * @implements ProcessorInterface<mixed,mixed>
41+ */
3442final class JsonStreamerProcessor implements ProcessorInterface
3543{
44+ use HttpResponseHeadersTrait;
45+ use HttpResponseStatusTrait;
46+
47+ /**
48+ * @param ProcessorInterface<mixed,mixed> $processor
49+ * @param StreamWriterInterface<array<string,mixed>> $jsonStreamer
50+ */
3651 public function __construct (
3752 private readonly ProcessorInterface $ processor ,
3853 private readonly StreamWriterInterface $ jsonStreamer ,
54+ ?IriConverterInterface $ iriConverter = null ,
55+ ?ResourceClassResolverInterface $ resourceClassResolver = null ,
56+ ?OperationMetadataFactoryInterface $ operationMetadataFactory = null ,
3957 private readonly string $ pageParameterName = 'page ' ,
4058 private readonly string $ enabledParameterName = 'pagination ' ,
41- private readonly int $ urlGenerationStrategy = UrlGeneratorInterface::ABS_PATH
59+ private readonly int $ urlGenerationStrategy = UrlGeneratorInterface::ABS_PATH ,
4260 ) {
61+ $ this ->resourceClassResolver = $ resourceClassResolver ;
62+ $ this ->iriConverter = $ iriConverter ;
63+ $ this ->operationMetadataFactory = $ operationMetadataFactory ;
4364 }
4465
66+ public function process (mixed $ data , Operation $ operation , array $ uriVariables = [], array $ context = [])
67+ {
68+ if (!$ operation ->getJsonStream () || !($ request = $ context ['request ' ] ?? null )) {
69+ return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
70+ }
71+
72+ // TODO: remove this before merging
73+ if ($ request ->query ->has ('skip_json_stream ' )) {
74+ return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
75+ }
76+
77+ if ($ operation instanceof Error || $ data instanceof Response) {
78+ return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
79+ }
80+
81+ if ($ operation instanceof CollectionOperationInterface) {
82+ $ requestUri = $ request ->getRequestUri () ?? '' ;
83+ $ collection = new Collection ();
84+ $ collection ->member = $ data ;
85+ $ collection ->view = $ this ->getView ($ data , $ requestUri , $ operation );
86+
87+ if ($ operation ->getParameters ()) {
88+ $ collection ->search = $ this ->getSearch ($ operation , $ requestUri );
89+ }
90+
91+ if ($ data instanceof PaginatorInterface) {
92+ $ collection ->totalItems = $ data ->getTotalItems ();
93+ }
94+
95+ if (\is_array ($ data ) || ($ data instanceof \Countable && !$ data instanceof PartialPaginatorInterface)) {
96+ $ collection ->totalItems = \count ($ data );
97+ }
98+
99+ $ data = $ this ->jsonStreamer ->write (
100+ $ collection ,
101+ Type::generic (Type::object ($ collection ::class), Type::object ($ operation ->getClass ())),
102+ ['data ' => $ data , 'operation ' => $ operation ],
103+ );
104+ } else {
105+ $ data = $ this ->jsonStreamer ->write ($ data , Type::object ($ operation ->getClass ()), [
106+ 'data ' => $ data ,
107+ 'operation ' => $ operation ,
108+ ]);
109+ }
110+
111+ /** @var iterable<string> $data */
112+ $ response = new StreamedResponse (
113+ $ data ,
114+ $ this ->getStatus ($ request , $ operation , $ context ),
115+ $ this ->getHeaders ($ request , $ operation , $ context )
116+ );
117+
118+ return $ this ->processor ->process ($ response , $ operation , $ uriVariables , $ context );
119+ }
120+
121+ // TODO: These come from our Hydra collection normalizer, try to share the logic
45122 private function getSearch (Operation $ operation , string $ requestUri ): IriTemplate
46123 {
47124 /** @var list<IriTemplateMapping> */
@@ -67,6 +144,7 @@ private function getSearch(Operation $operation, string $requestUri): IriTemplat
67144 }
68145
69146 $ parts = parse_url ($ requestUri );
147+
70148 return new IriTemplate (
71149 variableRepresentation: 'BasicRepresentation ' ,
72150 mapping: $ mapping ,
@@ -91,11 +169,11 @@ private function getView(mixed $object, string $requestUri, Operation $operation
91169 // TODO: This needs to be changed as well as I wrote in the CollectionFiltersNormalizer
92170 // We should not rely on the request_uri but instead rely on the UriTemplate
93171 // This needs that we implement the RFC and that we do more parsing before calling the serialization (MainController)
94- $ parsed = IriHelper::parseIri ($ requestUri ?? ' / ' , $ this ->pageParameterName );
172+ $ parsed = IriHelper::parseIri ($ requestUri , $ this ->pageParameterName );
95173 $ appliedFilters = $ parsed ['parameters ' ];
96174 unset($ appliedFilters [$ this ->enabledParameterName ]);
97175
98- $ urlGenerationStrategy = $ operation? ->getUrlGenerationStrategy() ?? $ this ->urlGenerationStrategy ;
176+ $ urlGenerationStrategy = $ operation ->getUrlGenerationStrategy () ?? $ this ->urlGenerationStrategy ;
99177 $ id = IriHelper::createIri ($ parsed ['parts ' ], $ parsed ['parameters ' ], $ this ->pageParameterName , $ paginated ? $ currentPage : null , $ urlGenerationStrategy );
100178 if (!$ appliedFilters && !$ paginated ) {
101179 return new PartialCollectionView ($ id );
@@ -117,46 +195,4 @@ private function getView(mixed $object, string $requestUri, Operation $operation
117195
118196 return new PartialCollectionView ($ id , $ first , $ last , $ previous , $ next );
119197 }
120-
121- public function process (mixed $ data , Operation $ operation , array $ uriVariables = [], array $ context = [])
122- {
123- if ($ context ['request ' ]->query ->has ('skip_json_stream ' )) {
124- return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
125- }
126-
127- if ($ operation instanceof Error || $ data instanceof Response) {
128- return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
129- }
130-
131- if ($ operation instanceof CollectionOperationInterface) {
132- $ requestUri = $ context ['request ' ]->getRequestUri () ?? '' ;
133- $ collection = new Collection ();
134- $ collection ->member = $ data ;
135- $ collection ->view = $ this ->getView ($ data , $ requestUri , $ operation );
136-
137- if ($ operation ->getParameters ()) {
138- $ collection ->search = $ this ->getSearch ($ operation , $ requestUri );
139- }
140-
141- if ($ data instanceof PaginatorInterface) {
142- $ collection ->totalItems = $ data ->getTotalItems ();
143- }
144-
145- if (\is_array ($ data ) || ($ data instanceof \Countable && !$ data instanceof PartialPaginatorInterface)) {
146- $ collection ->totalItems = \count ($ data );
147- }
148-
149- $ response = new StreamedResponse ($ this ->jsonStreamer ->write ($ collection , Type::generic (Type::object ($ collection ::class), Type::object ($ operation ->getClass ())), [
150- 'data ' => $ data ,
151- 'operation ' => $ operation ,
152- ]));
153- } else {
154- $ response = new StreamedResponse ($ this ->jsonStreamer ->write ($ data , Type::object ($ operation ->getClass ()), [
155- 'data ' => $ data ,
156- 'operation ' => $ operation ,
157- ]));
158- }
159-
160- return $ this ->processor ->process ($ response , $ operation , $ uriVariables , $ context );
161- }
162198}
0 commit comments