@@ -28,7 +28,7 @@ class Helper
2828 *
2929 * @return ExecutionResult|Promise
3030 */
31- public static function executeOperation (ServerConfig $ config , OperationParams $ op )
31+ public function executeOperation (ServerConfig $ config , OperationParams $ op )
3232 {
3333 $ phpErrors = [];
3434 $ execute = function () use ($ config , $ op ) {
@@ -83,8 +83,12 @@ public static function executeOperation(ServerConfig $config, OperationParams $o
8383 * @param OperationParams $op
8484 * @return string|DocumentNode
8585 */
86- private static function loadPersistedQuery (ServerConfig $ config , OperationParams $ op )
86+ public function loadPersistedQuery (ServerConfig $ config , OperationParams $ op )
8787 {
88+ if (!$ op ->queryId ) {
89+ throw new InvariantViolation ("Could not load persisted query: queryId is not set " );
90+ }
91+
8892 // Load query if we got persisted query id:
8993 $ loader = $ config ->getPersistentQueryLoader ();
9094
@@ -110,7 +114,7 @@ private static function loadPersistedQuery(ServerConfig $config, OperationParams
110114 * @param OperationParams $params
111115 * @return array
112116 */
113- private static function resolveValidationRules (ServerConfig $ config , OperationParams $ params )
117+ public function resolveValidationRules (ServerConfig $ config , OperationParams $ params )
114118 {
115119 // Allow customizing validation rules per operation:
116120 $ validationRules = $ config ->getValidationRules ();
@@ -129,16 +133,85 @@ private static function resolveValidationRules(ServerConfig $config, OperationPa
129133 return $ validationRules ;
130134 }
131135
136+
132137 /**
133138 * Parses HTTP request and returns GraphQL QueryParams contained in this request.
134139 * For batched requests it returns an array of QueryParams.
135140 *
136141 * @return OperationParams|OperationParams[]
137142 */
138- public static function parseHttpRequest ()
143+ public function parseHttpRequest ()
144+ {
145+ list ($ parsedBody , $ isReadonly ) = $ this ->parseRawBody ();
146+ return $ this ->toOperationParams ($ parsedBody , $ isReadonly );
147+ }
148+
149+ /**
150+ * Extracts parsed body and readonly flag from HTTP request
151+ *
152+ * If $readRawBodyFn argument is not provided - will attempt to read raw request body from php://input stream
153+ *
154+ * @param callable|null $readRawBodyFn
155+ * @return array
156+ */
157+ public function parseRawBody (callable $ readRawBodyFn = null )
139158 {
140- $ contentType = isset ($ _SERVER ['CONTENT_TYPE ' ]) ? $ _SERVER ['CONTENT_TYPE ' ] : null ;
159+ $ method = isset ($ _SERVER ['REQUEST_METHOD ' ]) ? $ _SERVER ['REQUEST_METHOD ' ] : null ;
141160
161+ if ($ method === 'GET ' ) {
162+ $ isReadonly = true ;
163+ $ request = array_change_key_case ($ _GET );
164+
165+ if (isset ($ request ['query ' ]) || isset ($ request ['queryid ' ]) || isset ($ request ['documentid ' ])) {
166+ $ body = $ _GET ;
167+ } else {
168+ throw new UserError ('Cannot execute GET request without "query" or "queryId" parameter ' );
169+ }
170+ } else if ($ method === 'POST ' ) {
171+ $ isReadonly = false ;
172+ $ contentType = isset ($ _SERVER ['CONTENT_TYPE ' ]) ? $ _SERVER ['CONTENT_TYPE ' ] : null ;
173+
174+ if (stripos ($ contentType , 'application/graphql ' ) !== false ) {
175+ $ rawBody = $ readRawBodyFn ? $ readRawBodyFn () : $ this ->readRawBody ();
176+ $ body = ['query ' => $ rawBody ?: '' ];
177+ } else if (stripos ($ contentType , 'application/json ' ) !== false ) {
178+ $ rawBody = $ readRawBodyFn ? $ readRawBodyFn () : $ this ->readRawBody ();
179+ $ body = json_decode ($ rawBody ?: '' , true );
180+
181+ if (json_last_error ()) {
182+ throw new UserError ("Could not parse JSON: " . json_last_error_msg ());
183+ }
184+ if (!is_array ($ body )) {
185+ throw new UserError (
186+ "GraphQL Server expects JSON object or array, but got " .
187+ Utils::printSafeJson ($ body )
188+ );
189+ }
190+ } else if (stripos ($ contentType , 'application/x-www-form-urlencoded ' ) !== false ) {
191+ $ body = $ _POST ;
192+ } else if (null === $ contentType ) {
193+ throw new UserError ('Missing "Content-Type" header ' );
194+ } else {
195+ throw new UserError ("Unexpected content type: " . Utils::printSafeJson ($ contentType ));
196+ }
197+ } else {
198+ throw new UserError ('HTTP Method " ' . $ method . '" is not supported ' , 405 );
199+ }
200+ return [
201+ $ body ,
202+ $ isReadonly
203+ ];
204+ }
205+
206+ /**
207+ * Converts parsed body to OperationParams (or list of OperationParams for batched request)
208+ *
209+ * @param $parsedBody
210+ * @param $isReadonly
211+ * @return OperationParams|OperationParams[]
212+ */
213+ public function toOperationParams ($ parsedBody , $ isReadonly )
214+ {
142215 $ assertValid = function (OperationParams $ opParams , $ queryNum = null ) {
143216 $ errors = $ opParams ->validate ();
144217 if (!empty ($ errors [0 ])) {
@@ -147,45 +220,69 @@ public static function parseHttpRequest()
147220 }
148221 };
149222
150- if (stripos ($ contentType , 'application/graphql ' !== false )) {
151- $ body = file_get_contents ('php://input ' ) ?: '' ;
152- $ op = OperationParams::create (['query ' => $ body ]);
153- $ assertValid ($ op );
154- } else if (stripos ($ contentType , 'application/json ' ) !== false || stripos ($ contentType , 'text/json ' ) !== false ) {
155- $ body = file_get_contents ('php://input ' ) ?: '' ;
156- $ data = json_decode ($ body , true );
157-
158- if (json_last_error ()) {
159- throw new UserError ("Could not parse JSON: " . json_last_error_msg ());
223+ if (isset ($ parsedBody [0 ])) {
224+ // Batched query
225+ $ result = [];
226+ foreach ($ parsedBody as $ index => $ entry ) {
227+ $ op = OperationParams::create ($ entry , $ isReadonly );
228+ $ assertValid ($ op , $ index );
229+ $ result [] = $ op ;
160230 }
161- if (!is_array ($ data )) {
162- throw new UserError (
163- "GraphQL Server expects JSON object or array, but got %s " .
164- Utils::printSafe ($ data )
165- );
166- }
167- if (isset ($ data [0 ])) {
168- $ op = [];
169- foreach ($ data as $ index => $ entry ) {
170- $ params = OperationParams::create ($ entry );
171- $ assertValid ($ params , $ index );
172- $ op [] = $ params ;
231+ } else {
232+ $ result = OperationParams::create ($ parsedBody , $ isReadonly );
233+ $ assertValid ($ result );
234+ }
235+ return $ result ;
236+ }
237+
238+ /**
239+ * @return bool|string
240+ */
241+ public function readRawBody ()
242+ {
243+ return file_get_contents ('php://input ' );
244+ }
245+
246+ /**
247+ * Assertion to check that parsed body is valid instance of OperationParams (or array of instances)
248+ *
249+ * @param $method
250+ * @param $parsedBody
251+ */
252+ public function assertBodyIsParsedProperly ($ method , $ parsedBody )
253+ {
254+ if (is_array ($ parsedBody )) {
255+ foreach ($ parsedBody as $ index => $ entry ) {
256+ if (!$ entry instanceof OperationParams) {
257+ throw new InvariantViolation (sprintf (
258+ '%s expects instance of %s or array of instances. Got invalid array where entry at position %d is %s ' ,
259+ $ method ,
260+ OperationParams::class,
261+ $ index ,
262+ Utils::printSafe ($ entry )
263+ ));
264+ }
265+ $ errors = $ entry ->validate ();
266+
267+ if (!empty ($ errors [0 ])) {
268+ $ err = $ index ? "Error in query # $ index: {$ errors [0 ]}" : $ errors [0 ];
269+ throw new InvariantViolation ($ err );
173270 }
174- } else {
175- $ op = OperationParams::create ($ data );
176- $ assertValid ($ op );
177271 }
178- } else if (stripos ($ contentType , 'application/x-www-form-urlencoded ' ) !== false ) {
179- if ($ _SERVER ['REQUEST_METHOD ' ] === 'GET ' ) {
180- $ op = OperationParams::create ($ _GET , false );
181- } else {
182- $ op = OperationParams::create ($ _POST );
272+ }
273+
274+ if ($ parsedBody instanceof OperationParams) {
275+ $ errors = $ parsedBody ->validate ();
276+ if (!empty ($ errors [0 ])) {
277+ throw new InvariantViolation ($ errors [0 ]);
183278 }
184- $ assertValid ($ op );
185- } else {
186- throw new UserError ("Bad request: unexpected content type: " . Utils::printSafe ($ contentType ));
187279 }
188280
189- return $ op ;
281+ throw new InvariantViolation (sprintf (
282+ '%s expects instance of %s or array of instances, but got %s ' ,
283+ $ method ,
284+ OperationParams::class,
285+ Utils::printSafe ($ parsedBody )
286+ ));
190287 }
191288}
0 commit comments