Completed
Push — master ( badd30...b58b91 )
by Lawrence
02:30 queued 01:02
created

Client::callEndpoint()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 13
ccs 0
cts 0
cp 0
rs 9.4285
cc 2
eloc 6
nc 1
nop 2
crap 6
1
<?php
2
3
namespace Plinker\Core;
4
5
use Requests;
6
7
/**
8
 * Plinker Client
9
 */
10
class Client
11
{
12
    private $endpoint;
13
    private $component;
14
    private $publicKey;
15
    private $privateKey;
16
    private $config;
17
    private $encrypt;
18
    private $response;
19
    private $signer;
20
21
    /**
22
     * @param string $endpoint
23
     * @param string $component
24
     * @param string $publicKey
25
     * @param string $privateKey
26
     * @param array  $config
27
     * @param bool   $encrypt
28
     */
29 4
    public function __construct(
30
        $endpoint,
31
        $component,
32
        $publicKey = '',
33
        $privateKey = '',
34
        $config = [],
35
        $encrypt = true
36
    ) {
37
        // define vars
38 4
        $this->endpoint = $endpoint;
39 4
        $this->component = $component;
40 4
        $this->publicKey = hash('sha256', gmdate('h').$publicKey);
41 4
        $this->privateKey = hash('sha256', gmdate('h').$privateKey);
42 4
        $this->config = $config;
43 4
        $this->encrypt = $encrypt;
44 4
        $this->response = null;
45
46
        // init signer
47 4
        $this->signer = new Signer($this->publicKey, $this->privateKey, $this->encrypt);
48 4
    }
49
50
    /**
51
     * Helper which changes the server component on the fly without changing
52
     * the connection.
53
     *
54
     * @param string $component - component class namespace
55
     * @param array  $config    - component array
56
     */
57 1
    public function useComponent($component = '', $config = [], $encrypt = true)
58
    {
59 1
        $this->component = $component;
60 1
        $this->config = $config;
61 1
        $this->encrypt = $encrypt;
62
63 1
        return new $this(
64 1
            $this->endpoint,
65 1
            $this->component,
66 1
            $this->publicKey,
67 1
            $this->privateKey,
68 1
            $this->config,
69 1
            $this->encrypt
70
        );
71
    }
72
    
73
    /**
74
     * Call endpoint
75
     *
76
     * @codeCoverageIgnore
77
     * @access private
78
     * @param string $encoded
79
     * @param array  $params
80
     * @return mixed
81
     */
82
    final private function callEndpoint($encoded, $params = [])
83
    {
84
        return Requests::post(
85
            $this->endpoint,
86
            [
87
                // send plinker header
88
                'plinker' => true,
89
                // sign token generated from encoded packet, send as header
90
                'token'   => hash_hmac('sha256', $encoded['token'], $this->privateKey),
91
            ],
92
            $encoded,
93
            [
94
                'timeout' => (!empty($this->config['timeout']) ? (int) $this->config['timeout'] : 60),
95
            ]
96
        );
97
    }
98
    
99
    /**
100
     * Decode response
101
     *
102
     * @codeCoverageIgnore
103
     * @param string $encoded
104
     * @param array  $params
105
     * @return mixed
106
     */
107
    final private function decodeResponse($response)
108
    {
109
        // check response is a serialized string
110
        if (@unserialize($response->body) === false) {
111
            if (empty($response->body)) {
112
                $message = $response->raw;
113
            } else {
114
                $message = $response->body;
115
            }
116
117
            throw new \Exception('Could not unserialize response: '.$message);
118
        }
119
120
        // initial unserialize response body
121
        $response->body = unserialize($response->body);
122
123
        // decode response
124
        return $this->signer->decode(
125
            $response->body
126
        );
127
    } 
128
    
129
    /**
130
     * Validate response
131
     *
132
     * @codeCoverageIgnore
133
     * @param array  $response
134
     * @return array
135
     */
136
    final private function validateResponse($response)
137
    {
138
        // verify response packet timing validity
139
        $response['packet_time'] = microtime(true) - $this->response->body['time'];
140
        if ($response['packet_time'] >= 1) {
141
            throw new \Exception('Response timing packet check failed');
142
        }
143
144
        // verify data timing validity
145
        $response['data_time'] = (microtime(true) - $response['time']);
146
        if ($response['data_time'] >= 1) {
147
            throw new \Exception('Response timing data check failed');
148
        }
149
150
        // decode response data
151
        if (is_string($response['response'])) {
152
            // empty data response
153
            if (empty($response['response'])) {
154
                return $response;
155
            }
156
            // response should be a serialized string
157
            if (@unserialize($response['response']) === false) {
158
                throw new \Exception('Could not unserialize response: '.$response['response']);
159
            }
160
            $response['response'] = unserialize($response['response']);
161
        }
162
163
        // check for errors
164
        if (is_array($response['response']) && !empty($response['response']['error'])) {
165
            throw new \Exception(ucfirst($response['response']['error']));
166
        }
167
        
168
        return $return;
169
    }
170
171
    /**
172
     * Magic caller.
173
     *
174
     * @codeCoverageIgnore
175
     * @param string $action
176
     * @param array  $params
177
     * @return mixed
178
     */
179
    public function __call($action, $params)
180
    {
181
        if (!is_scalar($action)) {
182
            throw new \InvalidArgumentException('Method name has no scalar value');
183
        }
184
185
        if (!is_array($params)) {
186
            throw new \InvalidArgumentException('Arguments must be given as array');
187
        }
188
189
        // change arguments array into numeric indexed
190
        $params = array_values($params);
191
192
        // unset local private key
193
        unset($this->config['plinker']['private_key']);
194
195
        // encode payload
196
        $encoded = $this->signer->encode([
197
            'time'      => microtime(true),
198
            'self'      => $this->endpoint,
199
            'component' => $this->component,
200
            'config'    => $this->config,
201
            'action'    => $action,
202
            'params'    => $params,
203
        ]);
204
205
        // call endpoint
206
        $this->response = $this->callEndpoint($encoded, $params);
207
208
        // decode response
209
        $response = $this->decodeResponse($this->response);
210
        
211
        // validate response
212
        $response = $this->validateResponse($response);
213
214
        // unserialize data
215
        return $response['response'];
216
    }
217
}