Passed
Push — master ( ace113...127e66 )
by Vuong
02:36
created

BaseGateway::hasClient()   A

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