|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace App\Services; |
|
4
|
|
|
|
|
5
|
|
|
use GuzzleHttp\Client; |
|
6
|
|
|
use GuzzleHttp\Exception\ClientException; |
|
7
|
|
|
use InvalidArgumentException; |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* Class RESTfulService. |
|
11
|
|
|
* |
|
12
|
|
|
* @method object get($uri) |
|
13
|
|
|
* @method object post($uri, ...$data) |
|
14
|
|
|
* @method object put($uri, ...$data) |
|
15
|
|
|
* @method object patch($uri, ...$data) |
|
16
|
|
|
* @method object head($uri, ...$data) |
|
17
|
|
|
* @method object delete($uri) |
|
18
|
|
|
*/ |
|
19
|
|
|
class RESTfulService |
|
20
|
|
|
{ |
|
21
|
|
|
protected $responseFormat = 'json'; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* The API endpoint. |
|
25
|
|
|
* |
|
26
|
|
|
* @var string |
|
27
|
|
|
*/ |
|
28
|
|
|
protected $endpoint; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* The GuzzleHttp client to talk to the API. |
|
32
|
|
|
* |
|
33
|
|
|
* @var Client; |
|
34
|
|
|
*/ |
|
35
|
|
|
protected $client; |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* The API key. |
|
39
|
|
|
* |
|
40
|
|
|
* @var string |
|
41
|
|
|
*/ |
|
42
|
|
|
protected $key; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* The query parameter name for the key. |
|
46
|
|
|
* For example, Last.fm use api_key, like this: |
|
47
|
|
|
* https://ws.audioscrobbler.com/2.0?method=artist.getInfo&artist=Kamelot&api_key=API_KEY. |
|
48
|
|
|
* |
|
49
|
|
|
* @var string |
|
50
|
|
|
*/ |
|
51
|
|
|
protected $keyParam = 'key'; |
|
52
|
|
|
|
|
53
|
|
|
/** |
|
54
|
|
|
* The API secret. |
|
55
|
|
|
* |
|
56
|
|
|
* @var string |
|
57
|
|
|
*/ |
|
58
|
|
|
protected $secret; |
|
59
|
|
|
|
|
60
|
|
|
public function __construct($key, $secret, $endpoint, Client $client) |
|
61
|
|
|
{ |
|
62
|
|
|
$this->setKey($key); |
|
63
|
|
|
$this->setSecret($secret); |
|
64
|
|
|
$this->setEndpoint($endpoint); |
|
65
|
|
|
$this->setClient($client); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
/** |
|
69
|
|
|
* Make a request to the API. |
|
70
|
|
|
* |
|
71
|
|
|
* @param string $verb The HTTP verb |
|
72
|
|
|
* @param string $uri The API URI (segment) |
|
73
|
|
|
* @param bool $appendKey Whether to automatically append the API key into the URI. |
|
74
|
|
|
* While it's usually the case, some services (like Last.fm) requires |
|
75
|
|
|
* an "API signature" of the request. Appending an API key will break the request. |
|
76
|
|
|
* @param array $params An array of parameters |
|
77
|
|
|
* |
|
78
|
|
|
* @return object|string |
|
79
|
|
|
*/ |
|
80
|
|
|
public function request($verb, $uri, $appendKey = true, array $params = []) |
|
81
|
|
|
{ |
|
82
|
|
|
try { |
|
83
|
|
|
$body = (string) $this->getClient() |
|
84
|
|
|
->$verb($this->buildUrl($uri, $appendKey), ['form_params' => $params]) |
|
85
|
|
|
->getBody(); |
|
86
|
|
|
|
|
87
|
|
|
if ($this->responseFormat === 'json') { |
|
88
|
|
|
$response = json_decode($body); |
|
89
|
|
|
|
|
90
|
|
|
if($response) return $response; |
|
91
|
|
|
|
|
92
|
|
|
return $body; |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
if ($this->responseFormat === 'xml') { |
|
96
|
|
|
return simplexml_load_string($body); |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
return $body; |
|
100
|
|
|
} catch (ClientException $e) { |
|
101
|
|
|
return false; |
|
|
|
|
|
|
102
|
|
|
} |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* Make an HTTP call to the external resource. |
|
107
|
|
|
* |
|
108
|
|
|
* @param string $method The HTTP method |
|
109
|
|
|
* @param array $args An array of parameters |
|
110
|
|
|
* |
|
111
|
|
|
* @throws \InvalidArgumentException |
|
112
|
|
|
* |
|
113
|
|
|
* @return object |
|
114
|
|
|
*/ |
|
115
|
|
|
public function __call($method, $args) |
|
116
|
|
|
{ |
|
117
|
|
|
if (count($args) < 1) { |
|
118
|
|
|
throw new InvalidArgumentException('Magic request methods require a URI and optional options array'); |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
$uri = $args[0]; |
|
122
|
|
|
$opts = isset($args[1]) ? $args[1] : []; |
|
123
|
|
|
$appendKey = isset($args[2]) ? $args[2] : true; |
|
124
|
|
|
|
|
125
|
|
|
return $this->request($method, $uri, $appendKey, $opts); |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
/** |
|
129
|
|
|
* Turn a URI segment into a full API URL. |
|
130
|
|
|
* |
|
131
|
|
|
* @param string $uri |
|
132
|
|
|
* @param bool $appendKey Whether to automatically append the API key into the URL. |
|
133
|
|
|
* |
|
134
|
|
|
* @return string |
|
135
|
|
|
*/ |
|
136
|
|
|
public function buildUrl($uri, $appendKey = true) |
|
137
|
|
|
{ |
|
138
|
|
|
if (!starts_with($uri, ['http://', 'https://'])) { |
|
139
|
|
|
if ($uri[0] !== '/') { |
|
140
|
|
|
$uri = "/$uri"; |
|
141
|
|
|
} |
|
142
|
|
|
|
|
143
|
|
|
$uri = $this->endpoint.$uri; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
if ($appendKey) { |
|
147
|
|
|
if (parse_url($uri, PHP_URL_QUERY)) { |
|
148
|
|
|
$uri .= "&{$this->keyParam}=".$this->getKey(); |
|
149
|
|
|
} else { |
|
150
|
|
|
$uri .= "?{$this->keyParam}=".$this->getKey(); |
|
151
|
|
|
} |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
return $uri; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* @return Client |
|
159
|
|
|
*/ |
|
160
|
|
|
public function getClient() |
|
161
|
|
|
{ |
|
162
|
|
|
return $this->client; |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
/** |
|
166
|
|
|
* @param Client $client |
|
167
|
|
|
*/ |
|
168
|
|
|
public function setClient($client) |
|
169
|
|
|
{ |
|
170
|
|
|
$this->client = $client; |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
/** |
|
174
|
|
|
* @return string |
|
175
|
|
|
*/ |
|
176
|
|
|
public function getKey() |
|
177
|
|
|
{ |
|
178
|
|
|
return $this->key; |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
/** |
|
182
|
|
|
* @param string $key |
|
183
|
|
|
*/ |
|
184
|
|
|
public function setKey($key) |
|
185
|
|
|
{ |
|
186
|
|
|
$this->key = $key; |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
/** |
|
190
|
|
|
* @return string |
|
191
|
|
|
*/ |
|
192
|
|
|
public function getSecret() |
|
193
|
|
|
{ |
|
194
|
|
|
return $this->secret; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* @param string $secret |
|
199
|
|
|
*/ |
|
200
|
|
|
public function setSecret($secret) |
|
201
|
|
|
{ |
|
202
|
|
|
$this->secret = $secret; |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* @return string |
|
207
|
|
|
*/ |
|
208
|
|
|
public function getEndpoint() |
|
209
|
|
|
{ |
|
210
|
|
|
return $this->endpoint; |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
/** |
|
214
|
|
|
* @param string $endpoint |
|
215
|
|
|
*/ |
|
216
|
|
|
public function setEndpoint($endpoint) |
|
217
|
|
|
{ |
|
218
|
|
|
$this->endpoint = $endpoint; |
|
219
|
|
|
} |
|
220
|
|
|
} |
|
221
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.