19
19
use ApiPlatform \Hydra \PartialCollectionView ;
20
20
use ApiPlatform \Metadata \CollectionOperationInterface ;
21
21
use ApiPlatform \Metadata \Error ;
22
+ use ApiPlatform \Metadata \HttpOperation ;
23
+ use ApiPlatform \Metadata \IriConverterInterface ;
22
24
use ApiPlatform \Metadata \Operation ;
25
+ use ApiPlatform \Metadata \Operation \Factory \OperationMetadataFactoryInterface ;
23
26
use ApiPlatform \Metadata \QueryParameterInterface ;
27
+ use ApiPlatform \Metadata \ResourceClassResolverInterface ;
24
28
use ApiPlatform \Metadata \UrlGeneratorInterface ;
25
29
use ApiPlatform \Metadata \Util \IriHelper ;
26
30
use ApiPlatform \State \Pagination \PaginatorInterface ;
27
31
use ApiPlatform \State \Pagination \PartialPaginatorInterface ;
28
32
use ApiPlatform \State \ProcessorInterface ;
33
+ use ApiPlatform \State \Util \HttpResponseHeadersTrait ;
34
+ use ApiPlatform \State \Util \HttpResponseStatusTrait ;
29
35
use Symfony \Component \HttpFoundation \Response ;
30
36
use Symfony \Component \HttpFoundation \StreamedResponse ;
31
37
use Symfony \Component \JsonStreamer \StreamWriterInterface ;
32
38
use Symfony \Component \TypeInfo \Type ;
33
39
40
+ /**
41
+ * @implements ProcessorInterface<mixed,mixed>
42
+ */
34
43
final class JsonStreamerProcessor implements ProcessorInterface
35
44
{
45
+ use HttpResponseHeadersTrait;
46
+ use HttpResponseStatusTrait;
47
+
48
+ /**
49
+ * @param ProcessorInterface<mixed,mixed> $processor
50
+ * @param StreamWriterInterface<array<string,mixed>> $jsonStreamer
51
+ */
36
52
public function __construct (
37
53
private readonly ProcessorInterface $ processor ,
38
54
private readonly StreamWriterInterface $ jsonStreamer ,
55
+ ?IriConverterInterface $ iriConverter = null ,
56
+ ?ResourceClassResolverInterface $ resourceClassResolver = null ,
57
+ ?OperationMetadataFactoryInterface $ operationMetadataFactory = null ,
39
58
private readonly string $ pageParameterName = 'page ' ,
40
59
private readonly string $ enabledParameterName = 'pagination ' ,
41
- private readonly int $ urlGenerationStrategy = UrlGeneratorInterface::ABS_PATH
60
+ private readonly int $ urlGenerationStrategy = UrlGeneratorInterface::ABS_PATH ,
42
61
) {
62
+ $ this ->resourceClassResolver = $ resourceClassResolver ;
63
+ $ this ->iriConverter = $ iriConverter ;
64
+ $ this ->operationMetadataFactory = $ operationMetadataFactory ;
43
65
}
44
66
67
+ public function process (mixed $ data , Operation $ operation , array $ uriVariables = [], array $ context = [])
68
+ {
69
+ if (!$ operation ->getJsonStream () || !($ request = $ context ['request ' ] ?? null )) {
70
+ return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
71
+ }
72
+
73
+ // TODO: remove this before merging
74
+ if ($ request ->query ->has ('skip_json_stream ' )) {
75
+ return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
76
+ }
77
+
78
+ if ($ operation instanceof Error || $ data instanceof Response || !$ operation instanceof HttpOperation) {
79
+ return $ this ->processor ->process ($ data , $ operation , $ uriVariables , $ context );
80
+ }
81
+
82
+ if ($ operation instanceof CollectionOperationInterface) {
83
+ $ requestUri = $ request ->getRequestUri () ?? '' ;
84
+ $ collection = new Collection ();
85
+ $ collection ->member = $ data ;
86
+ $ collection ->view = $ this ->getView ($ data , $ requestUri , $ operation );
87
+
88
+ if ($ operation ->getParameters ()) {
89
+ $ collection ->search = $ this ->getSearch ($ operation , $ requestUri );
90
+ }
91
+
92
+ if ($ data instanceof PaginatorInterface) {
93
+ $ collection ->totalItems = $ data ->getTotalItems ();
94
+ }
95
+
96
+ if (\is_array ($ data ) || ($ data instanceof \Countable && !$ data instanceof PartialPaginatorInterface)) {
97
+ $ collection ->totalItems = \count ($ data );
98
+ }
99
+
100
+ $ data = $ this ->jsonStreamer ->write (
101
+ $ collection ,
102
+ Type::generic (Type::object ($ collection ::class), Type::object ($ operation ->getClass ())),
103
+ ['data ' => $ data , 'operation ' => $ operation ],
104
+ );
105
+ } else {
106
+ $ data = $ this ->jsonStreamer ->write ($ data , Type::object ($ operation ->getClass ()), [
107
+ 'data ' => $ data ,
108
+ 'operation ' => $ operation ,
109
+ ]);
110
+ }
111
+
112
+ /** @var iterable<string> $data */
113
+ $ response = new StreamedResponse (
114
+ $ data ,
115
+ $ this ->getStatus ($ request , $ operation , $ context ),
116
+ $ this ->getHeaders ($ request , $ operation , $ context )
117
+ );
118
+
119
+ return $ this ->processor ->process ($ response , $ operation , $ uriVariables , $ context );
120
+ }
121
+
122
+ // TODO: These come from our Hydra collection normalizer, try to share the logic
45
123
private function getSearch (Operation $ operation , string $ requestUri ): IriTemplate
46
124
{
47
125
/** @var list<IriTemplateMapping> */
@@ -67,6 +145,7 @@ private function getSearch(Operation $operation, string $requestUri): IriTemplat
67
145
}
68
146
69
147
$ parts = parse_url ($ requestUri );
148
+
70
149
return new IriTemplate (
71
150
variableRepresentation: 'BasicRepresentation ' ,
72
151
mapping: $ mapping ,
@@ -91,11 +170,11 @@ private function getView(mixed $object, string $requestUri, Operation $operation
91
170
// TODO: This needs to be changed as well as I wrote in the CollectionFiltersNormalizer
92
171
// We should not rely on the request_uri but instead rely on the UriTemplate
93
172
// 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 );
173
+ $ parsed = IriHelper::parseIri ($ requestUri , $ this ->pageParameterName );
95
174
$ appliedFilters = $ parsed ['parameters ' ];
96
175
unset($ appliedFilters [$ this ->enabledParameterName ]);
97
176
98
- $ urlGenerationStrategy = $ operation? ->getUrlGenerationStrategy() ?? $ this ->urlGenerationStrategy ;
177
+ $ urlGenerationStrategy = $ operation ->getUrlGenerationStrategy () ?? $ this ->urlGenerationStrategy ;
99
178
$ id = IriHelper::createIri ($ parsed ['parts ' ], $ parsed ['parameters ' ], $ this ->pageParameterName , $ paginated ? $ currentPage : null , $ urlGenerationStrategy );
100
179
if (!$ appliedFilters && !$ paginated ) {
101
180
return new PartialCollectionView ($ id );
@@ -117,46 +196,4 @@ private function getView(mixed $object, string $requestUri, Operation $operation
117
196
118
197
return new PartialCollectionView ($ id , $ first , $ last , $ previous , $ next );
119
198
}
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
- }
162
199
}
0 commit comments