Completed
Push — master ( 240e40...5c6ab1 )
by Vuong
04:20
created

BaseGateway   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 378
Duplicated Lines 0 %

Test Coverage

Coverage 86.46%

Importance

Changes 0
Metric Value
eloc 99
dl 0
loc 378
ccs 83
cts 96
cp 0.8646
rs 9.6
c 0
b 0
f 0
wmc 35

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getDefaultClient() 0 13 3
A getHttpClientConfig() 0 3 1
A beforeRequest() 0 3 1
A request() 0 30 2
A setDefaultClient() 0 7 1
A getHttpClient() 0 10 3
A getSupportedVersions() 0 3 1
A getClient() 0 22 6
A requestCommands() 0 15 4
A defaultVersion() 0 3 1
A setClients() 0 7 2
A getVersion() 0 6 2
A hasClient() 0 3 1
A getClients() 0 8 2
A afterRequest() 0 3 1
A setClient() 0 9 2
A setVersion() 0 10 2
1
<?php
2
/**
3
 * @link https://github.com/vuongxuongminh/yii2-gateway-clients
4
 * @copyright Copyright (c) 2018 Vuong Xuong Minh
5
 * @license [New BSD License](http://www.opensource.org/licenses/bsd-license.php)
6
 */
7
8
namespace vxm\gatewayclients;
9
10
use Yii;
11
use ReflectionClass;
12
13
use GatewayClients\ClientInterface;
14
use GatewayClients\DataInterface;
15
use GatewayClients\GatewayInterface;
16
17
use yii\base\Component;
18
use yii\base\InvalidArgumentException;
19
use yii\base\InvalidConfigException;
20
use yii\base\NotSupportedException;
21
use yii\helpers\ArrayHelper;
22
use yii\httpclient\Client as HttpClient;
23
24
25
/**
26
 * Class BaseClient a base class that implements the [[GatewayInterface]].
27
 * It is an abstraction layer, implements classes will be add more properties to support create request to gateway server api.
28
 *
29
 * @property BaseClient $defaultClient Default client of gateway.
30
 * @property BaseClient $client Default client of gateway.
31
 * @property array|string[] $supportedVersions The list supported versions of gateway.
32
 *
33
 * @author Vuong Minh <[email protected]>
34
 * @since 1.0
35
 */
