Completed
Pull Request — master (#4)
by
unknown
03:19 queued 18s
created

Client::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
c 8
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
1
<?php namespace Crunch\Salesforce;
2
3
use Crunch\Salesforce\Exceptions\RequestException;
4
use GuzzleHttp\Exception\RequestException as GuzzleRequestException;
5
use Crunch\Salesforce\Exceptions\AuthenticationException;
6
use GuzzleHttp\Psr7\Response;
7
use Psr\Http\Message\ResponseInterface;
8
9
class Client
10
{
11
12
    /**
13
     * @var ClientConfigInterface
14
     */
15
    private $clientConfig;
16
    /**
17
     * @var AccessToken
18
     */
19
    private $accessToken;
20
21
    /**
22
     * @var string
23
     */
24
    private $baseUrl;
25
26
    /**
27
     * @var \GuzzleHttp\Client
28
     */
29
    private $guzzleClient;
30
31
32
    /**
33
     * Create a sf client using a client config object or an array of params
34
     *
35
     * @param ClientConfigInterface $clientConfig
36
     * @param \GuzzleHttp\Client    $guzzleClient
37
     * @throws \Exception
38
     */
39
    public function __construct(ClientConfigInterface $clientConfig, \GuzzleHttp\Client $guzzleClient)
40
    {
41
        $this->clientConfig = $clientConfig;
42
        $this->guzzleClient = $guzzleClient;
43
    }
44
45
    /**
46
     * Create an instance of the salesforce client using the passed in config data
47
     *
48
     * @param        $salesforceLoginUrl
49
     * @param        $clientId
50
     * @param        $clientSecret
51
     * @param string $version of the API
52
     *
53
     * @return Client
54
     */
55
    public static function create($salesforceLoginUrl, $clientId, $clientSecret, $version = "v37.0")
56
    {
57
        return new self(new ClientConfig($salesforceLoginUrl, $clientId, $clientSecret, $version), new \GuzzleHttp\Client);
58
    }
59
60
    /**
61
     * Log the user using the credential if known in advance
62
     *
63
     * Only use when not needing the OAuth usual flow.
64
     *
65
     * @param string $user
66
     * @param string $password
67
     *
68
     * @return AccessToken
69
     * @throws \Exception
70
     */
71
    public function login(string $user, string $password)
72
    {
73
        $res = $this->guzzleClient->post($this->clientConfig->getLoginUrl() . 'services/oauth2/token', [
74
            'headers'     => ['Accept' => 'application/json'],
75
            'form_params' => [
76
                'client_id'     => $this->clientConfig->getClientId(),
77
                'client_secret' => $this->clientConfig->getClientSecret(),
78
                'grant_type'    => 'password',
79
                'username'      => $user,
80
                'password'      => $password,
81
            ],
82
        ]);
83
        if (!$this->isSuccessful($res)) {
84
            throw new RequestException("Can't login", (string)$res->getBody());
85
        }
86
        $tokeGenerator = new AccessTokenGenerator();
87
88
        $decodedJson = json_decode((string)$res->getBody(), true);
89
        $this->setAccessToken($tokeGenerator->createFromSalesforceResponse($decodedJson));
90
    }
91
92
93
    /**
94
     * Fetch a specific object
95
     *
96
     * @param string $objectType
97
     * @param string $sfId
98
     * @param array  $fields
99
     *
100
     * @return string
101
     */
102
    public function getRecord($objectType, $sfId, array $fields = [])
103
    {
104
        $fieldsQuery = '';
105
        if (!empty($fields)) {
106
            $fieldsQuery = '?fields=' . implode(',', $fields);
107
        }
108
        $url      = $this->generateUrl('sobjects/'. $objectType . '/' . $sfId . $fieldsQuery);
109
        $response = $this->makeRequest('get', $url, ['headers' => ['Authorization' => $this->getAuthHeader()]]);
110
111
        return json_decode($response->getBody(), true);
112
    }
113
114
    /**
115
     * Execute an SOQL query and return the result set
116
     * This will loop through large result sets collecting all the data so the query should be limited
117
     *
118
     * @param string|null $query
119
     * @param string|null $next_url
120
     * @return array
121
     * @throws \Exception
122
     */
123
    public function search($query = null, $next_url = null)
124
    {
125
        if ( ! empty($next_url)) {
126
            $url = $this->baseUrl . '/' . $next_url;
127
        } else {
128
            $url = $this->generateUrl('query/?q=' . urlencode($query));
129
        }
130
        $response = $this->makeRequest('get', $url, ['headers' => ['Authorization' => $this->getAuthHeader()]]);
131
        $data     = json_decode($response->getBody(), true);
132
133
        $results = $data['records'];
134
        if ( ! $data['done']) {
135
            $more_results = $this->search(null, substr($data['nextRecordsUrl'], 1));
136
            if ( ! empty($more_results)) {
137
                $results = array_merge($results, $more_results);
138
            }
139
        }
140
141
        return $results;
142
    }
143
144
145
    /**
146
     * Make an update request
147
     *
148
     * @param string $object The object type to update
149
     * @param string $id The ID of the record to update
150
     * @param array  $data The data to put into the record
151
     * @return bool
152
     * @throws \Exception
153
     */
154
    public function updateRecord($object, $id, array $data)
155
    {
156
        $url =  $this->generateUrl('sobjects/'. $object . '/' . $id);
157
158
        $this->makeRequest('patch', $url, [
159
            'headers' => ['Content-Type' => 'application/json', 'Authorization' => $this->getAuthHeader()],
160
            'body'    => json_encode($data)
161
        ]);
162
163
        return true;
164
    }
165
166
    /**
167
     * Create a new object in salesforce
168
     *
169
     * @param string $object
170
     * @param string $data
171
     * @return bool
172
     * @throws \Exception
173
     */
174
    public function createRecord($object, $data)
175
    {
176
        $url = $this->generateUrl('sobjects/'. $object);
177
178
        $response     = $this->makeRequest('post', $url, [
179
            'headers' => ['Content-Type' => 'application/json', 'Authorization' => $this->getAuthHeader()],
180
            'body'    => json_encode($data)
181
        ]);
182
        $responseBody = json_decode($response->getBody(), true);
183
184
        return $responseBody['id'];
185
    }
186
187
    /**
188
     * Delete an object with th specified id
189
     *
190
     * @param $object
191
     * @param $id
192
     * @return bool
193
     * @throws \Exception
194
     */
195
    public function deleteRecord($object, $id)
196
    {
197
        $url = $this->generateUrl('sobjects/'. $object . '/' . $id);
198
199
        $this->makeRequest('delete', $url, ['headers' => ['Authorization' => $this->getAuthHeader()]]);
200
201
        return true;
202
    }
203
204
    /**
205
     * Complete the oauth process by confirming the code and returning an access token
206
     *
207
     * @param $code
208
     * @param $redirect_url
209
     * @return array|mixed
210
     * @throws \Exception
211
     */
212
    public function authorizeConfirm($code, $redirect_url)
213
    {
214
        $url = $this->clientConfig->getLoginUrl() . 'services/oauth2/token';
215
216
        $post_data = [
217
            'grant_type'    => 'authorization_code',
218
            'client_id'     => $this->clientConfig->getClientId(),
219
            'client_secret' => $this->clientConfig->getClientSecret(),
220
            'code'          => $code,
221
            'redirect_uri'  => $redirect_url
222
        ];
223
224
        $response = $this->makeRequest('post', $url, ['form_params' => $post_data]);
225
226
        return json_decode($response->getBody(), true);
227
    }
228
229
    /**
230
     * Get the url to redirect users to when setting up a salesforce access token
231
     *
232
     * @param $redirectUrl
233
     * @return string
234
     */
235
    public function getLoginUrl($redirectUrl)
236
    {
237
        $params = [
238
            'client_id'     => $this->clientConfig->getClientId(),
239
            'redirect_uri'  => $redirectUrl,
240
            'response_type' => 'code',
241
            'grant_type'    => 'authorization_code'
242
        ];
243
244
        return $this->clientConfig->getLoginUrl() . 'services/oauth2/authorize?' . http_build_query($params);
245
    }
246
247
    /**
248
     * Refresh an existing access token
249
     *
250
     * @return AccessToken
251
     * @throws \Exception
252
     */
253
    public function refreshToken()
254
    {
255
        $url = $this->clientConfig->getLoginUrl() . 'services/oauth2/token';
256
257
        $post_data = [
258
            'grant_type'    => 'refresh_token',
259
            'client_id'     => $this->clientConfig->getClientId(),
260
            'client_secret' => $this->clientConfig->getClientSecret(),
261
            'refresh_token' => $this->accessToken->getRefreshToken()
262
        ];
263
264
        $response = $this->makeRequest('post', $url, ['form_params' => $post_data]);
265
266
        $update = json_decode($response->getBody(), true);
267
        $this->accessToken->updateFromSalesforceRefresh($update);
268
269
        return $this->accessToken;
270
    }
271
272
    /**
273
     * @param AccessToken $accessToken
274
     */
275
    public function setAccessToken(AccessToken $accessToken)
276
    {
277
        $this->accessToken = $accessToken;
278
        $this->baseUrl     = $accessToken->getApiUrl();
279
    }
280
281
    /**
282
     * @param string $method
283
     * @param string $url
284
     * @param array  $data
285
     * @return mixed
286
     * @throws AuthenticationException
287
     * @throws RequestException
288
     */
289
    private function makeRequest($method, $url, $data)
290
    {
291
        try {
292
            $response = $this->guzzleClient->$method($url, $data);
293
294
            return $response;
295
        } catch (GuzzleRequestException $e) {
296
297
            if ($e->getResponse() === null) {
298
        		throw $e;
299
        	}
300
301
            //If its an auth error convert to an auth exception
302
            if ($e->getResponse()->getStatusCode() == 401) {
303
                $error = json_decode($e->getResponse()->getBody(), true);
304
                throw new AuthenticationException($error[0]['errorCode'], $error[0]['message']);
305
            }
306
            throw new RequestException($e->getMessage(), (string)$e->getResponse()->getBody());
307
        }
308
309
    }
310
311
    /**
312
     * @return string
313
     * @throws AuthenticationException
314
     */
315
    private function getAuthHeader()
316
    {
317
        if ($this->accessToken === null) {
318
    		throw new AuthenticationException(0, "Access token not set");
319
    	}
320
321
        return 'Bearer ' . $this->accessToken->getAccessToken();
322
    }
323
324
    /**
325
     * Is the response successful
326
     *
327
     * @param ResponseInterface $res
328
     *
329
     * @return bool
330
     */
331
    private function isSuccessful(ResponseInterface $res) {
332
        return $res->getStatusCode() >= 200 && $res->getStatusCode() < 300;
333
    }
334
335
    /**
336
     * Generate the call URL
337
     * @param string $append
338
     *
339
     * @return string
340
     */
341
    private function generateUrl($append)
342
    {
343
        return $this->baseUrl . '/services/data/'.$this->clientConfig->getVersion().'/'.$append;
344
    }
345
346
}
347