1212use ProgrammatorDev \Api \Builder \ClientBuilder ;
1313use ProgrammatorDev \Api \Builder \Listener \CacheLoggerListener ;
1414use ProgrammatorDev \Api \Builder \LoggerBuilder ;
15+ use ProgrammatorDev \Api \Builder \ResponseBuilder ;
1516use ProgrammatorDev \Api \Event \PostRequestEvent ;
1617use ProgrammatorDev \Api \Event \PreRequestEvent ;
1718use ProgrammatorDev \Api \Event \ResponseContentsEvent ;
1819use ProgrammatorDev \Api \Helper \StringHelper ;
20+ use ProgrammatorDev \Api \Request \RequestOptions ;
1921use Psr \Http \Client \ClientExceptionInterface as ClientException ;
2022use Psr \Http \Message \RequestInterface ;
23+ use Psr \Http \Message \ResponseInterface ;
2124use Psr \Http \Message \StreamInterface ;
2225use Symfony \Component \EventDispatcher \EventDispatcher ;
2326
@@ -37,11 +40,14 @@ class Api
3740
3841 private ?Authentication $ authentication = null ;
3942
43+ private ResponseBuilder $ responseBuilder ;
44+
4045 private EventDispatcher $ eventDispatcher ;
4146
4247 public function __construct ()
4348 {
4449 $ this ->clientBuilder ??= new ClientBuilder ();
50+ $ this ->responseBuilder = new ResponseBuilder ();
4551 $ this ->eventDispatcher = new EventDispatcher ();
4652 }
4753
@@ -53,30 +59,10 @@ public function request(
5359 string $ path ,
5460 array $ query = [],
5561 array $ headers = [],
56- string |StreamInterface $ body = null
62+ string |StreamInterface | null $ body = null
5763 ): mixed
5864 {
59- $ this ->configurePlugins ();
60-
61- if (!empty ($ this ->queryDefaults )) {
62- $ query = array_merge ($ this ->queryDefaults , $ query );
63- }
64-
65- if (!empty ($ this ->headerDefaults )) {
66- $ headers = array_merge ($ this ->headerDefaults , $ headers );
67- }
68-
69- $ url = $ this ->buildUrl ($ path , $ query );
70- $ request = $ this ->createRequest ($ method , $ url , $ headers , $ body );
71-
72- // pre request listener
73- $ request = $ this ->eventDispatcher ->dispatch (new PreRequestEvent ($ request ))->getRequest ();
74-
75- // request
76- $ response = $ this ->clientBuilder ->getClient ()->sendRequest ($ request );
77-
78- // post request listener
79- $ response = $ this ->eventDispatcher ->dispatch (new PostRequestEvent ($ request , $ response ))->getResponse ();
65+ $ response = $ this ->sendRequest ($ method , $ path , $ query , $ headers , $ body );
8066
8167 // always rewind the body contents in case it was used in the PostRequestEvent
8268 // otherwise it would return an empty string
@@ -87,6 +73,73 @@ public function request(
8773 return $ this ->eventDispatcher ->dispatch (new ResponseContentsEvent ($ contents ))->getContents ();
8874 }
8975
76+ /**
77+ * @internal
78+ * @throws ClientException
79+ */
80+ public function send (
81+ string $ method ,
82+ string $ path ,
83+ array $ pathParams = [],
84+ ?RequestOptions $ options = null
85+ ): Response
86+ {
87+ $ options ??= new RequestOptions ();
88+ $ path = $ this ->buildPath ($ path , $ pathParams );
89+
90+ $ response = $ this ->sendRequest (
91+ method: $ method ,
92+ path: $ path ,
93+ query: $ options ->getQuery (),
94+ headers: $ options ->getHeaders ()
95+ );
96+
97+ return new Response (
98+ data: $ this ->getResponseData ($ response ),
99+ rawResponse: $ response
100+ );
101+ }
102+
103+ /**
104+ * @template T of Resource
105+ * @param class-string<T> $class
106+ * @return T
107+ */
108+ protected function resource (string $ class ): Resource
109+ {
110+ return new $ class ($ this );
111+ }
112+
113+ protected function baseUrl (?string $ baseUrl ): static
114+ {
115+ $ this ->setBaseUrl ($ baseUrl );
116+
117+ return $ this ;
118+ }
119+
120+ protected function queryDefaults (array $ query ): static
121+ {
122+ foreach ($ query as $ name => $ value ) {
123+ $ this ->addQueryDefault ($ name , $ value );
124+ }
125+
126+ return $ this ;
127+ }
128+
129+ protected function headerDefaults (array $ headers ): static
130+ {
131+ foreach ($ headers as $ name => $ value ) {
132+ $ this ->addHeaderDefault ($ name , $ value );
133+ }
134+
135+ return $ this ;
136+ }
137+
138+ protected function responses (): ResponseBuilder
139+ {
140+ return $ this ->responseBuilder ;
141+ }
142+
90143 private function configurePlugins (): void
91144 {
92145 // https://docs.php-http.org/en/latest/plugins/content-type.html
@@ -268,7 +321,7 @@ public function buildPath(string $path, array $parameters): string
268321 foreach ($ parameters as $ parameter => $ value ) {
269322 $ path = str_replace (
270323 sprintf ('{%s} ' , $ parameter ),
271- $ value ,
324+ rawurlencode (( string ) $ value) ,
272325 $ path
273326 );
274327 }
@@ -278,7 +331,8 @@ public function buildPath(string $path, array $parameters): string
278331
279332 private function buildUrl (string $ path , array $ query = []): string
280333 {
281- $ appendQuery = http_build_query ($ query );
334+ $ query = array_filter ($ query , static fn (mixed $ value ): bool => $ value !== null );
335+ $ appendQuery = http_build_query ($ query , '' , '& ' , PHP_QUERY_RFC3986 );
282336
283337 if (StringHelper::isUrl ($ path )) {
284338 return append_query_string ($ path , $ appendQuery , APPEND_QUERY_STRING_REPLACE_DUPLICATE );
@@ -292,7 +346,7 @@ private function createRequest(
292346 string $ method ,
293347 string $ url ,
294348 array $ headers = [],
295- string |StreamInterface $ body = null
349+ string |StreamInterface | null $ body = null
296350 ): RequestInterface
297351 {
298352 $ request = $ this ->clientBuilder ->getRequestFactory ()->createRequest ($ method , $ url );
@@ -309,4 +363,54 @@ private function createRequest(
309363
310364 return $ request ;
311365 }
312- }
366+
367+ /**
368+ * @throws ClientException
369+ */
370+ private function sendRequest (
371+ string $ method ,
372+ string $ path ,
373+ array $ query = [],
374+ array $ headers = [],
375+ string |StreamInterface |null $ body = null
376+ ): ResponseInterface
377+ {
378+ $ this ->configurePlugins ();
379+
380+ if (!empty ($ this ->queryDefaults )) {
381+ $ query = array_merge ($ this ->queryDefaults , $ query );
382+ }
383+
384+ if (!empty ($ this ->headerDefaults )) {
385+ $ headers = array_merge ($ this ->headerDefaults , $ headers );
386+ }
387+
388+ $ url = $ this ->buildUrl ($ path , $ query );
389+ $ request = $ this ->createRequest ($ method , $ url , $ headers , $ body );
390+
391+ // pre request listener
392+ $ request = $ this ->eventDispatcher ->dispatch (new PreRequestEvent ($ request ))->getRequest ();
393+
394+ // request
395+ $ response = $ this ->clientBuilder ->getClient ()->sendRequest ($ request );
396+
397+ // post request listener
398+ return $ this ->eventDispatcher ->dispatch (new PostRequestEvent ($ request , $ response ))->getResponse ();
399+ }
400+
401+ private function getResponseData (ResponseInterface $ response ): mixed
402+ {
403+ $ response ->getBody ()->rewind ();
404+ $ contents = $ response ->getBody ()->getContents ();
405+
406+ if (!$ this ->responseBuilder ->shouldDecodeJson ()) {
407+ return $ contents ;
408+ }
409+
410+ if ($ contents === '' ) {
411+ return null ;
412+ }
413+
414+ return json_decode ($ contents , true , flags: JSON_THROW_ON_ERROR );
415+ }
416+ }
0 commit comments