11<?php
22
3+ declare(strict_types=1);
4+
35/**
4- *
6+ * This file is part of the MultiFlexi package
7+ *
8+ * https://github.com/VitexSoftware/php-vitexsoftware-rbczpremiumapi
9+ *
10+ * (c) Vítězslav Dvořák <http: //vitexsoftware.com >
511 *
6- * @author Vitex <vitex @hippy.cz >
7- * @copyright 2023 Vitex@hippy.cz (G)
8- *
9- * PHP 8
12+ * For the full copyright and license information, please view the LICENSE
13+ * file that was distributed with this source code.
1014 */
1115
1216namespace VitexSoftware\Raiffeisenbank;
1317
18+ use VitexSoftware\Raiffeisenbank\RateLimit\RateLimiter;
19+
1420/**
15- * Description of ApiClient
21+ * Description of ApiClient.
1622 *
1723 * @author vitex
1824 */
1925class ApiClient extends \GuzzleHttp\Client
2026{
21-
2227 /**
2328 * ClientID obtained from Developer Portal - when you registered your app with us.
24- * @var string
2529 */
2630 protected string $xIBMClientId = ' ' ;
2731
2832 /**
29- * the end IP address of the client application (no server) in IPv4 or IPv6
30- * format. If the bank client (your user) uses a browser by which he
31- * accesses your server app, we need to know the IP address of his browser.
32- * Always provide the closest IP address to the real end-user possible.
33- * (optional)
34- *
35- * @var string
33+ * the end IP address of the client application (no server) in IPv4 or IPv6
34+ * format. If the bank client (your user) uses a browser by which he
35+ * accesses your server app, we need to know the IP address of his browser.
36+ * Always provide the closest IP address to the real end-user possible.
37+ * (optional).
3638 */
3739 protected string $pSUIPAddress = ' ' ;
3840
3941 /**
4042 * Use mocking for api calls ?
41- * @var boolean
4243 */
4344 protected bool $mockMode = false ;
45+ private RateLimiter $rateLimiter ;
4446
4547 /**
46- * @inheritDoc
47- *
48+ * { @inheritDoc}
49+ *
4850 * $config['clientid'] - obtained from Developer Portal - when you registered your app with us.
4951 * $config['cert'] = ['/path/to/cert.p12','certificat password']
5052 * $config['clientpubip'] = the closest IP address to the real end-user
5153 * $config['mocking'] = true to use /rbcz/premium/mock/* endpoints
52- *
53- * @param array $config
54+ *
5455 * @throws \Exception CERT_FILE is not set
5556 * @throws \Exception CERT_PASS is not set
5657 */
5758 public function __construct(array $config = [])
5859 {
59- if (array_key_exists(' clientid' , $config ) === false ) {
60+ if (\ array_key_exists(' clientid' , $config ) === false ) {
6061 $this -> xIBMClientId = \Ease\Shared::cfg(' XIBMCLIENTID' );
6162 } else {
6263 $this -> xIBMClientId = $config [' clientid' ];
6364 }
6465
65- if (array_key_exists('cert', $config) === false) {
66+ if (\ array_key_exists('cert', $config) === false) {
6667 $config [' cert' ] = [\Ease\Shared::cfg(' CERT_FILE' ), \Ease\Shared::cfg(' CERT_PASS' )];
68+
6769 if (empty($config [' cert' ][0])) {
6870 throw new \Exception(' Certificate (CERT_FILE) not specified' );
6971 }
72+
7073 if (empty($config['cert'][1])) {
7174 throw new \Exception(' Certificate password (CERT_PASS) not specified' );
7275 }
7376 }
7477
75- if (array_key_exists('debug', $config) === false) {
78+ if (\ array_key_exists('debug', $config) === false) {
7679 $config [' debug' ] = \Ease\Shared::cfg(' API_DEBUG' , false );
77- }
78-
79- if (array_key_exists('clientpubip', $config)){
80+ }
81+
82+ if (\ array_key_exists('clientpubip', $config)) {
8083 $this -> pSUIPAddress = $config [' clientpubip' ];
8184 }
8285
83- if (array_key_exists('mocking', $config)){
84- $this -> mockMode = boolval( $config [' mocking' ]) ;
86+ if (\ array_key_exists('mocking', $config)) {
87+ $this -> mockMode = (bool) $config [' mocking' ];
8588 }
86-
89+
90+ $limitStore = new RateLimit\JsonRateLimitStore(sys_get_temp_dir().'/rbczpremiumapi_rates.json');
91+
92+ $this->rateLimiter = new RateLimiter($limitStore);
93+
8794 parent::__construct($config);
8895 }
8996
9097 /**
91- * ClientID obtained from Developer Portal
92- *
98+ * ClientID obtained from Developer Portal.
99+ *
93100 * @return string
94101 */
95102 public function getXIBMClientId()
@@ -98,102 +105,152 @@ class ApiClient extends \GuzzleHttp\Client
98105 }
99106
100107 /**
101- * Keep user public IP here
102- *
108+ * Keep user public IP here.
109+ *
103110 * @return string
104111 */
105112 public function getpSUIPAddress()
106113 {
107114 return $this -> pSUIPAddress ;
108- }
115+ }
109116
110117 /**
111118 * Use mocking uri for api calls ?
112- *
113- * @return boolean
119+ *
120+ * @return bool
114121 */
115122 public function getMockMode()
116123 {
117124 return $this -> mockMode ;
118125 }
119-
126+
120127 /**
121- * Obtain Your current Public IP
122- *
128+ * Obtain Your current Public IP.
129+ *
123130 * @deprecated since version 0.1 - Do not use in production Environment!
124- *
131+ *
125132 * @return string
126133 */
127134 public static function getPublicIP()
128135 {
129136 $curl = curl_init();
130- curl_setopt($curl , CURLOPT_URL, " http://httpbin.org/ip" );
131- curl_setopt($curl , CURLOPT_RETURNTRANSFER, 1);
137+ curl_setopt($curl , \ CURLOPT_URL, ' http://httpbin.org/ip' );
138+ curl_setopt($curl , \ CURLOPT_RETURNTRANSFER, 1);
132139 $output = curl_exec($curl );
133140 curl_close($curl );
134141 $ip = json_decode($output , true );
142+
135143 return $ip [' origin' ];
136144 }
137-
145+
138146 /**
139- * Source Identifier
140- *
147+ * Source Identifier.
148+ *
141149 * @deprecated since version 0.1 - Do not use in production Environment!
142- *
150+ *
143151 * @return string
144152 */
145153 public static function sourceString()
146154 {
147- return substr(__FILE__ . ' @' . gethostname(), -50);
155+ return substr(__FILE__. ' @' . gethostname(), -50);
148156 }
149157
150158 /**
151- * Try to check certificate readibilty
152- *
159+ * Try to check certificate readibilty.
160+ *
161+ * @param string $certFile path to certificate
162+ * @param bool $die throw exception or return false ?
163+ *
153164 * @throws Exception - Certificate file not found
154- *
155- * @param string $certFile path to certificate
156- * @param boolean $die throw exception or return false ?
157- *
158- * @return boolean certificate file
165+ *
166+ * @return bool certificate file
159167 */
160168 public static function checkCertificatePresence(string $certFile, bool $die = false): bool
161169 {
162170 $found = false ;
171+
163172 if ((file_exists($certFile ) === false ) || (is_readable($certFile ) === false )) {
164- $errMsg = ' Cannot read specified certificate file: ' . $certFile ;
165- fwrite(STDERR, $errMsg . PHP_EOL);
166- if ($die ){
173+ $errMsg = ' Cannot read specified certificate file: ' .$certFile ;
174+ fwrite(\STDERR, $errMsg .\PHP_EOL);
175+
176+ if ($die ) {
167177 throw new \Exception($errMsg );
168178 }
169179 } else {
170180 $found = true ;
171181 }
172- return $found;
182+
183+ return $found;
173184 }
174-
175- public static function checkCertificate($certFile,$password): bool {
176- return self::checkCertificatePresence($certFile ) && self::checkCertificatePassword($certFile ,$password );
185+
186+ public static function checkCertificate($certFile, $password): bool
187+ {
188+ return self::checkCertificatePresence($certFile ) && self::checkCertificatePassword($certFile , $password );
177189 }
178-
179- public static function checkCertificatePassword(string $certFile, string $password): bool {
190+
191+ public static function checkCertificatePassword(string $certFile, string $password): bool
192+ {
180193 $certContent = file_get_contents($certFile );
194+
181195 if (openssl_pkcs12_read($certContent , $certs , $password ) === false ) {
182196 fwrite(\STDERR, ' Cannot read PKCS12 certificate file: ' .$certFile .\PHP_EOL);
197+
183198 exit(1);
184199 }
200+
185201 return true;
186202 }
187-
188-
203+
189204 /**
190- * Request Identifier
191- *
205+ * Request Identifier.
206+ *
207+ * @todo Obtain using RateLimiter
208+ *
192209 * @deprecated since version 0.1 - Do not use in production Environment!
193- *
210+ *
194211 * @return string
195212 */
196- public static function getxRequestId() {
197- return substr(self::sourceString() . ' #' . time(),-59);
213+ public static function getxRequestId()
214+ {
215+ return substr(self::sourceString().' #' .time(), -59);
216+ }
217+
218+ /**
219+ * Send an HTTP request.
220+ *
221+ * @param array $options Request options to apply to the given
222+ * request and to the transfer. See \GuzzleHttp\RequestOptions.
223+ *
224+ * @throws GuzzleException
225+ * @throws RateLimitExceededException
226+ */
227+ public function send(\Psr\Http\Message\RequestInterface $request, array $options = []): \Psr\Http\Message\ResponseInterface
228+ {
229+ $this -> rateLimiter -> checkBeforeRequest ($this -> xIBMClientId );
230+
231+ $response = parent::send($request , $options );
232+
233+ $statusCode = $response -> getStatusCode ();
234+ $responseHeaders = $response -> getHeaders ();
235+
236+ if (isset($responseHeaders [' x-ratelimit-remaining-second' ])) {
237+ $remainingSecond = (int) $responseHeaders [' x-ratelimit-remaining-second' ][0];
238+ $remainingDay = (int) $responseHeaders [' x-ratelimit-remaining-day' ][0];
239+
240+ $timestamp = time();
241+
242+ $this -> rateLimiter -> handleRateLimits ($this -> xIBMClientId , $remainingSecond , $remainingDay , $timestamp );
243+ }
244+
245+ if ($statusCode === 429) { // 429 Too Many Requests
246+ if ($this -> rateLimiter -> isWaitMode ()) {
247+ $this -> rateLimiter -> checkBeforeRequest ($this -> xIBMClientId );
248+ $response = parent::send($request , $options );
249+ } else {
250+ throw new RateLimitExceededException(' Rate limit exceeded (HTTP 429)' );
251+ }
252+ }
253+
254+ return $response;
198255 }
199256}
0 commit comments