Methods

Source

LineCode
1
<?php
2
3
namespace AuthStack\UserDirectory\Service;
4
5
use AuthStack\UserDirectory\Exceptions\JsonDecodingException;
6
use AuthStack\UserDirectory\Exceptions\TransferException;
7
use AuthStack\UserDirectory\Exceptions\UserDirectoryException;
8
use Buckhill\Support\Traits\DotNotationAccessorTrait;
9
use GuzzleHttp\Client;
10
use Psr\Http\Message\ResponseInterface;
11
use GuzzleHttp\Exception\RequestException;
12
13
abstract class RESTService
14
{
15
    use DotNotationAccessorTrait;
16
17
    protected $httpClient;
18
    protected $baseUrl;
19
    protected $config = [];
20
    protected $tz_offset = null;
21
    protected $tz_dst = null;
22
23
    public function __construct($baseUrl = '', array $options = [], array $routes = [], string $tz_offset = null, string $tz_dst = null)
24
    {
25
        $this->baseUrl = $baseUrl;
26
27
        $this->config = [
28
            'options' => $options,
29
            'routes' => $routes
30
        ];
31
32
        $this->tz_offset = $tz_offset;
33
        $this->tz_dst = $tz_dst;
34
    }
35
36
    public function setTimeZoneOffset(string $offset): self
37
    {
38
        $this->tz_offset = $offset;
39
40
        return $this;
41
    }
42
43
    public function setTimeZoneDST(string $dst): self
44
    {
45
        $this->tz_dst = $dst;
46
47
        return $this;
48
    }
49
50
    public function getHttpClient(): Client
51
    {
52
        if(!$this->httpClient instanceof Client)
53
        {
54
            $this->httpClient = new Client();
55
        }
56
57
        return $this->httpClient;
58
    }
59
60
    protected function section($section)
61
    {
62
        $verb       = $this->config(sprintf("routes.%s.method", $section));
63
        $path       = sprintf("%s%s", $this->getBaseUrl(), $this->config(sprintf("routes.%s.path", $section)));
64
        $verify     = $this->config(sprintf("routes.%s.verify", $section), false);
65
66
        return [
67
            'method' => $verb,
68
            'path' => $path,
69
            'verify' => $verify
70
        ];
71
    }
72
73
    protected function config($path, $default = null)
74
    {
75
        return $this->dotNotationFromArray($this->config, $path, $default);
76
    }
77
78
    /**
79
     * @param array $options
80
     * @return array
81
     */
82
    protected function options(array $options)
83
    {
84
        return array_merge($this->config('options', []), $options);
85
    }
86
87
    /**
88
     * @return string
89
     */
90
    protected function getBaseUrl()
91
    {
92
        return $this->baseUrl;
93
    }
94
95
    /**
96
     * @param callable $callback
97
     * @param $nextException
98
     * @param callable|null $afterException
99
     * @return mixed
100
     */
101
    protected function remote(callable $callback, $nextException = UserDirectoryException::class, callable $afterException = null)
102
    {
103
        try
104
        {
105
            return $callback();
106
        }
107
        catch(\Exception $e)
108
        {
109
            // Useful for error logging
110
            if(is_callable($afterException))
111
            {
112
                $exception = $this->getExceptionFromResponse($e, $nextException);
113
114
                $afterException($exception);
115
            }
116
117
            $this->handleException($e, $nextException);
118
        }
119
    }
120
121
    /**
122
     * @param $cfg_section
123
     * @param array|null $form_params
124
     * @param array|null $path_params
125
     * @param array|null $get_params
126
     * @return mixed|ResponseInterface
127
     */
128
    protected function request($cfg_section, array $form_params = null, array $path_params = null, array $get_params = null)
129
    {
130
        $client = $this->getHttpClient();
131
        $section = $this->section($cfg_section);
132
133
        $path = !empty($path_params) ? vsprintf($section['path'], $path_params) : $section['path'];
134
135
        $options = array_merge($this->config['options'], ['verify' => $section['verify']]);
136
137
        // Add time zone headers
138
        if(!is_null($this->tz_offset) && !is_null($this->tz_dst))
139
        {
140
            if(!isset($options['headers'])) $options['headers'] = [];
141
142
            $options['headers']['X-TimeZoneOffset'] = $this->tz_offset;
143
            $options['headers']['X-TimeZoneDST'] = $this->tz_dst;
144
        }
145
146
        if(!empty($form_params))
147
        {
148
            $options['form_params'] = $form_params;
149
        }
150
151
        if(!empty($get_params))
152
        {
153
            $options['query'] = $get_params;
154
        }
155
156
        return $client->request($section['method'], $path, $options);
157
    }
158
159
    /**
160
     * @param array ...$params
161
     * @return array
162
     * @throws JsonDecodingException
163
     */
164
    protected function json(...$params): array
165
    {
166
        $result = $this->request(...$params);
167
168
        $json = @json_decode($result->getBody(), true);
169
170
        if(json_last_error() !== JSON_ERROR_NONE)
171
        {
172
            throw new JsonDecodingException(json_last_error_msg(), json_last_error());
173
        }
174
175
        return $json;
176
    }
177
178
    /**
179
     * @param RequestException $e
180
     * @param $nextException
181
     * @throws TransferException
182
     * @throws UserDirectoryException
183
     */
184
    protected function handleException(\Exception $e, $nextException)
185
    {
186
        if(method_exists($e, 'getResponse'))
187
        {
188
            $response = $e->getResponse();
189
190
            if($this->isJson($response))
191
            {
192
                $body = @json_decode($e->getResponse()->getBody(), true);
193
194
                $message = isset($body) && is_array($body) ? $body : $e->getResponse()->getBody()->getContents();
195
196
                throw new $nextException($message, (int)$e->getCode());
197
            }
198
199
            throw new TransferException($e->getResponse()->getBody()->getContents(), $e->getCode());
200
        }
201
202
        throw new UserDirectoryException($e->getMessage(), $e->getCode());
203
    }
204
205
    /**
206
     * @param \Exception $e
207
     * @param $nextException
208
     * @return \Exception
209
     */
210
    protected function getExceptionFromResponse(\Exception $e, $nextException): \Exception
211
    {
212
        if(method_exists($e, 'getResponse'))
213
        {
214
            $response = $e->getResponse();
215
216
            if($this->isJson($response))
217
            {
218
                $body = @json_decode($e->getResponse()->getBody(), true);
219
220
                $message = isset($body) && is_array($body) ? $body : $e->getResponse()->getBody()->getContents();
221
222
                return new $nextException($message, (int)$e->getCode());
223
            }
224
        }
225
226
        return new $nextException(['message' => 'Uknown error during transfer'], 500);
227
    }
228
229
    /**
230
     * @param ResponseInterface $response
231
     * @return bool
232
     */
233
    protected function isJson(ResponseInterface $response)
234
    {
235
        $result = false;
236
237
        if($response->hasHeader('content-type'))
238
        {
239
            $contentType = $response->getHeader('content-type');
240
241
            if(is_string($contentType))
242
            {
243
                $result = 0 === strcmp($contentType, 'application/json');
244
            }
245
246
            if(is_array($contentType))
247
            {
248
                foreach($contentType as $ctype)
249
                {
250
                    $result = 0 === strcmp($ctype, 'application/json');
251
252
                    if($result)
253
                        break;
254
                }
255
            }
256
        }
257
258
        return $result;
259
    }
260
}