1 <?php
2 /**
3 * Copyright 2019 Klarna AB
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * File containing the abstract base resource class.
18 */
19
20 namespace Klarna\Rest;
21
22 use GuzzleHttp\Exception\RequestException;
23 use Klarna\Rest\Transport\ConnectorInterface;
24 use Klarna\Rest\Transport\Method;
25 use Klarna\Rest\Transport\Exception\ConnectorException;
26 use Klarna\Rest\Transport\ResponseValidator;
27
28 /**
29 * Abstract resource class.
30 */
31 abstract class Resource extends \ArrayObject
32 {
33 /**
34 * Id property field name.
35 */
36 const ID_FIELD = 'id';
37
38 /**
39 * Path to the resource endpoint.
40 *
41 * @var string
42 */
43 public static $path;
44
45 /**
46 * HTTP transport connector instance.
47 *
48 * @var Connector
49 */
50 protected $connector;
51
52 /**
53 * Url to the resource.
54 *
55 * @var string
56 */
57 protected $url;
58
59 /**
60 * Constructs a resource instance.
61 *
62 * @param ConnectorInterface $connector HTTP transport instance.
63 */
64 public function __construct(ConnectorInterface $connector)
65 {
66 $this->connector = $connector;
67 }
68
69 /**
70 * Gets the resource id.
71 *
72 * @return string|null
73 */
74 public function getId()
75 {
76 return isset($this[static::ID_FIELD]) ? $this[static::ID_FIELD] : null;
77 }
78
79 /**
80 * Gets the resource location.
81 *
82 * @return string|null
83 */
84 public function getLocation()
85 {
86 return $this->url;
87 }
88
89 /**
90 * Sets the resource location.
91 *
92 * @param string $url Url to the resource
93 *
94 * @return self
95 */
96 public function setLocation($url)
97 {
98 $this->url = $url;
99
100 return $this;
101 }
102
103 /**
104 * Fetches the resource.
105 *
106 * @throws ConnectorException When the API replies with an error response
107 * @throws RequestException When an error is encountered
108 * @throws \RuntimeException On an unexpected API response
109 * @throws \RuntimeException If the response content type is not JSON
110 * @throws \InvalidArgumentException If the JSON cannot be parsed
111 * @throws \LogicException When Guzzle cannot populate the response
112 *
113 * @return self
114 */
115 public function fetch()
116 {
117 $data = $this->get($this->getLocation())
118 ->expectSuccessfull()
119 ->status('200')
120 ->contentType('application/json')
121 ->getJson();
122
123 $this->exchangeArray($data);
124
125 return $this;
126 }
127
128 /**
129 * Sends a HTTP request to the specified url.
130 *
131 * @param string $method HTTP method, e.g. 'GET'
132 * @param string $url Request destination
133 * @param array $headers
134 * @param string $body
135 *
136 * @throws ConnectorException When the API replies with an error response
137 * @throws RequestException When an error is encountered
138 * @throws \LogicException When Guzzle cannot populate the response'
139 * @return ResponseValidator When the API replies with an error response
140 *
141 */
142 protected function request($method, $url, array $headers = [], $body = null)
143 {
144 $debug = getenv('DEBUG_SDK') || defined('DEBUG_SDK');
145
146 if ($debug) {
147 $methodDebug = str_pad($method, 7, ' ', STR_PAD_LEFT);
148 $debugHeaders = json_encode($headers);
149 echo <<<DEBUG_BODY
150 DEBUG MODE: Request
151 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
152 {$methodDebug} : {$url}
153 Headers : $debugHeaders
154 Body : {$body}
155 \n
156 DEBUG_BODY;
157 }
158
159 switch ($method) {
160 case Method::GET:
161 $response = $this->connector->get($url, $headers);
162 break;
163 case Method::POST:
164 $response = $this->connector->post($url, $body, $headers);
165 break;
166 case Method::PUT:
167 $response = $this->connector->put($url, $body, $headers);
168 break;
169 case Method::DELETE:
170 $response = $this->connector->delete($url, $body, $headers);
171 break;
172 case Method::PATCH:
173 $response = $this->connector->patch($url, $body, $headers);
174 break;
175 default:
176 throw new \RuntimeException('Unknown request method ' + $method);
177 }
178
179 if ($debug) {
180 $debugHeaders = json_encode($response->getHeaders());
181 echo <<<DEBUG_BODY
182 DEBUG MODE: Response
183 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
184 Headers : $debugHeaders
185 Body : {$response->getBody()}
186 \n
187 DEBUG_BODY;
188 }
189
190 $location = $response->getLocation();
191 if (!empty($location)) {
192 $this->setLocation($location);
193 }
194
195 return new ResponseValidator($response);
196 }
197
198 /**
199 * Sends a HTTP GET request to the specified url.
200 *
201 * @param string $url Request destination
202 *
203 * @throws ConnectorException When the API replies with an error response
204 * @throws RequestException When an error is encountered
205 * @throws \LogicException When Guzzle cannot populate the response
206 *
207 * @return ResponseValidator
208 */
209 protected function get($url)
210 {
211 return $this->request('GET', $url);
212 }
213
214 /**
215 * Sends a HTTP DELETE request to the specified url.
216 *
217 * @param string $url Request destination
218 * @param array $data Data to be JSON encoded
219 *
220 * @throws ConnectorException When the API replies with an error response
221 * @throws RequestException When an error is encountered
222 * @throws \LogicException When Guzzle cannot populate the response
223 *
224 * @return ResponseValidator
225 */
226 protected function delete($url, array $data = null)
227 {
228 return $this->request(
229 'DELETE',
230 $url,
231 ['Content-Type' => 'application/json'],
232 $data !== null ? json_encode($data) : null
233 );
234 }
235
236 /**
237 * Sends a HTTP PATCH request to the specified url.
238 *
239 * @param string $url Request destination
240 * @param array $data Data to be JSON encoded
241 *
242 * @throws ConnectorException When the API replies with an error response
243 * @throws RequestException When an error is encountered
244 * @throws \LogicException When Guzzle cannot populate the response
245 *
246 * @return ResponseValidator
247 */
248 protected function patch($url, array $data)
249 {
250 return $this->request(
251 'PATCH',
252 $url,
253 ['Content-Type' => 'application/json'],
254 json_encode($data)
255 );
256 }
257
258 /**
259 * Sends a HTTP PUT request to the specified url.
260 *
261 * @param string $url Request destination
262 * @param array $data Data to be JSON encoded
263 *
264 * @throws ConnectorException When the API replies with an error response
265 * @throws RequestException When an error is encountered
266 * @throws \LogicException When Guzzle cannot populate the response
267 *
268 * @return ResponseValidator
269 */
270 protected function put($url, array $data)
271 {
272 return $this->request(
273 'PUT',
274 $url,
275 ['Content-Type' => 'application/json'],
276 json_encode($data)
277 );
278 }
279
280 /**
281 * Sends a HTTP POST request to the specified url.
282 *
283 * @param string $url Request destination
284 * @param array $data Data to be JSON encoded
285 *
286 * @throws ConnectorException When the API replies with an error response
287 * @throws RequestException When an error is encountered
288 * @throws \LogicException When Guzzle cannot populate the response
289 *
290 * @return ResponseValidator
291 */
292 protected function post($url, array $data = null)
293 {
294 return $this->request(
295 'POST',
296 $url,
297 ['Content-Type' => 'application/json'],
298 $data !== null ? \json_encode($data) : null
299 );
300 }
301 }
302