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