BaseGateway::beforeRequest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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\InvalidCallException;
20
use yii\base\InvalidConfigException;
21
use yii\base\NotSupportedException;
22
use yii\helpers\ArrayHelper;
23
use yii\httpclient\Client as HttpClient;
24
25
26
/**
27
 * Class BaseClient a base class that implements the [[GatewayInterface]].
28
 * It is an abstraction layer, implements classes will be add more properties to support create request to gateway server api.
29
 *
30
 * @property BaseClient $defaultClient Default client of gateway.
31
 * @property BaseClient $client Default client of gateway.
32
 * @property array|string[] $supportedVersions The list supported versions of gateway.
33
 *
34
 * @author Vuong Minh <[email protected]>
35
 * @since 1.0
36
 */
37
abstract class BaseGateway extends Component implements GatewayInterface
38
{
39
40
    /**
41
     * @event RequestEvent an event that is triggered at the beginning of request to gateway server api.
42
     */
43
    const EVENT_BEFORE_REQUEST = 'beforeRequest';
44
45
    /**
46
     * @event RequestEvent an event that is triggered at the end of requested to gateway server api.
47
     */
48
    const EVENT_AFTER_REQUEST = 'afterRequest';
49
50
    /**
51
     * @var array config use for setup properties of all request data to send to gateway server api. It called by [[request()]].
52
     * An extend class must be override it for config default request data class.
53
     */
54
    public $requestDataConfig = [];
55
56
    /**
57
     * @var array config use for setup properties of all response data get from gateway server api. It called by [[request()]].
58
     * An extend class must be override it for config default response data class.
59
     */
60
    public $responseDataConfig = [];
61
62
    /**
63
     * @var array config of client use for setup properties of the clients list.
64
     * An extend class must be override it for config default client class.
65
     */
66
    public $clientConfig = [];
67
68
    /**
69
     * @var array config of `yii\httpclient\Client` use for send request to api server.
70
     * @since 2.0.1
71
     */
72
    public $httpClientConfig = [];
73
74
    /**
75
     * The clients list.
76
     *
77
     * @var array|BaseClient[]
78
     */
79
    private $_clients = [];
80
81
    /**
82
     * @var string A currently version of gateway
83
     */
84
    private $_version;
85
86
    /**
87
     * @inheritdoc
88
     */
89 6
    public function getVersion(): string
90
    {
91 6
        if ($this->_version === null) {
92 5
            return $this->_version = $this->defaultVersion();
93
        } else {
94 6
            return $this->_version;
95
        }
96
    }
97
98
    /**
99
     * This method return list supported versions of gateway.
100
     *
101
     * @return array|string[] List supported versions.
102
     */
103
    public function getSupportedVersions(): array
104
    {
105
        return [$this->getVersion()];
106
    }
107
108
    /**
109
     * This method support set version of gateway.
110
     *
111
     * @param string $version of gateway
112
     * @return bool Return TRUE if version has been set.
113
     * @throws NotSupportedException
114
     */
115 1
    public function setVersion(string $version): bool
116
    {
117 1
        $supportedVersions = $this->getSupportedVersions();
118
119 1
        if (in_array($version, $supportedVersions, true)) {
120 1
            $this->_version = $version;
121
122 1
            return true;
123
        } else {
124 1
            throw new NotSupportedException("Version `$version` is not supported in " . __CLASS__ . "! Supported versions: " . implode(' - ', $supportedVersions));
125
        }
126
    }
127
128
    /**
129
     * This method return default version of gateway if `version` is not set.
130
     *
131
     * @return string
132
     */
133
    protected function defaultVersion(): string
134
    {
135
        return '1.0';
136
    }
137
138
    /**
139
     * @inheritdoc
140
     * @throws InvalidConfigException
141
     */
142
    public function getClients(): array
143
    {
144
        $clients = [];
145
        foreach ($this->_clients as $id => $client) {
146
            $clients[$id] = $this->getClient($id);
147
        }
148
149
        return $clients;
150
    }
151
152
    /**
153
     * @inheritdoc
154
     * @throws InvalidConfigException
155
     */
156 2
    public function setClients(array $clients): bool
157
    {
158 2
        foreach ($clients as $id => $client) {
159 2
            $this->setClient($id, $client);
160
        }
161
162 2
        return true;
163
    }
164
165
    /**
166
     * @inheritdoc
167
     * @throws InvalidConfigException|InvalidArgumentException
168
     */
169 14
    public function getClient($id = null): ClientInterface
170
    {
171 14
        if ($id === null) {
172 12
            return $this->getDefaultClient();
173 11
        } elseif ($this->hasClient($id)) {
174 11
            $client = $this->_clients[$id];
175
176 11
            if (is_string($client)) {
177
                $client = ['class' => $client];
178
            }
179
180 11
            if (is_array($client)) {
181 10
                $client = ArrayHelper::merge($this->clientConfig, $client);
182
            }
183
184 11
            if (!$client instanceof BaseClient) {
185 10
                $client = $this->_clients[$id] = Yii::createObject($client, [$this]);
186
            }
187
188 11
            return $client;
189
        } else {
190
            throw new InvalidArgumentException("An id client: `$id` not exist!");
191
        }
192
    }
193
194
    /**
195
     * @var BaseClient|ClientInterface The default client value.
196
     */
197
    private $_defaultClient;
198
199
    /**
200
     * @inheritdoc
201
     * @return BaseClient|ClientInterface
202
     * @throws InvalidConfigException
203
     */
204 12
    public function getDefaultClient(): ClientInterface
205
    {
206 12
        if ($this->_defaultClient === null) {
207 12
            if (!empty($this->_clients)) {
208 9
                $ids = array_keys($this->_clients);
209 9
                $id = array_pop($ids);
210
211 9
                return $this->_defaultClient = $this->getClient($id);
212
            } else {
213 3
                throw new InvalidConfigException('Can not detect default client from an empty client lists!');
214
            }
215
        } else {
216 9
            return $this->_defaultClient;
217
        }
218
    }
219
220
    /**
221
     * @inheritdoc
222
     * @throws InvalidConfigException
223
     */
224 11
    public function setClient($id, $client = null): bool
225
    {
226 11
        if ($client === null) {
227 9
            $this->setDefaultClient($id);
228
        } else {
229 6
            $this->_clients[$id] = $client;
230
        }
231
232 11
        return true;
233
    }
234
235
    /**
236
     * @inheritdoc
237
     * @throws InvalidConfigException
238
     */
239 9
    public function setDefaultClient($client): bool
240
    {
241 9
        array_push($this->_clients, $client);
242 9
        $this->_defaultClient = null;
243 9
        $this->getDefaultClient();
244
245 9
        return true;
246
    }
247
248
    /**
249
     * @inheritdoc
250
     */
251 11
    public function hasClient($id): bool
252
    {
253 11
        return array_key_exists($id, $this->_clients);
254
    }
255
256
    /**
257
     * @inheritdoc
258
     * @return ResponseData|DataInterface
259
     * @throws InvalidConfigException|InvalidArgumentException|\ReflectionException
260
     */
261 13
    public function request($command, array $data, $clientId = null): DataInterface
262
    {
263 13
        if (in_array($command, $this->requestCommands(), true)) {
264 9
            $client = $this->getClient($clientId);
265
266
            /**
267
             * @var RequestData $requestData
268
             * @var RequestEvent $event
269
             * @var ResponseData $responseData
270
             */
271
272 6
            $requestData = Yii::createObject($this->requestDataConfig, [$command, $data, $client]);
273 6
            $event = Yii::createObject([
274 6
                'class' => RequestEvent::class,
275 6
                'command' => $command,
276 6
                'client' => $client,
277 6
                'requestData' => $requestData
278
            ]);
279
280 6
            $this->beforeRequest($event);
281 6
            $httpClient = $this->getHttpClient();
282 6
            $data = $this->requestInternal($requestData, $httpClient);
283 5
            $responseData = Yii::createObject($this->responseDataConfig, [$command, $data, $client]);
284 5
            $event->responseData = $responseData;
285 5
            $this->afterRequest($event);
286 5
            Yii::debug(__CLASS__ . " requested sent with command: `$command` - version: " . $this->getVersion());
287
288 5
            return $responseData;
289
        } else {
290 4
            throw new InvalidArgumentException("Request command `$command` invalid in " . __CLASS__);
291
        }
292
    }
293
294
    /**
295
     * An array store all request commands supported in gateway [[request()]].
296
     *
297
     * @see requestCommands
298
     * @var array
299
     */
300
    private $_requestCommands;
301
302
    /**
303
     * This method automatically detect request commands supported via constants name prefix with `RC_`.
304
     * `RC` meaning Request Command.
305
     *
306
     * @return array An array constants value have name prefix with `RC_`
307
     * @throws \ReflectionException
308
     */
309 13
    public function requestCommands(): array
310
    {
311 13
        if ($this->_requestCommands === null) {
312 13
            $reflection = new ReflectionClass($this);
313
314 13
            $commands = [];
315 13
            foreach ($reflection->getConstants() as $name => $value) {
316 13
                if (strpos($name, 'RC_') === 0) {
317 13
                    $commands[] = $value;
318
                }
319
            }
320
321 13
            return $this->_requestCommands = $commands;
322
        } else {
323 6
            return $this->_requestCommands;
324
        }
325
    }
326
327
    /**
328
     * This method is called at the beginning of requesting data to gateway server api.
329
     *
330
     * The default implementation will trigger an [[EVENT_BEFORE_REQUEST]] event.
331
     * When overriding this method, make sure you call the parent implementation like the following:
332
     *
333
     * ```php
334
     * public function beforeRequest(RequestEvent $event)
335
     * {
336
     *     // ...custom code here...
337
     *
338
     *     parent::beforeRequest($event);
339
     * }
340
     * ```
341
     *
342
     * @param RequestEvent $event an event will be trigger in this method.
343
     */
344 6
    public function beforeRequest(RequestEvent $event)
345
    {
346 6
        $this->trigger(self::EVENT_BEFORE_REQUEST, $event);
347 6
    }
348
349
    /**
350
     * @var HttpClient will be use to send request to gateway server api.
351
     */
352
    private $_httpClient;
353
354
    /**
355
     * This method is called in [[request()]] invoke client and use it make request send to gateway server api.
356
     *
357
     * @param bool $force whether to force new instance
358
     * @return object|HttpClient instance use to send request
359
     * @throws InvalidConfigException
360
     */
361 6
    protected function getHttpClient(bool $force = false): HttpClient
362
    {
363 6
        if ($this->_httpClient === null || $force) {
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
     * This magic method is a shorthand of [[request()]] use request command for method name.
418
     *
419
     * @param string $name command exist in [[requestCommands()]]
420
     * @param array $params use for [[request()]]
421
     * @return ResponseData|DataInterface
422
     * @throws \ReflectionException
423
     * @see http://php.net/manual/en/language.oop5.overloading.php#object.call
424
     */
425 3
    public function __call($name, $params)
426
    {
427 3
        if (in_array($name, $this->requestCommands(), true)) {
428 3
            array_unshift($params, $name);
429
430 3
            return call_user_func_array([$this, 'request'], $params);
431
        } else {
432
            throw new InvalidCallException("Method `$name` does not exist in " . __CLASS__);
433
        }
434
    }
435
436
}
437