36
abstract class BaseGateway extends Component implements GatewayInterface
37
{
38
39
    /**
40
     * @event RequestEvent an event that is triggered at the beginning of request to gateway server api.
41
     */
42
    const EVENT_BEFORE_REQUEST = 'beforeRequest';
43
44
    /**
45
     * @event RequestEvent an event that is triggered at the end of requested to gateway server api.
46
     */
47
    const EVENT_AFTER_REQUEST = 'afterRequest';
48
49
    /**
50
     * @var array config use for setup properties of all request data to send to gateway server api. It called by [[request()]].
51
     * An extend class must be override it for config default request data class.
52
     */
53
    public $requestDataConfig = [];
54
55
    /**
56
     * @var array config use for setup properties of all response data get from gateway server api. It called by [[request()]].
57
     * An extend class must be override it for config default response data class.
58
     */
59
    public $responseDataConfig = [];
60
61
    /**
62
     * @var array config of client use for setup properties of the clients list.
63
     * An extend class must be override it for config default client class.
64
     */
65
    public $clientConfig = [];
66
67
    /**
68
     * @var array config of `yii\httpclient\Client` use for send request to api server.
69
     * @since 2.0.1
70
     */
71
    public $httpClientConfig = [];
72
73
    /**
74
     * The clients list.
75
     *
76
     * @var array|BaseClient[]
77
     */
78
    private $_clients = [];
79
80
    /**
81
     * @var string A currently version of gateway
82
     */
83
    private $_version;
84
85
    /**
86
     * @inheritdoc
87
     */
88 6
    public function getVersion(): string
89
    {
90 6
        if ($this->_version === null) {
91 5
            return $this->_version = $this->defaultVersion();
92
        } else {
93 6
            return $this->_version;
94
        }
95
    }
96
97
    /**
98
     * This method return list supported versions of gateway.
99
     *
100
     * @return array|string[] List supported versions.
101
     */
102
    public function getSupportedVersions(): array
103
    {
104
        return [$this->getVersion()];
105
    }
106
107
    /**
108
     * This method support set version of gateway.
109
     *
110
     * @param string $version of gateway
111
     * @return bool Return TRUE if version has been set.
112
     * @throws NotSupportedException
113
     */
114 1
    public function setVersion(string $version): bool
115
    {
116 1
        $supportedVersions = $this->getSupportedVersions();
117
118 1
        if (in_array($version, $supportedVersions, true)) {
119 1
            $this->_version = $version;
120
121 1
            return true;
122
        } else {
123 1
            throw new NotSupportedException("Version `$version` is not supported in " . __CLASS__ . "! Supported versions: " . implode(' - ', $supportedVersions));
124
        }
125
    }
126
127
    /**
128
     * This method return default version of gateway if `version` is not set.
129
     *
130
     * @return string
131
     */
132
    protected function defaultVersion(): string
133
    {
134
        return '1.0';
135
    }
136
137
    /**
138
     * @inheritdoc
139
     * @throws InvalidConfigException
140
     */
141
    public function getClients(): array
142
    {
143
        $clients = [];
144
        foreach ($this->_clients as $id => $client) {
145
            $clients[$id] = $this->getClient($id);
146
        }
147
148
        return $clients;
149
    }
150
151
    /**
152
     * @inheritdoc
153
     * @throws InvalidConfigException
154
     */
155 2
    public function setClients(array $clients): bool
156
    {
157 2
        foreach ($clients as $id => $client) {
158 2
            $this->setClient($id, $client);
159
        }
160
161 2
        return true;
162
    }
163
164
    /**
165
     * @inheritdoc
166
     * @throws InvalidConfigException|InvalidArgumentException
167
     */
168 14
    public function getClient($id = null): ClientInterface
169
    {
170 14
        if ($id === null) {
171 12
            return $this->getDefaultClient();
172 11
        } elseif ($this->hasClient($id)) {
173 11
            $client = $this->_clients[$id];
174
175 11
            if (is_string($client)) {
176
                $client = ['class' => $client];
177
            }
178
179 11
            if (is_array($client)) {
180 10
                $client = ArrayHelper::merge($this->clientConfig, $client);
181
            }
182
183 11
            if (!$client instanceof BaseClient) {
184 10
                $client = $this->_clients[$id] = Yii::createObject($client, [$this]);
185
            }
186
187 11
            return $client;
188
        } else {
189
            throw new InvalidArgumentException("An id client: `$id` not exist!");
190
        }
191
    }
192
193
    /**
194
     * @var BaseClient|ClientInterface The default client value.
195
     */
196
    private $_defaultClient;
197
198
    /**
199
     * @inheritdoc
200
     * @return BaseClient|ClientInterface
201
     * @throws InvalidConfigException
202
     */
203 12
    public function getDefaultClient(): ClientInterface
204
    {
205 12
        if ($this->_defaultClient === null) {
206 12
            if (!empty($this->_clients)) {
207 9
                $ids = array_keys($this->_clients);
208 9
                $id = array_pop($ids);
209
210 9
                return $this->_defaultClient = $this->getClient($id);
211
            } else {
212 3
                throw new InvalidConfigException('Can not detect default client from an empty client lists!');
213
            }
214
        } else {
215 9
            return $this->_defaultClient;
216
        }
217
    }
218
219
    /**
220
     * @inheritdoc
221
     * @throws InvalidConfigException
222
     */
223 11
    public function setClient($id, $client = null): bool
224
    {
225 11
        if ($client === null) {
226 9
            $this->setDefaultClient($id);
227
        } else {
228 6
            $this->_clients[$id] = $client;
229
        }
230
231 11
        return true;
232
    }
233
234
    /**
235
     * @inheritdoc
236
     * @throws InvalidConfigException
237
     */
238 9
    public function setDefaultClient($client): bool
239
    {
240 9
        array_push($this->_clients, $client);
241 9
        $this->_defaultClient = null;
242 9
        $this->getDefaultClient();
243
244 9
        return true;
245
    }
246
247
    /**
248
     * @inheritdoc
249
     */
250 11
    public function hasClient($id): bool
251
    {
252 11
        return array_key_exists($id, $this->_clients);
253
    }
254
255
    /**
256
     * @inheritdoc
257
     * @return ResponseData|DataInterface
258
     * @throws InvalidConfigException|InvalidArgumentException|\ReflectionException
259
     */
260 13
    public function request($command, array $data, $clientId = null): DataInterface
261
    {
262 13
        if (in_array($command, $this->requestCommands(), true)) {
263 9
            $client = $this->getClient($clientId);
264
265
            /**
266
             * @var RequestData $requestData
267
             * @var RequestEvent $event
268
             * @var ResponseData $responseData
269
             */
270
271 6
            $requestData = Yii::createObject($this->requestDataConfig, [$command, $data, $client]);
272 6
            $event = Yii::createObject([
273 6
                'class' => RequestEvent::class,
274 6
                'command' => $command,
275 6
                'client' => $client,
276 6
                'requestData' => $requestData
277
            ]);
278
279 6
            $this->beforeRequest($event);
280 6
            $httpClient = $this->getHttpClient();
281 6
            $data = $this->requestInternal($requestData, $httpClient);
282 5
            $responseData = Yii::createObject($this->responseDataConfig, [$command, $data, $client]);
283 5
            $event->responseData = $responseData;
284 5
            $this->afterRequest($event);
285 5
            Yii::debug(__CLASS__ . " requested sent with command: `$command` - version: " . $this->getVersion());
286
287 5
            return $responseData;
288
        } else {
289 4
            throw new InvalidArgumentException("Unknown request command `$command`");
290
        }
291
    }
292
293
    /**
294
     * An array store all request commands supported in gateway [[request()]].
295
     *
296
     * @see requestCommands
297
     * @var array
298
     */
299
    private $_requestCommands;
300
301
    /**
302
     * This method automatically detect request commands supported via constants name prefix with `RC_`.
303
     * `RC` meaning Request Command.
304
     *
305
     * @return array An array constants value have name prefix with `RC_`
306
     * @throws \ReflectionException
307
     */
308 13
    public function requestCommands(): array
309
    {
310 13
        if ($this->_requestCommands === null) {
311 13
            $reflection = new ReflectionClass($this);
312
313 13
            $commands = [];
314 13
            foreach ($reflection->getConstants() as $name => $value) {
315 13
                if (strpos($name, 'RC_') === 0) {
316 13
                    $commands[] = $value;
317
                }
318
            }
319
320 13
            return $this->_requestCommands = $commands;
321
        } else {
322 5
            return $this->_requestCommands;
323
        }
324
    }
325
326
    /**
327
     * This method is called at the beginning of requesting data to gateway server api.
328
     *
329
     * The default implementation will trigger an [[EVENT_BEFORE_REQUEST]] event.
330
     * When overriding this method, make sure you call the parent implementation like the following:
331
     *
332
     * ```php
333
     * public function beforeRequest(RequestEvent $event)
334
     * {
335
     *     // ...custom code here...
336
     *
337
     *     parent::beforeRequest($event);
338
     * }
339
     * ```
340
     *
341
     * @param RequestEvent $event an event will be trigger in this method.
342
     */
343 6
    public function beforeRequest(RequestEvent $event)
344
    {
345 6
        $this->trigger(self::EVENT_BEFORE_REQUEST, $event);
346 6
    }
347
348
    /**
349
     * @var HttpClient will be use to send request to gateway server api.
350
     */
351
    private $_httpClient;
352
353
    /**
354
     * This method is called in [[request()]] invoke client and use it make request send to gateway server api.
355
     *
356
     * @param bool $force whether to force new instance
357
     * @return object|HttpClient instance use to send request
358
     * @throws InvalidConfigException
359
     */
360 6
    protected function getHttpClient(bool $force = false): HttpClient
361
    {
362 6
        if ($this->_httpClient === null || $force) {
363
            /** @var HttpClient $client */
364 6
            $config = ArrayHelper::merge(['class' => HttpClient::class], $this->httpClientConfig);
365 6
            $config['baseUrl'] = $this->getBaseUrl();
366
367 6
            return $this->_httpClient = Yii::createObject($config);
368
        } else {
369 5
            return $this->_httpClient;
370
        }
371
    }
372
373
    /**
374
     * Returns the config for the HttpClient.
375
     * An implement class may override this method for add default headers, transport, options...
376
     *
377
     * @return array
378
     * @deprecated since 2.0.1 move config to public property `httpClientConfig`
379
     */
380
    protected function getHttpClientConfig(): array
381
    {
382
        return [];
383
    }
384
385
    /**
386
     * An internal method get and send customize data depend on gateway server api. This method called by [[request()]] .
387
     *
388
     * @param RequestData $requestData use to get data, command, client for prepare request to send.
389
     * @param HttpClient $httpClient use for make request to gateway server api.
390
     * @return array response requested data.
391
     */
392
    abstract protected function requestInternal(RequestData $requestData, HttpClient $httpClient): array;
393
394
395
    /**
396
     * This method is called at the end of requesting data to gateway server api.
397
     * The default implementation will trigger an [[EVENT_AFTER_REQUEST]] event.
398
     * When overriding this method, make sure you call the parent implementation at the end like the following:
399
     *
400
     * ```php
401
     * public function afterRequest(RequestEvent $event)
402
     * {
403
     *     // ...custom code here...
404
     *
405
     *     parent::afterRequest($event);
406
     * }
407
     * ```
408
     *
409
     * @param RequestEvent $event an event will be trigger in this method.
410
     */
411 5
    public function afterRequest(RequestEvent $event)
412
    {
413 5
        $this->trigger(self::EVENT_AFTER_REQUEST, $event);
414 5
    }
415
416
}
417