├── .gitignore ├── src ├── Exception │ ├── Salesforce.php │ ├── SalesforceNoResults.php │ ├── SalesforceAuthentication.php │ ├── SalesforceServerError.php │ ├── RequestRefused.php │ ├── ResourceNotFound.php │ ├── UnsupportedFormat.php │ ├── SessionExpired.php │ └── SalesforceFields.php ├── Authentication │ ├── AuthenticationInterface.php │ └── PasswordAuthentication.php ├── QueryResults.php ├── QueryIterator.php └── Client.php ├── composer.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /src/Exception/Salesforce.php: -------------------------------------------------------------------------------- 1 | =5.3" 8 | }, 9 | "autoload": { 10 | "psr-4": { 11 | "Gmo\\Salesforce\\": "src/" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Authentication/AuthenticationInterface.php: -------------------------------------------------------------------------------- 1 | fields = is_array($fields) ? $fields : array(); 13 | } 14 | 15 | /** 16 | * @return string[] 17 | */ 18 | public function getFields() 19 | { 20 | return $this->fields; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Global Media Outreach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/QueryResults.php: -------------------------------------------------------------------------------- 1 | results = array_values($results); 15 | $this->totalSize = $totalSize; 16 | $this->isDone = $isDone; 17 | $this->nextQuery = $nextQuery; 18 | } 19 | 20 | /** 21 | * The Query API output, converted from JSON to an associative array 22 | * @return array 23 | */ 24 | public function getResults() 25 | { 26 | return $this->results; 27 | } 28 | 29 | /** 30 | * Returns the total number of records that the query matched 31 | * @return int 32 | */ 33 | public function getTotalSize() 34 | { 35 | return $this->totalSize; 36 | } 37 | 38 | /** 39 | * Returns whether or not there are more query results that haven't been returned in this results set 40 | * @return bool 41 | */ 42 | public function isDone() 43 | { 44 | return $this->isDone; 45 | } 46 | 47 | /** 48 | * Get the query string to grab the next results set 49 | * @return string 50 | */ 51 | public function getNextQuery() 52 | { 53 | return $this->nextQuery; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # salesforce-rest-api 2 | A simple PHP client for the Salesforce REST API 3 | 4 | ## Installation 5 | 6 | Install with composer: 7 | ``` 8 | composer config repositories.salesforce-rest-api vcs https://github.com/gmo/salesforce-rest-api 9 | composer require "gmo/salesforce-rest-api:^1.0" 10 | ``` 11 | 12 | ## Usage 13 | 14 | Initialize the `Salesforce\Client` class, call the APIs you want. 15 | 16 | ```php 17 | use Gmo\Salesforce; 18 | use Gmo\Salesforce\Exception; 19 | use Guzzle\Http; 20 | 21 | $authentication = new Salesforce\Authentication\PasswordAuthentication( 22 | "ClientId", 23 | "ClientSecret", 24 | "Username", 25 | "Password", 26 | "SecurityToken", 27 | new Http\Client() 28 | ); 29 | $salesforce = new Salesforce\Client($authentication, new Http\Client(), "na5"); 30 | 31 | try { 32 | $contactQueryResults = $salesforce->query("SELECT AccountId, LastName 33 | FROM Contact 34 | WHERE FirstName = ?", 35 | array('Alice') 36 | ); 37 | foreach($contactQueryResults as $queryResult) { 38 | print_r($queryResult); // The output of a single record from the query API JSON, converted to associative array 39 | } 40 | 41 | $contactQueryResults2 = $salesforce->query("SELECT AccountId, LastName 42 | FROM Contact 43 | WHERE FirstName = :firstName", 44 | array('firstName' => 'Bob') 45 | ); 46 | foreach($contactQueryResults2 as $queryResult) { 47 | print_r($queryResult); // The output of a single record from the query API JSON, converted to associative array 48 | } 49 | 50 | } catch(Exception\SalesforceNoResults $e) { 51 | // Do something when you have no results from your query 52 | } catch(Exception\Salesforce $e) { 53 | // Error handling 54 | } 55 | ``` 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/QueryIterator.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | 25 | $this->firstResultsSet = $firstResultsSet; 26 | $this->currentResultsSet = $firstResultsSet; 27 | $this->rewind(); 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function rewind() 34 | { 35 | $this->reset(); 36 | $this->next(); 37 | } 38 | 39 | /** 40 | * Resets the inner state of the iterator. 41 | */ 42 | protected function reset() 43 | { 44 | $this->valid = true; 45 | $this->position = -1; 46 | $this->currentResultsSet = $this->firstResultsSet; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function next() 53 | { 54 | $results = $this->currentResultsSet->getResults(); 55 | if (++$this->position < count($results)) { 56 | return; 57 | } 58 | 59 | if ($this->currentResultsSet->isDone()) { 60 | $this->valid = false; 61 | 62 | return; 63 | } 64 | 65 | $this->currentResultsSet = $this->getNextResultsSet(); 66 | $this->position = 0; 67 | } 68 | 69 | /** 70 | * Gets the next results set 71 | * @return QueryResults 72 | * @throws Exception\SalesforceNoResults 73 | */ 74 | protected function getNextResultsSet() 75 | { 76 | try { 77 | return $this->client->getNextQueryResults($this->currentResultsSet); 78 | } catch (Exception\SalesforceNoResults $e) { 79 | return new QueryResults(array(), $this->firstResultsSet->getTotalSize(), true, null); 80 | } 81 | } 82 | 83 | /** 84 | * Returns the current QueryResults object being iterated 85 | * @return QueryResults 86 | */ 87 | public function getCurrentResultsSet() 88 | { 89 | return $this->currentResultsSet; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function current() 96 | { 97 | $results = $this->currentResultsSet->getResults(); 98 | 99 | return $results[$this->position]; 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function key() 106 | { 107 | return $this->position; 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function valid() 114 | { 115 | return $this->valid; 116 | } 117 | 118 | /** 119 | * {@inheritdoc} 120 | */ 121 | public function count() 122 | { 123 | return $this->currentResultsSet->getTotalSize(); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/Authentication/PasswordAuthentication.php: -------------------------------------------------------------------------------- 1 | log = $log ?: new NullLogger(); 41 | $this->clientId = $clientId; 42 | $this->clientSecret = $clientSecret; 43 | $this->username = $username; 44 | $this->password = $password; 45 | $this->securityToken = $securityToken; 46 | $this->guzzle = $guzzle; 47 | $this->guzzle->setBaseUrl($loginApiUrl); 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | public function getAccessToken() 54 | { 55 | if ($this->accessToken) { 56 | return $this->accessToken; 57 | } 58 | 59 | $postFields = array( 60 | 'grant_type' => 'password', 61 | 'client_id' => $this->clientId, 62 | 'client_secret' => $this->clientSecret, 63 | 'username' => $this->username, 64 | 'password' => $this->password . $this->securityToken, 65 | ); 66 | $request = $this->guzzle->post('oauth2/token', null, $postFields); 67 | $request->setAuth('user', 'pass'); 68 | $response = $request->send(); 69 | $responseBody = $response->getBody(); 70 | $jsonResponse = json_decode($responseBody, true); 71 | 72 | if ($response->getStatusCode() !== 200) { 73 | $message = $responseBody; 74 | if (isset($jsonResponse['error_description'])) { 75 | $message = $jsonResponse['error_description']; 76 | } 77 | $this->log->error($message, array('response' => $responseBody)); 78 | throw new Exception\SalesforceAuthentication($message); 79 | } 80 | 81 | if (!isset($jsonResponse['access_token']) || empty($jsonResponse['access_token'])) { 82 | $message = 'Access token not found'; 83 | $this->log->error($message, array('response' => $responseBody)); 84 | throw new Exception\SalesforceAuthentication($message); 85 | } 86 | 87 | $this->accessToken = $jsonResponse['access_token']; 88 | 89 | return $this->accessToken; 90 | } 91 | 92 | public function invalidateAccessToken() 93 | { 94 | $this->accessToken = null; 95 | } 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | public function setLogger(LoggerInterface $logger) 101 | { 102 | $this->log = $logger; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | apiBaseUrl = str_replace(array('{region}', '{version}'), array($apiRegion, $apiVersion), 41 | static::SALESFORCE_API_URL_PATTERN); 42 | $this->log = $log ?: new NullLogger(); 43 | $this->authentication = $authentication; 44 | $this->guzzle = $guzzle; 45 | $this->guzzle->setBaseUrl($this->apiBaseUrl); 46 | } 47 | 48 | /** 49 | * Makes a call to the QueryAll API 50 | * @param string $queryToRun 51 | * @param array $parameters Parameters to bind 52 | * @return QueryIterator 53 | * @throws Exception\SalesforceNoResults 54 | */ 55 | public function queryAll($queryToRun, $parameters = array()) 56 | { 57 | $apiPath = $this->buildApiPathForQuery('queryAll', $queryToRun, $parameters); 58 | $queryResults = $this->callQueryApiAndGetQueryResults($apiPath); 59 | 60 | return new QueryIterator($this, $queryResults); 61 | } 62 | 63 | /** 64 | * @param string $queryMethod 65 | * @param string $queryToRun 66 | * @param array $parameters 67 | * @return string 68 | */ 69 | protected function buildApiPathForQuery($queryMethod, $queryToRun, $parameters = array()) 70 | { 71 | if (!empty($parameters)) { 72 | $queryToRun = $this->bindParameters($queryToRun, $parameters); 73 | } 74 | 75 | $queryToRun = urlencode($queryToRun); 76 | 77 | return "{$queryMethod}/?q={$queryToRun}"; 78 | } 79 | 80 | /** 81 | * @param string $queryString 82 | * @param array $parameters 83 | * @return string 84 | */ 85 | protected function bindParameters($queryString, $parameters) 86 | { 87 | $paramKeys = array_keys($parameters); 88 | $isNumericIndexes = array_reduce(array_map('is_int', $paramKeys), function ($carry, $item) { 89 | return $carry && $item; 90 | }, true); 91 | 92 | if ($isNumericIndexes) { 93 | $searchArray = array_fill(0, count($paramKeys), '?'); 94 | $replaceArray = array_values($parameters); 95 | } else { 96 | // NOTE: krsort here will prevent the scenario of a replacement of array('foo' => 1, 'foobar' => 2) on string "Hi :foobar" resulting in "Hi 1bar" 97 | krsort($parameters); 98 | $searchArray = array_map(function ($string) { 99 | return ':' . $string; 100 | }, array_keys($parameters)); 101 | $replaceArray = array_values($parameters); 102 | } 103 | 104 | $replaceArray = $this->addQuotesToStringReplacements($replaceArray); 105 | $replaceArray = $this->replaceBooleansWithStringLiterals($replaceArray); 106 | 107 | return str_replace($searchArray, $replaceArray, $queryString); 108 | } 109 | 110 | protected function addQuotesToStringReplacements($replacements) 111 | { 112 | foreach ($replacements as $key => $val) { 113 | if (is_string($val) && !$this->isSalesforceDateFormat($val)) { 114 | $val = str_replace("'", "\'", $val); 115 | $replacements[$key] = "'{$val}'"; 116 | } 117 | } 118 | 119 | return $replacements; 120 | } 121 | 122 | protected function isSalesforceDateFormat($string) 123 | { 124 | return preg_match('/\d+[-]\d+[-]\d+[T]\d+[:]\d+[:]\d+[Z]/', $string) === 1; 125 | } 126 | 127 | protected function replaceBooleansWithStringLiterals($replacements) 128 | { 129 | return array_map(function ($val) { 130 | if (!is_bool($val)) { 131 | return $val; 132 | } 133 | 134 | $retval = $val ? 'true' : 'false'; 135 | 136 | return $retval; 137 | }, $replacements); 138 | } 139 | 140 | /** 141 | * Call the API for the provided query API path, handle No Results, and return a QueryResults object 142 | * @param $apiPath 143 | * @return QueryResults 144 | * @throws Exception\SalesforceNoResults 145 | */ 146 | protected function callQueryApiAndGetQueryResults($apiPath) 147 | { 148 | $response = $this->get($apiPath); 149 | $jsonResponse = json_decode($response, true); 150 | 151 | if (!isset($jsonResponse['totalSize']) || empty($jsonResponse['totalSize'])) { 152 | $message = 'No results found'; 153 | $this->log->info($message, array('response' => $response)); 154 | throw new Exception\SalesforceNoResults($message); 155 | } 156 | 157 | return new QueryResults( 158 | $jsonResponse['records'], 159 | $jsonResponse['totalSize'], 160 | $jsonResponse['done'], 161 | isset($jsonResponse['nextRecordsUrl']) ? $jsonResponse['nextRecordsUrl'] : null 162 | ); 163 | } 164 | 165 | protected function get($path, $headers = array(), $body = null, $options = array()) 166 | { 167 | return $this->requestWithAutomaticReauthorize('GET', $path, $headers, $body, $options); 168 | } 169 | 170 | protected function requestWithAutomaticReauthorize( 171 | $type, 172 | $path, 173 | $headers = array(), 174 | $body = null, 175 | $options = array() 176 | ) { 177 | try { 178 | return $this->request($type, $path, $headers, $body, $options); 179 | } catch (Exception\SessionExpired $e) { 180 | $this->authentication->invalidateAccessToken(); 181 | $this->setAccessTokenInGuzzleFromAuthentication(); 182 | 183 | return $this->request($type, $path, $headers, $body, $options); 184 | } 185 | } 186 | 187 | protected function request($type, $path, $headers = array(), $body = null, $options = array()) 188 | { 189 | $this->initializeGuzzle(); 190 | $request = $this->guzzle->createRequest($type, $path, $headers, $body, $options); 191 | try { 192 | $response = $request->send(); 193 | $responseBody = $response->getBody(); 194 | 195 | } catch (ClientErrorResponseException $e) { 196 | $response = $e->getResponse(); 197 | $responseBody = $response->getBody(); 198 | $message = $responseBody; 199 | $errorCode = $response->getStatusCode(); 200 | 201 | $jsonResponse = json_decode($responseBody, true); 202 | if (isset($jsonResponse[0]) && isset($jsonResponse[0]['message'])) { 203 | $message = $jsonResponse[0]['message']; 204 | } 205 | 206 | $fields = array(); 207 | if (isset($jsonResponse[0]) && isset($jsonResponse[0]['fields'])) { 208 | $fields = $jsonResponse[0]['fields']; 209 | } 210 | $this->log->error($message, array( 211 | 'response' => $responseBody, 212 | 'fields' => $fields, 213 | )); 214 | 215 | throw $this->getExceptionForSalesforceError($message, $errorCode, $fields); 216 | } 217 | 218 | return $responseBody; 219 | } 220 | 221 | /** 222 | * Lazy loads the access token by running authentication and setting the access token into the $this->guzzle headers 223 | */ 224 | protected function initializeGuzzle() 225 | { 226 | if ($this->guzzle->getDefaultOption('headers/Authorization')) { 227 | return; 228 | } 229 | 230 | $this->setAccessTokenInGuzzleFromAuthentication(); 231 | } 232 | 233 | protected function setAccessTokenInGuzzleFromAuthentication() 234 | { 235 | $accessToken = $this->authentication->getAccessToken(); 236 | $this->guzzle->setDefaultOption('headers/Authorization', "Bearer {$accessToken}"); 237 | } 238 | 239 | /** 240 | * @param string $message 241 | * @param int $code 242 | * @param array $fields 243 | * @return Exception\Salesforce 244 | */ 245 | protected function getExceptionForSalesforceError($message, $code, $fields) 246 | { 247 | if (!empty($fields)) { 248 | return new Exception\SalesforceFields($message, $code, $fields); 249 | } 250 | 251 | if ($code === Exception\SessionExpired::ERROR_CODE) { 252 | return new Exception\SessionExpired($message, $code); 253 | } 254 | 255 | if ($code === Exception\RequestRefused::ERROR_CODE) { 256 | return new Exception\RequestRefused($message, $code); 257 | } 258 | 259 | if ($code === Exception\ResourceNotFound::ERROR_CODE) { 260 | return new Exception\ResourceNotFound($message, $code); 261 | } 262 | 263 | if ($code === Exception\UnsupportedFormat::ERROR_CODE) { 264 | return new Exception\UnsupportedFormat($message, $code); 265 | } 266 | 267 | return new Exception\Salesforce($message, $code); 268 | } 269 | 270 | /** 271 | * Fetch the next QueryResults for a query that has multiple pages worth of returned records 272 | * @param QueryResults $queryResults 273 | * @return QueryResults 274 | * @throws Exception\SalesforceNoResults 275 | */ 276 | public function getNextQueryResults(QueryResults $queryResults) 277 | { 278 | $basePath = $this->getPathFromUrl($this->apiBaseUrl); 279 | $nextRecordsRelativePath = str_replace($basePath, '', $queryResults->getNextQuery()); 280 | 281 | return $this->callQueryApiAndGetQueryResults($nextRecordsRelativePath); 282 | } 283 | 284 | protected function getPathFromUrl($url) 285 | { 286 | $parts = parse_url($url); 287 | 288 | return $parts['path']; 289 | } 290 | 291 | /** 292 | * Get a record Id via a call to the Query API 293 | * @param $queryString 294 | * @return string The Id field of the first result of the query 295 | * @throws Exception\SalesforceNoResults 296 | */ 297 | public function queryForId($queryString) 298 | { 299 | $jsonResponse = $this->query($queryString); 300 | 301 | return $jsonResponse['records'][0]['Id']; 302 | } 303 | 304 | /** 305 | * Makes a call to the Query API 306 | * @param string $queryToRun 307 | * @param array $parameters Parameters to bind 308 | * @return QueryIterator 309 | * @throws Exception\SalesforceNoResults 310 | */ 311 | public function query($queryToRun, $parameters = array()) 312 | { 313 | $apiPath = $this->buildApiPathForQuery('query', $queryToRun, $parameters); 314 | $queryResults = $this->callQueryApiAndGetQueryResults($apiPath); 315 | 316 | return new QueryIterator($this, $queryResults); 317 | } 318 | 319 | /** 320 | * Get an Account by Account Id 321 | * @param string $accountId 322 | * @param string[]|null $fields The Account fields to return. Default Name & BillingCountry 323 | * @return mixed The API output, converted from JSON to an associative array 324 | * @throws Exception\SalesforceNoResults 325 | */ 326 | public function getAccount($accountId, $fields = null) 327 | { 328 | $accountId = urlencode($accountId); 329 | $defaultFields = array('Name', 'BillingCountry'); 330 | if (empty($fields)) { 331 | $fields = $defaultFields; 332 | } 333 | $fields = implode(',', $fields); 334 | $response = $this->get("sobjects/Account/{$accountId}?fields={$fields}"); 335 | $jsonResponse = json_decode($response, true); 336 | 337 | if (!isset($jsonResponse['attributes']) || empty($jsonResponse['attributes'])) { 338 | $message = 'No results found'; 339 | $this->log->info($message, array('response' => $response)); 340 | throw new Exception\SalesforceNoResults($message); 341 | } 342 | 343 | return $jsonResponse; 344 | } 345 | 346 | /** 347 | * Get a Contact by Account Id 348 | * @param string $accountId 349 | * @param string[]|null $fields The Contact fields to return. Default FirstName, LastName and MailingCountry 350 | * @return mixed The API output, converted from JSON to an associative array 351 | * @throws Exception\SalesforceNoResults 352 | */ 353 | public function getContact($accountId, $fields = null) 354 | { 355 | $accountId = urlencode($accountId); 356 | $defaultFields = array('FirstName', 'LastName', 'MailingCountry'); 357 | if (empty($fields)) { 358 | $fields = $defaultFields; 359 | } 360 | $fields = implode(',', $fields); 361 | $response = $this->get("sobjects/Contact/{$accountId}?fields={$fields}"); 362 | $jsonResponse = json_decode($response, true); 363 | 364 | if (!isset($jsonResponse['attributes']) || empty($jsonResponse['attributes'])) { 365 | $message = 'No results found'; 366 | $this->log->info($message, array('response' => $response)); 367 | throw new Exception\SalesforceNoResults($message); 368 | } 369 | 370 | return $jsonResponse; 371 | } 372 | 373 | /** 374 | * Creates a new Account using the provided field values 375 | * @param string[] $fields The field values to set on the new Account 376 | * @return string The Id of the newly created Account 377 | * @throws Exception\Salesforce 378 | */ 379 | public function newAccount($fields) 380 | { 381 | return $this->newSalesforceObject("Account", $fields); 382 | } 383 | 384 | /** 385 | * Creates a new Salesforce Object using the provided field values 386 | * @param string $object The name of the salesforce object. i.e. Account or Contact 387 | * @param string[] $fields The field values to set on the new Salesforce Object 388 | * @return string The Id of the newly created Salesforce Object 389 | * @throws Exception\Salesforce 390 | */ 391 | public function newSalesforceObject($object, $fields) 392 | { 393 | $this->log->info('Creating Salesforce object', array( 394 | 'object' => $object, 395 | 'fields' => $fields, 396 | )); 397 | $fields = json_encode($fields); 398 | $headers = array( 399 | 'Content-Type' => 'application/json' 400 | ); 401 | 402 | $response = $this->post("sobjects/{$object}/", $headers, $fields); 403 | $jsonResponse = json_decode($response, true); 404 | 405 | if (!isset($jsonResponse['id']) || empty($jsonResponse['id'])) { 406 | $message = 'Error while creating account'; 407 | $this->log->info($message, array('response' => $response)); 408 | throw new Exception\Salesforce($message); 409 | } 410 | 411 | return $jsonResponse['id']; 412 | } 413 | 414 | protected function post($path, $headers = array(), $body = null, $options = array()) 415 | { 416 | return $this->requestWithAutomaticReauthorize('POST', $path, $headers, $body, $options); 417 | } 418 | 419 | /** 420 | * Creates a new Contact using the provided field values 421 | * @param string[] $fields The field values to set on the new Contact 422 | * @return string The Id of the newly created Contact 423 | * @throws Exception\Salesforce 424 | */ 425 | public function newContact($fields) 426 | { 427 | return $this->newSalesforceObject("Contact", $fields); 428 | } 429 | 430 | /** 431 | * Updates an Account using the provided field values 432 | * @param string $id The Account Id of the Account to update 433 | * @param string[] $fields The fields to update 434 | * @return bool 435 | */ 436 | public function updateAccount($id, $fields) 437 | { 438 | return $this->updateSalesforceObject("Account", $id, $fields); 439 | } 440 | 441 | /** 442 | * Updates an Salesforce Object using the provided field values 443 | * @param string $object The name of the salesforce object. i.e. Account or Contact 444 | * @param string $id The Id of the Salesforce Object to update 445 | * @param string[] $fields The fields to update 446 | * @return bool 447 | */ 448 | public function updateSalesforceObject($object, $id, $fields) 449 | { 450 | $this->log->info('Updating Salesforce object', array( 451 | 'id' => $id, 452 | 'object' => $object, 453 | )); 454 | $id = urlencode($id); 455 | $fields = json_encode($fields); 456 | $headers = array( 457 | 'Content-Type' => 'application/json' 458 | ); 459 | 460 | $this->patch("sobjects/{$object}/{$id}", $headers, $fields); 461 | 462 | return true; 463 | } 464 | 465 | protected function patch($path, $headers = array(), $body = null, $options = array()) 466 | { 467 | return $this->requestWithAutomaticReauthorize('PATCH', $path, $headers, $body, $options); 468 | } 469 | 470 | /** 471 | * Updates an Contact using the provided field values 472 | * @param string $id The Contact Id of the Contact to update 473 | * @param string[] $fields The fields to update 474 | * @return bool 475 | */ 476 | public function updateContact($id, $fields) 477 | { 478 | return $this->updateSalesforceObject("Contact", $id, $fields); 479 | } 480 | 481 | /** 482 | * Gets the valid fields for Accounts via the describe API 483 | * @return mixed The API output, converted from JSON to an associative array 484 | */ 485 | public function getAccountFields() 486 | { 487 | return $this->getFields('Account'); 488 | } 489 | 490 | /** 491 | * Gets the valid fields for a given Salesforce Object via the describe API 492 | * @param string $object The name of the salesforce object. i.e. Account or Contact 493 | * @return mixed The API output, converted from JSON to an associative array 494 | */ 495 | public function getFields($object) 496 | { 497 | $response = $this->get("sobjects/{$object}/describe"); 498 | $jsonResponse = json_decode($response, true); 499 | $fields = array(); 500 | foreach ($jsonResponse['fields'] as $row) { 501 | $fields[] = array('label' => $row['label'], 'name' => $row['name']); 502 | } 503 | 504 | return $fields; 505 | } 506 | 507 | /** 508 | * Gets the valid fields for Contacts via the describe API 509 | * @return mixed The API output, converted from JSON to an associative array 510 | */ 511 | public function getContactFields() 512 | { 513 | return $this->getFields('Contact'); 514 | } 515 | 516 | /** 517 | * @inheritdoc 518 | */ 519 | public function setLogger(LoggerInterface $logger) 520 | { 521 | $this->log = $logger; 522 | } 523 | } 524 | --------------------------------------------------------------------------------