Test Setup Failed
Pull Request — master (#623)
by Alejandro Carstens
09:06
created

RESTfulService   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 202
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 0
dl 0
loc 202
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B request() 0 24 5
A __call() 0 12 4
B buildUrl() 0 20 5
A getClient() 0 4 1
A setClient() 0 4 1
A getKey() 0 4 1
A setKey() 0 4 1
A getSecret() 0 4 1
A setSecret() 0 4 1
A getEndpoint() 0 4 1
A setEndpoint() 0 4 1
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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by App\Services\RESTfulService::request of type object|string.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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