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