Completed
Push — master ( bb3faf...1c3da6 )
by Ibrahim
13:51
created

Router::moveArgsToSentargs()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30
Metric Value
dl 0
loc 23
ccs 0
cts 12
cp 0
rs 8.5906
cc 5
eloc 11
nc 7
nop 3
crap 30
1
<?php
2
3
namespace Yabacon\Paystack\Helpers;
4
5
use \Closure;
6
use \Yabacon\Paystack\Contracts\RouteInterface;
7
8
/**
9
 * Router
10
 * Insert description here
11
 *
12
 * @category
13
 * @package
14
 * @author
15
 * @copyright
16
 * @license
17
 * @version
18
 * @link
19
 * @see
20
 * @since
21
 */
22
class Router
23
{
24
25
    private $route;
26
    private $route_class;
27
    private $secret_key;
28
    private $methods;
29
    private $use_guzzle=false;
30
31
    const ID_KEY = 'id';
32
    const PAYSTACK_API_ROOT = 'https://api.paystack.co';
33
34
    /**
35
 * moveArgsToSentargs
36
 * Insert description here
37
 *
38
 * @param $interface
39
 * @param $payload
40
 * @param $sentargs
41
 *
42
 * @return
43
 *
44
 * @access
45
 * @static
46
 * @see
47
 * @since
48
 */
49
    private function moveArgsToSentargs(
50
        $interface,
51
        &$payload,
52
        &$sentargs
53
    ) {
54
55
56
57
        // check if interface supports args
58
        if (array_key_exists(RouteInterface:: ARGS_KEY, $interface)) {
59
            // to allow args to be specified in the payload, filter them out and put them in sentargs
60
            $sentargs = (!$sentargs) ? [ ] : $sentargs; // Make sure $sentargs is not null
61
            $args = $interface[RouteInterface::ARGS_KEY];
62
            while (list($key, $value) = each($payload)) {
63
                // check that a value was specified
64
                // with a key that was expected as an arg
65
                if (in_array($key, $args)) {
66
                    $sentargs[$key] = $value;
67
                    unset($payload[$key]);
68
                }
69
            }
70
        }
71
    }
72
73
    /**
74
 * putArgsIntoEndpoint
75
 * Insert description here
76
 *
77
 * @param $endpoint
78
 * @param $sentargs
79
 *
80
 * @return
81
 *
82
 * @access
83
 * @static
84
 * @see
85
 * @since
86
 */
87
    private function putArgsIntoEndpoint(&$endpoint, $sentargs)
88
    {
89
        // substitute sentargs in endpoint
90
        while (list($key, $value) = each($sentargs)) {
91
            $endpoint = str_replace('{' . $key . '}', $value, $endpoint);
92
        }
93
    }
94
95
    /**
96
 * callViaCurl
97
 * Insert description here
98
 *
99
 * @param $interface
100
 * @param $payload
101
 * @param $sentargs
102
 *
103
 * @return
104
 *
105
 * @access
106
 * @static
107
 * @see
108
 * @since
109
 */
110
    private function callViaCurl($interface, $payload = [ ], $sentargs = [ ])
111
    {
112
 
113
114
        $endpoint = Router::PAYSTACK_API_ROOT . $interface[RouteInterface::ENDPOINT_KEY];
115
        $method = $interface[RouteInterface::METHOD_KEY];
116
117
        $this->moveArgsToSentargs($interface, $payload, $sentargs);
118
        $this->putArgsIntoEndpoint($endpoint, $sentargs);
119
 
120
        $headers = ["Authorization"=>"Bearer " . $this->secret_key ];
121
        $body = '';
122
        if (($method === RouteInterface::POST_METHOD)
123
            || ($method === RouteInterface::PUT_METHOD)
124
        ) {
125
            $headers["Content-Type"] = "application/json";
126
            $body = json_encode($payload);
127
        } elseif ($method === RouteInterface::GET_METHOD) {
128
            $endpoint = $endpoint . '?' . http_build_query($payload);
129
        }
130
        // Use Guzzle if found, else use Curl
131
        if ($this->use_guzzle && class_exists('\\GuzzleHttp\\Client') && class_exists('\\GuzzleHttp\\Psr7\\Request')) {
132
            $request = new \GuzzleHttp\Psr7\Request(strtoupper($method), $endpoint, $headers, $body);
133
            $client = new \GuzzleHttp\Client();
134
            try {
135
                $response = $client->send($request);
136
            } catch (\Exception $e) {
137
                if ($e->hasResponse()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method hasResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
138
                    $response = $e->getResponse();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException, Guzzle\Http\Exception\BadResponseException, Guzzle\Http\Exception\ClientErrorResponseException, Guzzle\Http\Exception\ServerErrorResponseException, Guzzle\Http\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
139
                } else {
140
                    throw $e;
141
                }
142
            }
143
            return $response;
144
        } else {
145
            //open connection
146
        
147
            $ch = \curl_init();
148
            // set url
149
            \curl_setopt($ch, \CURLOPT_URL, $endpoint);
150
 
151
            if ($method === RouteInterface::POST_METHOD || $method === RouteInterface::PUT_METHOD) {
152
                ($method === RouteInterface:: POST_METHOD) && \curl_setopt($ch, \CURLOPT_POST, true);
153
                ($method === RouteInterface ::PUT_METHOD) && \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
154
155
                \curl_setopt($ch, \CURLOPT_POSTFIELDS, $body);
156
            }
157
            //flatten the headers
158
            $flattened_headers = [];
159
            while (list($key, $value) = each($headers)) {
160
                $flattened_headers[] = $key . ": " . $value;
161
            }
162
            \curl_setopt($ch, \CURLOPT_HTTPHEADER, $flattened_headers);
163
            \curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
164
165
            // Make sure CURL_SSLVERSION_TLSv1_2 is defined as 6
166
            // Curl must be able to use TLSv1.2 to connect
167
            // to Paystack servers
168
            
169
            if (!defined('CURL_SSLVERSION_TLSV1_2')) {
170
                define('CURL_SSLVERSION_TLSV1_2', 6);
171
            }
172
            curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSV1_2);
173
174
            $response = \curl_exec($ch);
175
            
176
            if (\curl_errno($ch)) {   // should be 0
177
                // curl ended with an error
178
                $cerr = \curl_error($ch);
179
                \curl_close($ch);
180
                throw new \Exception("Curl failed with response: '" . $cerr . "'.");
181
            }
182
183
            // Then, after your \curl_exec call:
184
            $resp = json_decode($response);
185
            //close connection
186
            \curl_close($ch);
187
188
            if (!$resp->status) {
189
                throw new \Exception("Paystack Request failed with response: '" . $resp->message . "'.");
190
            }
191
192
            return $resp;
193
        }
194
195
    }
196
    
197
    /**
198
 * __call
199
 * Insert description here
200
 *
201
 * @param $methd
202
 * @param $sentargs
203
 *
204
 * @return
205
 *
206
 * @access
207
 * @static
208
 * @see
209
 * @since
210
 */
211
    public function __call($methd, $sentargs)
212
    {
213
        $method = ($methd === 'list' ? 'getList' : $methd );
214
        if (array_key_exists($method, $this->methods) && is_callable($this->methods[$method])) {
215
            return call_user_func_array($this->methods[$method], $sentargs);
216
        } else {
217
            // User attempted to call a function that does not exist
218
            throw new \Exception('Function "' . $method . '" does not exist for "' . $this->route . '".');
219
        }
220
    }
221
222
    /**
223
 * A magic resource object that can make method calls to API
224
 *
225
 * @param $route
226
 * @param $paystackObj - A Yabacon\Paystack Object
227
 */
228
    public function __construct($route, $paystackObj)
229
    {
230
        $this->route = strtolower($route);
231
        $this->route_class = 'Yabacon\\Paystack\\Routes\\' . ucwords($route);
232
        $this->secret_key = $paystackObj->secret_key;
233
        $this->use_guzzle = $paystackObj->use_guzzle;
234
235
        $mets = get_class_methods($this->route_class);
236
        if (!$mets) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mets of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
237
            throw new \InvalidArgumentException('Class "' . $this->route . '" does not exist.');
238
        }
239
        // add methods to this object per method, except root
240
        foreach ($mets as $mtd) {
241
            if ($mtd === 'root') {
242
                // skip root method
243
                continue;
244
            }
245
            /**
246
 * array
247
 * Insert description here
248
 *
249
 * @param $params
250
 * @param array
251
 * @param $sentargs
252
 *
253
 * @return
254
 *
255
 * @access
256
 * @static
257
 * @see
258
 * @since
259
 */
260
            $mtdFunc = function (
261
                array $params = [ ],
262
                array $sentargs = [ ]
263
            ) use ($mtd) {
264
                $interface = call_user_func($this->route_class . '::' . $mtd);
265
                // TODO: validate params and sentargs against definitions
266
                return $this->callViaCurl($interface, $params, $sentargs);
267
            };
268
            $this->methods[$mtd] = \Closure::bind($mtdFunc, $this, get_class());
269
        }
270
    }
271
}
272