├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── Phpforce │ └── SoapClient │ ├── BulkSaver.php │ ├── BulkSaverInterface.php │ ├── Client.php │ ├── ClientBuilder.php │ ├── ClientInterface.php │ ├── Event │ ├── FaultEvent.php │ ├── RequestEvent.php │ └── ResponseEvent.php │ ├── EventListener │ └── LogTransactionListener.php │ ├── Events.php │ ├── Exception │ ├── DeleteException.php │ └── SaveException.php │ ├── Plugin │ └── LogPlugin.php │ ├── Request │ ├── BaseEmail.php │ ├── EmailFileAttachment.php │ ├── LeadConvert.php │ ├── MergeRequest.php │ └── SingleEmailMessage.php │ ├── Result │ ├── ChildRelationship.php │ ├── DeleteResult.php │ ├── DeletedRecord.php │ ├── DescribeGlobalResult.php │ ├── DescribeGlobalSObjectResult.php │ ├── DescribeSObjectResult.php │ ├── DescribeSObjectResult │ │ └── Field.php │ ├── DescribeTab.php │ ├── DescribeTabSetResult.php │ ├── EmptyRecycleBinResult.php │ ├── Error.php │ ├── GetDeletedResult.php │ ├── GetServerTimestampResult.php │ ├── GetUpdatedResult.php │ ├── GetUserInfoResult.php │ ├── LeadConvertResult.php │ ├── LoginResult.php │ ├── MergeResult.php │ ├── ProcessResult.php │ ├── QueryResult.php │ ├── RecordIterator.php │ ├── SObject.php │ ├── SaveResult.php │ ├── SearchResult.php │ ├── SendEmailError.php │ ├── SendEmailResult.php │ ├── UndeleteResult.php │ └── UpsertResult.php │ └── Soap │ ├── SoapClient.php │ ├── SoapClientFactory.php │ └── TypeConverter │ ├── DateTimeTypeConverter.php │ ├── DateTypeConverter.php │ ├── TypeConverterCollection.php │ └── TypeConverterInterface.php └── tests ├── Phpforce └── SoapClient │ └── Tests │ ├── AbstractResultTest.php │ ├── BulkSaverTest.php │ ├── ClientTest.php │ └── Fixtures │ └── sandbox.enterprise.wsdl.xml └── bootstrap.php /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - hhvm 9 | 10 | env: 11 | - SYMFONY_VERSION="2.1.*" 12 | 13 | before_script: 14 | - wget http://getcomposer.org/composer.phar 15 | - php composer.phar install 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 David de Boer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/phpforce/soap-client.svg?branch=master)](https://travis-ci.org/phpforce/soap-client) 2 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phpforce/soap-client/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phpforce/soap-client/?branch=master) 3 | 4 | ###[I’m looking for maintainers!](https://github.com/phpforce/soap-client/issues/4) 5 | 6 | 7 | PHPForce Soap Client: a PHP client for the Salesforce SOAP API 8 | ============================================================== 9 | 10 | Introduction 11 | ------------ 12 | 13 | This library is a client for the 14 | [Salesforce SOAP API](http://www.salesforce.com/us/developer/docs/api/index.htm), 15 | and intended as a replacement for the 16 | [Force.com Tookit for PHP](http://wiki.developerforce.com/page/Force.com_Toolkit_for_PHP). 17 | 18 | ### Features 19 | 20 | This library’s features include the following. 21 | 22 | * Automatic conversion between PHP and SOAP date and datetime objects. 23 | * Automatic conversion of Salesforce (UTC) times to your local timezone. 24 | * Easily extensible through events: add custom logging, caching, error handling etc. 25 | * Iterating over large results sets that require multiple calls to the API 26 | is easy through the record iterator. 27 | * The BulkSaver helps you stay within your Salesforce API limits by using bulk 28 | creates, deletes, updates and upserts. 29 | * Completely unit tested (still working on that one). 30 | * Use the client in conjunction with the Symfony2 31 | [Mapper Bundle](https://github.com/ddeboer/DdeboerSalesforceMapperBundle) 32 | to get even easier access to your Salesforce data. 33 | 34 | Installation 35 | ------------ 36 | 37 | This library is available on [Packagist](http://packagist.org/packages/phpforce/soap-client). 38 | The recommended way to install this library is through [Composer](http://getcomposer.org): 39 | 40 | ```bash 41 | $ php composer.phar require phpforce/soap-client dev-master 42 | ``` 43 | 44 | Usage 45 | ----- 46 | 47 | ### The client 48 | 49 | Use the client to query and manipulate your organisation’s Salesforce data. First construct a client using the builder: 50 | 51 | ```php 52 | $builder = new \Phpforce\SoapClient\ClientBuilder( 53 | '/path/to/your/salesforce/wsdl/sandbox.enterprise.wsdl.xml', 54 | 'username', 55 | 'password', 56 | 'security_token' 57 | ); 58 | 59 | $client = $builder->build(); 60 | ``` 61 | 62 | ### SOQL queries 63 | 64 | ```php 65 | $results = $client->query('select Name, SystemModstamp from Account limit 5'); 66 | ``` 67 | 68 | This will fetch five accounts from Salesforce and return them as a 69 | `RecordIterator`. You can now iterate over the results. The account’s 70 | `SystemModstamp` is returned as a `\DateTime` object: 71 | 72 | ```php 73 | foreach ($results as $account) { 74 | echo 'Last modified: ' . $account->SystemModstamp->format('Y-m-d H:i:') . "\n"; 75 | } 76 | ``` 77 | 78 | ### One-to-many relations (subqueries) 79 | 80 | Results from [subqueries](http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_soql_select.htm) 81 | are themselves returned as record iterators. So: 82 | 83 | ```php 84 | $accounts = $client->query( 85 | 'select Id, (select Id, Name from Contacts) from Account limit 10' 86 | ); 87 | 88 | foreach ($accounts as $account) { 89 | if (isset($account->Contacts)) { 90 | foreach ($account->Contacts as $contact) { 91 | echo sprintf("Contact %s has name %s\n", $contact->Id, $contact->Name); 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ### Fetching large numbers of records 98 | 99 | If you issue a query that returns over 2000 records, only the first 2000 records 100 | will be returned by the Salesforce API. Using the `queryLocator`, you can then 101 | fetch the following results in batches of 2000. The record iterator does this 102 | automatically for you: 103 | 104 | ```php 105 | $accounts = $client->query('Select Name from Account'); 106 | echo $accounts->count() . ' accounts returned'; 107 | foreach ($accounts as $account) { 108 | // This will iterate over the 2000 first accounts, then fetch the next 2000 109 | // and iterate over these, etc. In the end, all your organisations’s accounts 110 | // will be iterated over. 111 | } 112 | ``` 113 | 114 | ### Logging 115 | 116 | To enable logging for the client, call `withLog()` on the builder. For instance when using [Monolog](https://github.com/Seldaek/monolog): 117 | 118 | ```php 119 | $log = new \Monolog\Logger('name'); 120 | $log->pushHandler(new \Monolog\Handler\StreamHandler('path/to/your.log')); 121 | 122 | $builder = new \Phpforce\SoapClient\ClientBuilder( 123 | '/path/to/your/salesforce/wsdl/sandbox.enterprise.wsdl.xml', 124 | 'username', 125 | 'password', 126 | 'security_token' 127 | ); 128 | $client = $builder->withLog($log) 129 | ->build(); 130 | ``` 131 | 132 | All requests to the Salesforce API, as well as the responses and any errors that it returns, will now be logged. 133 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phpforce/soap-client", 3 | "type": "library", 4 | "description": "A PHP client for the Salesforce SOAP API", 5 | "keywords": [ "salesforce", "crm", "soap", "force.com", "web services" ], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "David de Boer", 10 | "email": "david@ddeboer.nl", 11 | "homepage": "http://ddeboer.nl" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.0", 16 | "phpforce/common": "dev-master", 17 | "psr/log": "*" 18 | }, 19 | "require-dev": { 20 | "doctrine/common": ">=2.3" 21 | }, 22 | "suggest": { 23 | "doctrine/common": "For caching SOAP responses", 24 | "monolog/monolog": "For logging SOAP transactions" 25 | }, 26 | "autoload": { 27 | "psr-0": { 28 | "Phpforce\\SoapClient": "src" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./tests/ 7 | 8 | 9 | 10 | 11 | 12 | ./src/ 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/BulkSaver.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BulkSaver implements BulkSaverInterface 14 | { 15 | /** 16 | * Maximum number of records that may be updated or created in one call 17 | * 18 | * @var int 19 | */ 20 | private $bulkSaveLimit = 200; 21 | 22 | /** 23 | * Maximum number of records that may be deleted in one call 24 | * 25 | * @var int 26 | */ 27 | private $bulkDeleteLimit = 200; 28 | 29 | /** 30 | * Salesforce SOAP client 31 | * 32 | * @var ClientInterface 33 | */ 34 | private $client; 35 | 36 | private $bulkCreateRecords = array(); 37 | private $bulkDeleteRecords = array(); 38 | private $bulkUpdateRecords = array(); 39 | private $bulkUpsertRecords = array(); 40 | private $bulkUpsertMatchFields = array(); 41 | 42 | /** 43 | * Construct bulk saver 44 | * 45 | * @param Client $client Salesforce client 46 | */ 47 | public function __construct(ClientInterface $client) 48 | { 49 | $this->client = $client; 50 | } 51 | 52 | /** 53 | * Save a record in bulk 54 | * 55 | * @param mixed $record 56 | * @param string $objectType The record type, e.g., Account 57 | * @param string $matchField Optional match field for upserts 58 | * 59 | * @return BulkSaver 60 | */ 61 | public function save($record, $objectType, $matchField = null) 62 | { 63 | if ($matchField) { 64 | $this->addBulkUpsertRecord($record, $objectType, $matchField); 65 | } elseif (isset($record->Id) && null !== $record->Id) { 66 | $this->addBulkUpdateRecord($record, $objectType); 67 | } else { 68 | $this->addBulkCreateRecord($record, $objectType); 69 | } 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function delete($record) 78 | { 79 | if (!isset($record->Id) || !$record->Id) { 80 | throw new \InvalidArgumentException( 81 | 'Only records with an Id can be deleted' 82 | ); 83 | } 84 | 85 | $this->addBulkDeleteRecord($record); 86 | 87 | return $this; 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public function flush() 94 | { 95 | $results = array(); 96 | 97 | if (count($this->bulkDeleteRecords) > 0) { 98 | $results[] = $this->flushDeletes(); 99 | } 100 | 101 | foreach ($this->bulkCreateRecords as $type => $objects) { 102 | if (count($objects) > 0) { 103 | $results[] = $this->flushCreates($type); 104 | } 105 | } 106 | 107 | foreach ($this->bulkUpdateRecords as $type => $objects) { 108 | if (count($objects) > 0) { 109 | $results[] = $this->flushUpdates($type); 110 | } 111 | } 112 | 113 | foreach ($this->bulkUpsertRecords as $type => $objects) { 114 | if (count($objects) > 0) { 115 | $results[] = $this->flushUpserts($type); 116 | } 117 | } 118 | 119 | return $results; 120 | } 121 | 122 | /** 123 | * Get bulk save limit 124 | * 125 | * @return int 126 | */ 127 | public function getBulkSaveLimit() 128 | { 129 | return $this->bulkSaveLimit; 130 | } 131 | 132 | /** 133 | * Set bulk Save limit 134 | * @param int $bulkSaveLimit 135 | * @return BulkSaver 136 | */ 137 | public function setBulkSaveLimit($bulkSaveLimit) 138 | { 139 | $this->bulkSaveLimit = $bulkSaveLimit; 140 | return $this; 141 | } 142 | 143 | /** 144 | * Get bulk delete limit 145 | * 146 | * @return int 147 | */ 148 | public function getBulkDeleteLimit() 149 | { 150 | return $this->bulkDeleteLimit; 151 | } 152 | 153 | /** 154 | * Set bulk delete limit 155 | * 156 | * @param int $bulkDeleteLimit 157 | * @return BulkSaver 158 | */ 159 | public function setBulkDeleteLimit($bulkDeleteLimit) 160 | { 161 | $this->bulkDeleteLimit = $bulkDeleteLimit; 162 | return $this; 163 | } 164 | 165 | /** 166 | * Add a record to the create queue 167 | * 168 | * @param sObject $sObject 169 | * @param type $objectType 170 | */ 171 | private function addBulkCreateRecord($record, $objectType) 172 | { 173 | if (isset($this->bulkCreateRecords[$objectType]) 174 | && count($this->bulkCreateRecords[$objectType]) == $this->bulkSaveLimit) { 175 | $this->flushCreates($objectType); 176 | } 177 | 178 | $this->bulkCreateRecords[$objectType][] = $record; 179 | } 180 | 181 | /** 182 | * Add a record id to the bulk delete queue 183 | * 184 | * (Delete calls 185 | * 186 | * @param string $id 187 | */ 188 | private function addBulkDeleteRecord($record) 189 | { 190 | if ($this->bulkDeleteLimit === count($this->bulkDeleteRecords)) { 191 | $this->flushDeletes(); 192 | } 193 | 194 | $this->bulkDeleteRecords[] = $record; 195 | } 196 | 197 | /** 198 | * Add a record to the update queue 199 | * 200 | * @param sObject $sObject 201 | * @param string $objectType 202 | */ 203 | private function addBulkUpdateRecord($sObject, $objectType) 204 | { 205 | if (isset($this->bulkUpdateRecords[$objectType]) 206 | && count($this->bulkUpdateRecords[$objectType]) == $this->bulkSaveLimit) { 207 | $this->flushUpdates($objectType); 208 | } 209 | 210 | $this->bulkUpdateRecords[$objectType][] = $sObject; 211 | } 212 | 213 | /** 214 | * Add a record to the update queue 215 | * 216 | * @param sObject $sObject 217 | * @param string $objectType 218 | */ 219 | private function addBulkUpsertRecord($sObject, $objectType, $matchField) 220 | { 221 | $this->bulkUpsertMatchFields[$objectType] = $matchField; 222 | 223 | if (isset($this->bulkUpsertRecords[$objectType]) 224 | && count($this->bulkUpsertRecords[$objectType]) == $this->bulkSaveLimit) { 225 | $this->flushUpserts($objectType); 226 | } 227 | 228 | $this->bulkUpsertRecords[$objectType][] = $sObject; 229 | } 230 | 231 | /** 232 | * Flush creates 233 | * 234 | * @param string $objectType 235 | * @return SaveResult[] 236 | */ 237 | private function flushCreates($objectType) 238 | { 239 | $result = $this->client->create($this->bulkCreateRecords[$objectType], $objectType); 240 | $this->bulkCreateRecords[$objectType] = array(); 241 | 242 | return $result; 243 | } 244 | 245 | /** 246 | * Flush deletes 247 | * 248 | * @return SaveResult[] 249 | */ 250 | private function flushDeletes() 251 | { 252 | $ids = array(); 253 | foreach ($this->bulkDeleteRecords as $record) { 254 | $ids[] = $record->Id; 255 | } 256 | 257 | $result = $this->client->delete($ids); 258 | $this->bulkDeleteRecords = array(); 259 | 260 | return $result; 261 | } 262 | 263 | /** 264 | * Flush updates 265 | * 266 | * @param string $objectType 267 | * @return SaveResult 268 | */ 269 | private function flushUpdates($objectType) 270 | { 271 | $result = $this->client->update($this->bulkUpdateRecords[$objectType], $objectType); 272 | $this->bulkUpdateRecords[$objectType] = array(); 273 | 274 | return $result; 275 | } 276 | 277 | /** 278 | * Flush upserts 279 | * 280 | * @param string $objectType 281 | * @return SaveResult[] 282 | */ 283 | private function flushUpserts($objectType) 284 | { 285 | $result = $this->client->upsert( 286 | $this->bulkUpsertMatchFields[$objectType], 287 | $this->bulkUpsertRecords[$objectType], 288 | $objectType); 289 | $this->bulkUpsertRecords[$objectType] = array(); 290 | 291 | return $result; 292 | } 293 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/BulkSaverInterface.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Client extends AbstractHasDispatcher implements ClientInterface 16 | { 17 | /* 18 | * SOAP namespace 19 | * 20 | * @var string 21 | */ 22 | const SOAP_NAMESPACE = 'urn:enterprise.soap.sforce.com'; 23 | 24 | /** 25 | * SOAP session header 26 | * 27 | * @var \SoapHeader 28 | */ 29 | protected $sessionHeader; 30 | 31 | /** 32 | * PHP SOAP client for interacting with the Salesforce API 33 | * 34 | * @var SoapClient 35 | */ 36 | protected $soapClient; 37 | 38 | /** 39 | * @var string 40 | */ 41 | protected $username; 42 | 43 | /** 44 | * @var string 45 | */ 46 | protected $password; 47 | 48 | /** 49 | * @var string 50 | */ 51 | protected $token; 52 | 53 | /** 54 | * Type collection as derived from the WSDL 55 | * 56 | * @var array 57 | */ 58 | protected $types = array(); 59 | 60 | /** 61 | * Login result 62 | * 63 | * @var Result\LoginResult 64 | */ 65 | protected $loginResult; 66 | 67 | /** 68 | * Construct Salesforce SOAP client 69 | * 70 | * @param SoapClient $soapClient SOAP client 71 | * @param string $username Salesforce username 72 | * @param string $password Salesforce password 73 | * @param string $token Salesforce security token 74 | */ 75 | public function __construct(SoapClient $soapClient, $username, $password, $token) 76 | { 77 | $this->soapClient = $soapClient; 78 | $this->username = $username; 79 | $this->password = $password; 80 | $this->token = $token; 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | public function convertLead(array $leadConverts) 87 | { 88 | return $this->call( 89 | 'convertLead', 90 | array( 91 | 'leadConverts' => $leadConverts 92 | ) 93 | ); 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public function emptyRecycleBin(array $ids) 100 | { 101 | $result = $this->call( 102 | 'emptyRecycleBin', 103 | array('ids' => $ids) 104 | ); 105 | 106 | return $this->checkResult($result, $ids); 107 | } 108 | 109 | /** 110 | * {@inheritdoc} 111 | */ 112 | public function create(array $objects, $type) 113 | { 114 | $result = $this->call( 115 | 'create', 116 | array('sObjects' => $this->createSoapVars($objects, $type)) 117 | ); 118 | 119 | return $this->checkResult($result, $objects); 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function delete(array $ids) 126 | { 127 | $result = $this->call( 128 | 'delete', 129 | array('ids' => $ids) 130 | ); 131 | 132 | return $this->checkResult($result, $ids); 133 | } 134 | 135 | /** 136 | * {@inheritdoc} 137 | */ 138 | public function describeGlobal() 139 | { 140 | return $this->call('describeGlobal'); 141 | } 142 | 143 | /** 144 | * {@inheritdoc} 145 | */ 146 | public function describeSObjects(array $objects) 147 | { 148 | return $this->call('describeSObjects', $objects); 149 | } 150 | 151 | /** 152 | * {@inheritdoc} 153 | */ 154 | public function describeTabs() 155 | { 156 | return $this->call('describeTabs'); 157 | } 158 | 159 | /** 160 | * {@inheritdoc} 161 | */ 162 | public function getDeleted($objectType, \DateTime $startDate, \DateTime $endDate) 163 | { 164 | return $this->call( 165 | 'getDeleted', 166 | array( 167 | 'sObjectType' => $objectType, 168 | 'startDate' => $startDate, 169 | 'endDate' => $endDate 170 | ) 171 | ); 172 | } 173 | 174 | /** 175 | * {@inheritdoc} 176 | */ 177 | public function getUpdated($objectType, \DateTime $startDate, \DateTime $endDate) 178 | { 179 | return $this->call( 180 | 'getUpdated', 181 | array( 182 | 'sObjectType' => $objectType, 183 | 'startDate' => $startDate, 184 | 'endDate' => $endDate 185 | ) 186 | ); 187 | } 188 | 189 | /** 190 | * {@inheritdoc} 191 | */ 192 | public function getUserInfo() 193 | { 194 | return $this->call('getUserInfo'); 195 | } 196 | 197 | /** 198 | * {@inheritdoc} 199 | */ 200 | public function invalidateSessions(array $sessionIds) 201 | { 202 | throw new \BadMethodCallException('Not yet implemented'); 203 | } 204 | 205 | /** 206 | * {@inheritdoc} 207 | */ 208 | public function doLogin($username, $password, $token) 209 | { 210 | $result = $this->soapClient->login( 211 | array( 212 | 'username' => $username, 213 | 'password' => $password.$token 214 | ) 215 | ); 216 | $this->setLoginResult($result->result); 217 | 218 | return $result->result; 219 | } 220 | 221 | /** 222 | * {@inheritdoc} 223 | */ 224 | public function login($username, $password, $token) 225 | { 226 | return $this->doLogin($username, $password, $token); 227 | } 228 | 229 | /** 230 | * Get login result 231 | * 232 | * @return Result\LoginResult 233 | */ 234 | public function getLoginResult() 235 | { 236 | if (null === $this->loginResult) { 237 | $this->login($this->username, $this->password, $this->token); 238 | } 239 | 240 | return $this->loginResult; 241 | } 242 | 243 | /** 244 | * {@inheritdoc} 245 | */ 246 | public function logout() 247 | { 248 | $this->call('logout'); 249 | $this->sessionHeader = null; 250 | $this->setSessionId(null); 251 | } 252 | 253 | /** 254 | * {@inheritdoc} 255 | */ 256 | public function merge(array $mergeRequests, $type) 257 | { 258 | foreach ($mergeRequests as $mergeRequest) { 259 | if (!($mergeRequest instanceof Request\MergeRequest)) { 260 | throw new \InvalidArgumentException( 261 | 'Each merge request must be an instance of MergeRequest' 262 | ); 263 | } 264 | 265 | if (!$mergeRequest->masterRecord || !is_object($mergeRequest->masterRecord)) { 266 | throw new \InvalidArgumentException('masterRecord must be an object'); 267 | } 268 | 269 | if (!$mergeRequest->masterRecord->Id) { 270 | throw new \InvalidArgumentException('Id for masterRecord must be set'); 271 | } 272 | 273 | if (!is_array($mergeRequest->recordToMergeIds)) { 274 | throw new \InvalidArgumentException('recordToMergeIds must be an array'); 275 | } 276 | 277 | $mergeRequest->masterRecord = new \SoapVar( 278 | $this->createSObject($mergeRequest->masterRecord, $type), 279 | SOAP_ENC_OBJECT, 280 | $type, 281 | self::SOAP_NAMESPACE 282 | ); 283 | } 284 | 285 | return $this->call( 286 | 'merge', 287 | array('request' => $mergeRequests) 288 | ); 289 | } 290 | 291 | /** 292 | * {@inheritdoc} 293 | */ 294 | public function process(array $processResults) 295 | { 296 | throw new \BadMethodCallException('Not yet implemented'); 297 | } 298 | 299 | /** 300 | * {@inheritdoc} 301 | */ 302 | public function query($query) 303 | { 304 | $result = $this->call( 305 | 'query', 306 | array('queryString' => $query) 307 | ); 308 | 309 | return new Result\RecordIterator($this, $result); 310 | } 311 | 312 | /** 313 | * {@inheritdoc} 314 | */ 315 | public function queryAll($query) 316 | { 317 | $result = $this->call( 318 | 'queryAll', 319 | array('queryString' => $query) 320 | ); 321 | 322 | return new Result\RecordIterator($this, $result); 323 | } 324 | 325 | /** 326 | * {@inheritdoc} 327 | */ 328 | public function queryMore($queryLocator) 329 | { 330 | return $this->call( 331 | 'queryMore', 332 | array('queryLocator' => $queryLocator) 333 | ); 334 | } 335 | 336 | /** 337 | * {@inheritdoc} 338 | */ 339 | public function retrieve(array $fields, array $ids, $objectType) 340 | { 341 | return $this->call( 342 | 'retrieve', 343 | array( 344 | 'fieldList' => implode(',', $fields), 345 | 'sObjectType' => $objectType, 346 | 'ids' => $ids 347 | ) 348 | ); 349 | } 350 | 351 | /** 352 | * {@inheritdoc} 353 | */ 354 | public function search($searchString) 355 | { 356 | return $this->call( 357 | 'search', 358 | array( 359 | 'searchString' => $searchString 360 | ) 361 | ); 362 | } 363 | 364 | /** 365 | * {@inheritdoc} 366 | */ 367 | public function undelete(array $ids) 368 | { 369 | $result = $this->call( 370 | 'undelete', 371 | array('ids' => $ids) 372 | ); 373 | 374 | return $this->checkResult($result, $ids); 375 | } 376 | 377 | /** 378 | * {@inheritdoc} 379 | */ 380 | public function update(array $objects, $type) 381 | { 382 | $result = $this->call( 383 | 'update', 384 | array('sObjects' => $this->createSoapVars($objects, $type)) 385 | ); 386 | 387 | return $this->checkResult($result, $objects); 388 | } 389 | 390 | /** 391 | * {@inheritdoc} 392 | */ 393 | public function upsert($externalIdFieldName, array $objects, $type) 394 | { 395 | return $this->call( 396 | 'upsert', 397 | array( 398 | 'externalIDFieldName' => $externalIdFieldName, 399 | 'sObjects' => $this->createSoapVars($objects, $type) 400 | ) 401 | ); 402 | } 403 | 404 | /** 405 | * {@inheritdoc} 406 | * 407 | * @return Result\GetServerTimestampResult 408 | */ 409 | public function getServerTimestamp() 410 | { 411 | return $this->call('getServerTimestamp'); 412 | } 413 | 414 | /** 415 | * {@inheritdoc} 416 | */ 417 | public function resetPassword($userId) 418 | { 419 | throw new \BadMethodCallException('Not yet implemented'); 420 | } 421 | 422 | /** 423 | * {@inheritdoc} 424 | */ 425 | public function sendEmail(array $emails) 426 | { 427 | $result = $this->call( 428 | 'sendEmail', 429 | array( 430 | 'messages' => $this->createSoapVars($emails, 'SingleEmailMessage') 431 | ) 432 | ); 433 | 434 | return $this->checkResult($result, $emails); 435 | } 436 | 437 | /** 438 | * {@inheritdoc} 439 | */ 440 | public function setPassword($userId, $password) 441 | { 442 | return $this->call( 443 | 'setPassword', 444 | array( 445 | 'userId' => $userId, 446 | 'password' => $password 447 | ) 448 | ); 449 | } 450 | 451 | /** 452 | * Turn Sobjects into \SoapVars 453 | * 454 | * @param array $objects Array of objects 455 | * @param string $type Object type 456 | * 457 | * @return \SoapVar[] 458 | */ 459 | protected function createSoapVars(array $objects, $type) 460 | { 461 | $soapVars = array(); 462 | 463 | foreach ($objects as $object) { 464 | 465 | $sObject = $this->createSObject($object, $type); 466 | 467 | $xml = ''; 468 | if (isset($sObject->fieldsToNull)) { 469 | foreach ($sObject->fieldsToNull as $fieldToNull) { 470 | $xml .= '' . $fieldToNull . ''; 471 | } 472 | $fieldsToNullVar = new \SoapVar(new \SoapVar($xml, XSD_ANYXML), SOAP_ENC_ARRAY); 473 | $sObject->fieldsToNull = $fieldsToNullVar; 474 | } 475 | 476 | $soapVar = new \SoapVar($sObject, SOAP_ENC_OBJECT, $type, self::SOAP_NAMESPACE); 477 | $soapVars[] = $soapVar; 478 | } 479 | 480 | return $soapVars; 481 | } 482 | 483 | /** 484 | * Fix the fieldsToNull property for sObjects 485 | * 486 | * @param \SoapVar $object 487 | * @return \SoapVar 488 | */ 489 | protected function fixFieldsToNullXml(\SoapVar $object) 490 | { 491 | if (isset($object->enc_value->fieldsToNull) 492 | && is_array($object->enc_value->fieldsToNull) 493 | && count($object->enc_value->fieldsToNull) > 0) 494 | { 495 | $xml = ''; 496 | foreach ($object->enc_value->fieldsToNull as $fieldToNull) { 497 | $xml .= '' . $fieldToNull . ''; 498 | } 499 | return new \SoapVar(new \SoapVar($xml, XSD_ANYXML), SOAP_ENC_ARRAY); 500 | } 501 | } 502 | 503 | /** 504 | * Check response for errors 505 | * 506 | * Add each submitted object to its corresponding success or error message 507 | * 508 | * @param array $results Results 509 | * @param array $params Parameters 510 | * 511 | * @throws Exception\SaveException When Salesforce returned an error 512 | * @return array 513 | */ 514 | protected function checkResult(array $results, array $params) 515 | { 516 | $exceptions = new Exception\SaveException(); 517 | 518 | for ($i = 0; $i < count($results); $i++) { 519 | 520 | // If the param was an (s)object, set it’s Id field 521 | if (is_object($params[$i]) 522 | && (!isset($params[$i]->Id) || null === $params[$i]->Id) 523 | && $results[$i] instanceof Result\SaveResult) { 524 | $params[$i]->Id = $results[$i]->getId(); 525 | } 526 | 527 | if (!$results[$i]->isSuccess()) { 528 | $results[$i]->setParam($params[$i]); 529 | $exceptions->add($results[$i]); 530 | } 531 | } 532 | 533 | if ($exceptions->count() > 0) { 534 | throw $exceptions; 535 | } 536 | 537 | return $results; 538 | } 539 | 540 | /** 541 | * Issue a call to Salesforce API 542 | * 543 | * @param string $method SOAP operation name 544 | * @param array $params SOAP parameters 545 | * 546 | * @return array | \Traversable An empty array or a result object, such 547 | * as QueryResult, SaveResult, DeleteResult. 548 | */ 549 | protected function call($method, array $params = array()) 550 | { 551 | $this->init(); 552 | 553 | // Prepare headers 554 | $this->soapClient->__setSoapHeaders($this->getSessionHeader()); 555 | 556 | $requestEvent = new Event\RequestEvent($method, $params); 557 | $this->dispatch(Events::REQUEST, $requestEvent); 558 | 559 | try { 560 | $result = $this->soapClient->$method($params); 561 | } catch (\SoapFault $soapFault) { 562 | $faultEvent = new Event\FaultEvent($soapFault, $requestEvent); 563 | $this->dispatch(Events::FAULT, $faultEvent); 564 | 565 | throw $soapFault; 566 | } 567 | 568 | // No result e.g. for logout, delete with empty array 569 | if (!isset($result->result)) { 570 | return array(); 571 | } 572 | 573 | $this->dispatch( 574 | Events::RESPONSE, 575 | new Event\ResponseEvent($requestEvent, $result->result) 576 | ); 577 | 578 | return $result->result; 579 | } 580 | 581 | /** 582 | * Initialize connection 583 | * 584 | */ 585 | protected function init() 586 | { 587 | // If there’s no session header yet, this means we haven’t yet logged in 588 | if (!$this->getSessionHeader()) { 589 | $this->doLogin($this->username, $this->password, $this->token); 590 | } 591 | } 592 | 593 | /** 594 | * Set soap headers 595 | * 596 | * @param array $headers 597 | */ 598 | protected function setSoapHeaders(array $headers) 599 | { 600 | $soapHeaderObjects = array(); 601 | foreach ($headers as $key => $value) { 602 | $soapHeaderObjects[] = new \SoapHeader(self::SOAP_NAMESPACE, $key, $value); 603 | } 604 | 605 | $this->soapClient->__setSoapHeaders($soapHeaderObjects); 606 | } 607 | 608 | /** 609 | * Get session header 610 | * 611 | * @return \SoapHeader 612 | */ 613 | protected function getSessionHeader() 614 | { 615 | return $this->sessionHeader; 616 | } 617 | 618 | /** 619 | * Save session id to SOAP headers to be used on subsequent requests 620 | * 621 | * @param string $sessionId 622 | */ 623 | protected function setSessionId($sessionId) 624 | { 625 | $this->sessionHeader = new \SoapHeader( 626 | self::SOAP_NAMESPACE, 627 | 'SessionHeader', 628 | array( 629 | 'sessionId' => $sessionId 630 | ) 631 | ); 632 | } 633 | 634 | protected function setLoginResult(Result\LoginResult $loginResult) 635 | { 636 | $this->loginResult = $loginResult; 637 | $this->setEndpointLocation($loginResult->getServerUrl()); 638 | $this->setSessionId($loginResult->getSessionId()); 639 | } 640 | 641 | /** 642 | * After successful log in, Salesforce wants us to change the endpoint 643 | * location 644 | * 645 | * @param string $location 646 | */ 647 | protected function setEndpointLocation($location) 648 | { 649 | $this->soapClient->__setLocation($location); 650 | } 651 | 652 | /** 653 | * Create a Salesforce object 654 | * 655 | * Converts PHP \DateTimes to their SOAP equivalents. 656 | * 657 | * @param object $object Any object with public properties 658 | * @param string $objectType Salesforce object type 659 | * 660 | * @return object 661 | */ 662 | protected function createSObject($object, $objectType) 663 | { 664 | $sObject = new \stdClass(); 665 | 666 | foreach (get_object_vars($object) as $field => $value) { 667 | $type = $this->soapClient->getSoapElementType($objectType, $field); 668 | if ($field != 'Id' && !$type) { 669 | continue; 670 | } 671 | 672 | if ($value === null) { 673 | $sObject->fieldsToNull[] = $field; 674 | continue; 675 | } 676 | 677 | // As PHP \DateTime to SOAP dateTime conversion is not done 678 | // automatically with the SOAP typemap for sObjects, we do it here. 679 | switch ($type) { 680 | case 'date': 681 | if ($value instanceof \DateTime) { 682 | $value = $value->format('Y-m-d'); 683 | } 684 | break; 685 | case 'dateTime': 686 | if ($value instanceof \DateTime) { 687 | $value = $value->format('Y-m-d\TH:i:sP'); 688 | } 689 | break; 690 | case 'base64Binary': 691 | $value = base64_encode($value); 692 | break; 693 | } 694 | 695 | $sObject->$field = $value; 696 | } 697 | 698 | return $sObject; 699 | } 700 | } 701 | 702 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/ClientBuilder.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ClientBuilder 14 | { 15 | protected $log; 16 | 17 | /** 18 | * Construct client builder with required parameters 19 | * 20 | * @param string $wsdl Path to your Salesforce WSDL 21 | * @param string $username Your Salesforce username 22 | * @param string $password Your Salesforce password 23 | * @param string $token Your Salesforce security token 24 | * @param array $soapOptions Further options to be passed to the SoapClient 25 | */ 26 | public function __construct($wsdl, $username, $password, $token, array $soapOptions = array()) 27 | { 28 | $this->wsdl = $wsdl; 29 | $this->username = $username; 30 | $this->password = $password; 31 | $this->token = $token; 32 | $this->soapOptions = $soapOptions; 33 | } 34 | 35 | /** 36 | * Enable logging 37 | * 38 | * @param LoggerInterface $log Logger 39 | * 40 | * @return ClientBuilder 41 | */ 42 | public function withLog(LoggerInterface $log) 43 | { 44 | $this->log = $log; 45 | 46 | return $this; 47 | } 48 | 49 | /** 50 | * Build the Salesforce SOAP client 51 | * 52 | * @return Client 53 | */ 54 | public function build() 55 | { 56 | $soapClientFactory = new SoapClientFactory(); 57 | $soapClient = $soapClientFactory->factory($this->wsdl, $this->soapOptions); 58 | 59 | $client = new Client($soapClient, $this->username, $this->password, $this->token); 60 | 61 | if ($this->log) { 62 | $logPlugin = new LogPlugin($this->log); 63 | $client->getEventDispatcher()->addSubscriber($logPlugin); 64 | } 65 | 66 | return $client; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/ClientInterface.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface ClientInterface 12 | { 13 | /** 14 | * Converts a Lead into an Account, Contact, or (optionally) an Opportunity 15 | * 16 | * @param array $leadConverts LeadConvert[] 17 | * 18 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_convertlead.htm 19 | */ 20 | public function convertLead(array $leadConverts); 21 | 22 | /** 23 | * Create one or more Salesforce objects 24 | * 25 | * @param array $objects Array of Salesforce objects 26 | * @param string $objectType Object type, e.g., account or contact 27 | * 28 | * @return Result\SaveResult[] 29 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_create.htm 30 | */ 31 | public function create(array $objects, $objectType); 32 | 33 | /** 34 | * Deletes one or more records from your organization’s data 35 | * 36 | * @param array $ids Salesforce object IDs 37 | * 38 | * @return Result\DeleteResult[] 39 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_delete.htm 40 | */ 41 | public function delete(array $ids); 42 | 43 | /** 44 | * Retrieves a list of available objects for your organization’s data 45 | * 46 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_describeglobal.htm 47 | * 48 | * @return Result\DescribeGlobalResult 49 | */ 50 | public function describeGlobal(); 51 | 52 | /** 53 | * Describes metadata (field list and object properties) for the specified object or array of objects 54 | * 55 | * 56 | * @param array $objectNames 57 | * 58 | * @return Result\DescribeSObjectResult[] 59 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_describesobjects.htm 60 | */ 61 | public function describeSObjects(array $objectNames); 62 | 63 | /** 64 | * Returns information about the standard and custom apps available to the 65 | * logged-in 66 | * 67 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_invalidatesessions.htm 68 | */ 69 | public function describeTabs(); 70 | 71 | /** 72 | * Delete records from the recycle bin immediately 73 | * 74 | * @param array $ids Object ids 75 | */ 76 | public function emptyRecycleBin(array $ids); 77 | 78 | /** 79 | * Retrieves the list of individual records that have been deleted within 80 | * the given timespan for the specified object 81 | * 82 | * @param string $objectType Object type 83 | * @param \DateTime $startDate Start date 84 | * @param \DateTime $endDate End date 85 | * 86 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_getdeleted.htm 87 | * @return Result\GetDeletedResult 88 | */ 89 | public function getDeleted($objectType, \DateTime $startDate, \DateTime $endDate); 90 | 91 | /** 92 | * Retrieves the list of individual objects that have been updated (added or 93 | * changed) within the given timespan for the specified object 94 | * 95 | * @param string $objectType Object type 96 | * @param \DateTime $startDate Start date 97 | * @param \DateTime $endDate End date 98 | * 99 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_getupdated.htm 100 | * @return Result\GetUpdatedResult 101 | */ 102 | public function getUpdated($objectType, \DateTime $startDate, \DateTime $endDate); 103 | 104 | /** 105 | * Ends one or more sessions specified by a sessionId 106 | * 107 | * @param array $sessionIds Array of session ids 108 | * 109 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_invalidatesessions.htm 110 | */ 111 | public function invalidateSessions(array $sessionIds); 112 | 113 | /** 114 | * Logs in to the login server and starts a client session 115 | * 116 | * @param string $username Salesforce username 117 | * @param string $password Salesforce password 118 | * @param string $token Salesforce security token 119 | * 120 | * @return Result\LoginResult 121 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_login.htm 122 | */ 123 | public function login($username, $password, $token); 124 | 125 | /** 126 | * Ends the session of the logged-in user 127 | * 128 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_logout.htm 129 | */ 130 | public function logout(); 131 | 132 | /** 133 | * Merge a Salesforce lead, contact or account with one or two other 134 | * Salesforce leads, contacts or accounts 135 | * 136 | * @param array $mergeRequests Array of merge request objects 137 | * @param string $objectType Object type, e.g., account or contact 138 | * 139 | * @return Result\MergeResult[] 140 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_merge.htm 141 | */ 142 | public function merge(array $mergeRequests, $objectType); 143 | 144 | /** 145 | * Submits an array of approval process instances for approval, or processes 146 | * an array of approval process instances to be approved, rejected, or 147 | * removed 148 | * 149 | * @param array $processRequests 150 | * 151 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_process.htm 152 | */ 153 | public function process(array $processRequests); 154 | 155 | /** 156 | * Query salesforce API and return results as record iterator 157 | * 158 | * @param string $query 159 | * 160 | * @return Result\RecordIterator 161 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_query.htm 162 | */ 163 | public function query($query); 164 | 165 | /** 166 | * Retrieves data from specified objects, whether or not they have been 167 | * deleted 168 | * 169 | * @param string $query 170 | * 171 | * @return Result\QueryResult[] 172 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_queryall.htm 173 | */ 174 | public function queryAll($query); 175 | 176 | /** 177 | * Retrieves the next batch of objects from a query 178 | * 179 | * @param string $queryLocator 180 | * 181 | * @return Result\QueryResult 182 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_querymore.htm 183 | */ 184 | public function queryMore($queryLocator); 185 | 186 | /** 187 | * Retrieves one or more records based on the specified IDs 188 | * 189 | * @param array $fields Fields to retrieve on the object 190 | * @param array $ids IDs of objects to retrieve 191 | * @param string $objectType Object type, e.g., account or contact 192 | * 193 | * @return Result\SObject[] 194 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_retrieve.htm 195 | */ 196 | public function retrieve(array $fields, array $ids, $objectType); 197 | 198 | /** 199 | * Executes a text search in your organization’s data 200 | * 201 | * @param string $searchString 202 | * 203 | * @return Result\SearchResult 204 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_search.htm 205 | */ 206 | public function search($searchString); 207 | 208 | /** 209 | * Undeletes records from the Recycle Bin 210 | * 211 | * @param array $ids 212 | * 213 | * @return Result\UndeleteResult[] 214 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_undelete.htm 215 | */ 216 | public function undelete(array $ids); 217 | 218 | /** 219 | * Updates one or more existing records in your organization’s data 220 | * 221 | * @param array $objects Array of objects 222 | * @param string $objectType Object type, e.g., account or contact 223 | * 224 | * @return Result\SaveResult[] 225 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_update.htm 226 | */ 227 | public function update(array $objects, $objectType); 228 | 229 | /** 230 | * Creates new records and updates existing records; uses a custom field to 231 | * determine the presence of existing records 232 | * 233 | * @param string $externalFieldName Name of external field (must be id 234 | * or external id) 235 | * @param array $objects Array of objects 236 | * @param string $objectType Object type, e.g., account or contact 237 | * 238 | * @return Result\UpsertResult[] 239 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_upsert.htm 240 | */ 241 | public function upsert($externalFieldName, array $objects, $objectType); 242 | 243 | /** 244 | * Retrieves the current system timestamp (Coordinated Universal Time (UTC) 245 | * time zone) from the API 246 | * 247 | * @return Result\GetServerTimestampResult 248 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_getservertimestamp.htm 249 | */ 250 | public function getServerTimestamp(); 251 | 252 | /** 253 | * Get user info 254 | * 255 | * @return Result\GetUserInfoResult 256 | * @link http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_getuserinfo.htm 257 | */ 258 | public function getUserInfo(); 259 | 260 | /** 261 | * Changes a user’s password to a temporary, system-generated value 262 | * 263 | * @param string $userId 264 | */ 265 | public function resetPassword($userId); 266 | 267 | /** 268 | * Immediately sends an email message 269 | * 270 | * @param array $emails 271 | */ 272 | public function sendEmail(array $emails); 273 | 274 | /** 275 | * Sets the specified user’s password to the specified value 276 | * 277 | * @param string $userId User id 278 | * @param string $password Password 279 | */ 280 | public function setPassword($userId, $password); 281 | } 282 | 283 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Event/FaultEvent.php: -------------------------------------------------------------------------------- 1 | setSoapFault($soapFault); 15 | $this->setRequestEvent($requestEvent); 16 | } 17 | 18 | public function getSoapFault() 19 | { 20 | return $this->soapFault; 21 | } 22 | 23 | public function setSoapFault($soapFault) 24 | { 25 | $this->soapFault = $soapFault; 26 | } 27 | 28 | public function getRequestEvent() 29 | { 30 | return $this->requestEvent; 31 | } 32 | 33 | public function setRequestEvent(RequestEvent $requestEvent) 34 | { 35 | $this->requestEvent = $requestEvent; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Event/RequestEvent.php: -------------------------------------------------------------------------------- 1 | setMethod($method); 15 | $this->setParams($params); 16 | } 17 | 18 | public function getMethod() 19 | { 20 | return $this->method; 21 | } 22 | 23 | public function setMethod($method) 24 | { 25 | $this->method = $method; 26 | } 27 | 28 | public function getParams() 29 | { 30 | return $this->params; 31 | } 32 | 33 | public function setParams(array $params) 34 | { 35 | $this->params = $params; 36 | } 37 | 38 | public function getResponse() 39 | { 40 | return $this->response; 41 | } 42 | 43 | public function setResponse($response) 44 | { 45 | $this->response = $response; 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Event/ResponseEvent.php: -------------------------------------------------------------------------------- 1 | requestEvent = $requestEvent; 19 | $this->response = $response; 20 | } 21 | 22 | public function getRequestEvent() 23 | { 24 | return $this->requestEvent; 25 | } 26 | 27 | public function getResponse() 28 | { 29 | return $this->response; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/EventListener/LogTransactionListener.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 27 | } 28 | 29 | public function onSalesforceClientResponse(Event\ResponseEvent $event) 30 | { 31 | if (true === $this->logging) { 32 | $this->logger->debug('[Salesforce] response:', array($event->getResponse())); 33 | } 34 | } 35 | 36 | public function onSalesforceClientSoapFault(Event\SoapFaultEvent $event) 37 | { 38 | $this->logger->err('[Salesforce] fault: ' . $event->getSoapFault()->getMessage()); 39 | } 40 | 41 | public function onSalesforceClientError(Event\ErrorEvent $event) 42 | { 43 | $error = $event->getError(); 44 | $this->logger->err('[Salesforce] error: ' . $error->statusCode . ' - ' 45 | . $error->message, get_object_vars($error)); 46 | } 47 | 48 | public function setLogging($logging) 49 | { 50 | $this->logging = $logging; 51 | } 52 | 53 | public function getLogging() 54 | { 55 | return $this->logging; 56 | } 57 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Events.php: -------------------------------------------------------------------------------- 1 | successes; 13 | } 14 | 15 | public function setSuccesses($successes) 16 | { 17 | $this->successes = $successes; 18 | } 19 | 20 | public function getErrors() 21 | { 22 | return $this->errors; 23 | } 24 | 25 | public function setErrors($errors) 26 | { 27 | $this->errors = $errors; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Exception/SaveException.php: -------------------------------------------------------------------------------- 1 | results[] = $result; 14 | 15 | $this->message = implode( 16 | "\n", 17 | array_map(function($e) { 18 | $errors = $e->getErrors(); 19 | if (count($errors) > 0) { 20 | return $errors[0]->getMessage(); 21 | } 22 | }, $this->results 23 | ) 24 | ); 25 | } 26 | 27 | public function getIterator() 28 | { 29 | return new \ArrayIterator($this->results); 30 | } 31 | 32 | public function count() 33 | { 34 | return count($this->results); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Plugin/LogPlugin.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 24 | } 25 | 26 | public function onClientRequest(RequestEvent $event) 27 | { 28 | $this->logger->info(sprintf( 29 | '[phpforce/soap-client] request: call "%s" with params %s', 30 | $event->getMethod(), 31 | \json_encode($event->getParams()) 32 | )); 33 | } 34 | 35 | public function onClientResponse(ResponseEvent $event) 36 | { 37 | $this->logger->info(sprintf( 38 | '[phpforce/soap-client] response: %s', 39 | \print_r($event->getResponse(), true) 40 | )); 41 | } 42 | 43 | public function onClientFault(FaultEvent $event) 44 | { 45 | $this->logger->error(sprintf( 46 | '[phpforce/soap-client] fault "%s" for request "%s" with params %s', 47 | $event->getSoapFault()->getMessage(), 48 | $event->getRequestEvent()->getMethod(), 49 | \json_encode($event->getRequestEvent()->getParams()) 50 | )); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public static function getSubscribedEvents() 57 | { 58 | return array( 59 | 'phpforce.soap_client.request' => 'onClientRequest', 60 | 'phpforce.soap_client.response' => 'onClientResponse', 61 | 'phpforce.soap_client.fault' => 'onClientFault' 62 | ); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Request/BaseEmail.php: -------------------------------------------------------------------------------- 1 | cascadeDelete; 16 | } 17 | 18 | public function getChildSObject() 19 | { 20 | return $this->childSObject; 21 | } 22 | 23 | public function isDeprecatedAndHidden() 24 | { 25 | return $this->deprecatedAndHidden; 26 | } 27 | 28 | public function getField() 29 | { 30 | return $this->field; 31 | } 32 | 33 | public function getRelationshipName() 34 | { 35 | return $this->relationshipName; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/DeleteResult.php: -------------------------------------------------------------------------------- 1 | deletedDate; 28 | } 29 | 30 | /** 31 | * Get record id 32 | * 33 | * @return string 34 | */ 35 | public function getId() 36 | { 37 | return $this->id; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/DescribeGlobalResult.php: -------------------------------------------------------------------------------- 1 | activateable; 39 | } 40 | 41 | /** 42 | * @return ArrayCollection 43 | */ 44 | public function getChildRelationships() 45 | { 46 | if (!$this->childRelationships instanceof ArrayCollection) { 47 | $this->childRelationships = new ArrayCollection($this->childRelationships); 48 | } 49 | return $this->childRelationships; 50 | } 51 | 52 | /** 53 | * Get child relationship by name 54 | * 55 | * @param string $name Relationship name 56 | * @return ChildRelationship 57 | */ 58 | public function getChildRelationship($name) 59 | { 60 | return $this->getChildRelationships()->filter(function($input) use ($name) { 61 | return $name === $input->getRelationshipName(); 62 | })->first(); 63 | } 64 | 65 | /** 66 | * @return boolean 67 | */ 68 | public function isCreateable() 69 | { 70 | return $this->createable; 71 | } 72 | 73 | /** 74 | * @return boolean 75 | */ 76 | public function isCustom() 77 | { 78 | return $this->custom; 79 | } 80 | 81 | /** 82 | * @return boolean 83 | */ 84 | public function isCustomSetting() 85 | { 86 | return $this->customSetting; 87 | } 88 | 89 | /** 90 | * @return boolean 91 | */ 92 | public function isDeletable() 93 | { 94 | return $this->deletable; 95 | } 96 | 97 | /** 98 | * @return boolean 99 | */ 100 | public function isDeprecatedAndHidden() 101 | { 102 | return $this->deprecatedAndHidden; 103 | } 104 | 105 | /** 106 | * @return boolean 107 | */ 108 | public function isFeedEnabled() 109 | { 110 | return $this->feedEnabled; 111 | } 112 | 113 | /** 114 | * 115 | * @return ArrayCollection|Field[] 116 | */ 117 | public function getFields() 118 | { 119 | if (!$this->fields instanceof ArrayCollection) { 120 | $this->fields = new ArrayCollection($this->fields); 121 | } 122 | return $this->fields; 123 | } 124 | 125 | /** 126 | * Get field description by field name 127 | * 128 | * @param string $field Field name 129 | * @return Field 130 | */ 131 | public function getField($field) 132 | { 133 | return $this->getFields()->filter(function($input) use ($field) { 134 | return $field === $input->getName(); 135 | })->first(); 136 | } 137 | 138 | /** 139 | * @return string 140 | */ 141 | public function getKeyPrefix() 142 | { 143 | return $this->keyPrefix; 144 | } 145 | 146 | /** 147 | * @return string 148 | */ 149 | public function getLabel() 150 | { 151 | return $this->label; 152 | } 153 | 154 | /** 155 | * @return string 156 | */ 157 | public function getLabelPlural() 158 | { 159 | return $this->labelPlural; 160 | } 161 | 162 | /** 163 | * @return boolean 164 | */ 165 | public function isLayoutable() 166 | { 167 | return $this->layoutable; 168 | } 169 | 170 | /** 171 | * @return boolean 172 | */ 173 | public function isMergeable() 174 | { 175 | return $this->mergeable; 176 | } 177 | 178 | /** 179 | * @return string 180 | */ 181 | public function getName() 182 | { 183 | return $this->name; 184 | } 185 | 186 | /** 187 | * @return boolean 188 | */ 189 | public function isQueryable() 190 | { 191 | return $this->queryable; 192 | } 193 | 194 | /** 195 | * @return boolean 196 | */ 197 | public function isReplicateable() 198 | { 199 | return $this->replicateable; 200 | } 201 | 202 | /** 203 | * @return boolean 204 | */ 205 | public function isRetrieveable() 206 | { 207 | return $this->retrieveable; 208 | } 209 | 210 | /** 211 | * @return boolean 212 | */ 213 | public function isSearchable() 214 | { 215 | return $this->searchable; 216 | } 217 | 218 | /** 219 | * @return boolean 220 | */ 221 | public function isTriggerable() 222 | { 223 | return $this->triggerable; 224 | } 225 | 226 | /** 227 | * @return boolean 228 | */ 229 | public function isUndeletable() 230 | { 231 | return $this->undeletable; 232 | } 233 | 234 | /** 235 | * @return boolean 236 | */ 237 | public function isUpdateable() 238 | { 239 | return $this->updateable; 240 | } 241 | 242 | /** 243 | * Get all fields that constitute relationships to other objects 244 | * 245 | * @return ArrayCollection 246 | */ 247 | public function getRelationshipFields() 248 | { 249 | return $this->getFields()->filter(function($field) { 250 | return null !== $field->getRelationshipName(); 251 | }); 252 | } 253 | 254 | /** 255 | * Get a relationship field 256 | * 257 | * @param string $name 258 | * @return Field 259 | */ 260 | public function getRelationshipField($name) 261 | { 262 | return $this->getRelationshipFields()->filter(function($field) use ($name) { 263 | return $name === $field->getName(); 264 | })->first(); 265 | } 266 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/DescribeSObjectResult/Field.php: -------------------------------------------------------------------------------- 1 | autoNumber; 45 | } 46 | 47 | public function getByteLength() 48 | { 49 | return $this->byteLength; 50 | } 51 | 52 | public function isCalculated() 53 | { 54 | return $this->calculated; 55 | } 56 | 57 | public function isCaseSensitive() 58 | { 59 | return $this->caseSensitive; 60 | } 61 | 62 | public function isCreateable() 63 | { 64 | return $this->createable; 65 | } 66 | 67 | public function isCustom() 68 | { 69 | return $this->custom; 70 | } 71 | 72 | public function isDefaultedOnCreate() 73 | { 74 | return $this->defaultedOnCreate; 75 | } 76 | 77 | public function getDependentPicklist() 78 | { 79 | return $this->dependentPicklist; 80 | } 81 | 82 | public function isDeprecatedAndHidden() 83 | { 84 | return $this->deprecatedAndHidden; 85 | } 86 | 87 | public function getDigits() 88 | { 89 | return $this->digits; 90 | } 91 | 92 | public function isFilterable() 93 | { 94 | return $this->filterable; 95 | } 96 | 97 | public function isGroupable() 98 | { 99 | return $this->groupable; 100 | } 101 | 102 | public function getHtmlFormatted() 103 | { 104 | return $this->htmlFormatted; 105 | } 106 | 107 | public function isIdLookup() 108 | { 109 | return $this->idLookup; 110 | } 111 | 112 | public function getInlineHelpText() 113 | { 114 | return $this->inlineHelpText; 115 | } 116 | 117 | public function getLabel() 118 | { 119 | return $this->label; 120 | } 121 | 122 | public function getLength() 123 | { 124 | return $this->length; 125 | } 126 | 127 | public function getName() 128 | { 129 | return $this->name; 130 | } 131 | 132 | public function getNameField() 133 | { 134 | return $this->nameField; 135 | } 136 | 137 | public function isNamePointing() 138 | { 139 | return $this->namePointing; 140 | } 141 | 142 | public function isNillable() 143 | { 144 | return $this->nillable; 145 | } 146 | 147 | public function getPicklistValues() 148 | { 149 | return $this->picklistValues; 150 | } 151 | 152 | public function getPrecision() 153 | { 154 | return $this->precision; 155 | } 156 | 157 | public function getRelationshipName() 158 | { 159 | return $this->relationshipName; 160 | } 161 | 162 | public function getRelationshipOrder() 163 | { 164 | return $this->relationshipOrder; 165 | } 166 | 167 | public function getReferenceTo() 168 | { 169 | return $this->referenceTo; 170 | } 171 | 172 | public function getRestrictedPicklist() 173 | { 174 | return $this->restrictedPicklist; 175 | } 176 | 177 | public function getScale() 178 | { 179 | return $this->scale; 180 | } 181 | 182 | public function getSoapType() 183 | { 184 | return $this->soapType; 185 | } 186 | 187 | /** 188 | * @return boolean 189 | */ 190 | public function isSortable() 191 | { 192 | return $this->sortable; 193 | } 194 | 195 | /** 196 | * @return string 197 | */ 198 | public function getType() 199 | { 200 | return $this->type; 201 | } 202 | 203 | /** 204 | * @return boolean 205 | */ 206 | public function isUnique() 207 | { 208 | return $this->unique; 209 | } 210 | 211 | /** 212 | * @return boolean 213 | */ 214 | public function isUpdateable() 215 | { 216 | return $this->updateable; 217 | } 218 | 219 | /** 220 | * @return boolean 221 | */ 222 | public function isWriteRequiresMasterRead() 223 | { 224 | return $this->writeRequiresMasterRead; 225 | } 226 | 227 | /** 228 | * Get whether this field references a certain object 229 | * 230 | * @param string $object Name of the referenced object 231 | * @return boolean 232 | */ 233 | public function references($object) 234 | { 235 | foreach ($this->referenceTo as $referencedObject) { 236 | return $object == $referencedObject; 237 | } 238 | 239 | return false; 240 | } 241 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/DescribeTab.php: -------------------------------------------------------------------------------- 1 | custom; 22 | } 23 | 24 | public function getIconUrl() 25 | { 26 | return $this->iconUrl; 27 | } 28 | 29 | public function getLabel() 30 | { 31 | return $this->label; 32 | } 33 | 34 | public function getMiniIconUrl() 35 | { 36 | return $this->miniIconUrl; 37 | } 38 | 39 | public function getSobjectName() 40 | { 41 | return $this->sobjectName; 42 | } 43 | 44 | public function getUrl() 45 | { 46 | return $this->url; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/DescribeTabSetResult.php: -------------------------------------------------------------------------------- 1 | " 12 | */ 13 | class DescribeTabsResult 14 | { 15 | //put your code here 16 | } 17 | 18 | ?> 19 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/EmptyRecycleBinResult.php: -------------------------------------------------------------------------------- 1 | fields; 20 | } 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function getMessage() 26 | { 27 | return $this->message; 28 | } 29 | 30 | /** 31 | * @return string 32 | */ 33 | public function getStatusCode() 34 | { 35 | return $this->statusCode; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/GetDeletedResult.php: -------------------------------------------------------------------------------- 1 | earliestDateAvailable; 19 | } 20 | 21 | /** 22 | * @return \DateTime 23 | */ 24 | public function getLatestDateCovered() 25 | { 26 | return $this->latestDateCovered; 27 | } 28 | 29 | /** 30 | * @return DeletedRecord[] 31 | */ 32 | public function getDeletedRecords() 33 | { 34 | return $this->deletedRecords; 35 | } 36 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/GetServerTimestampResult.php: -------------------------------------------------------------------------------- 1 | timestamp; 15 | } 16 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/GetUpdatedResult.php: -------------------------------------------------------------------------------- 1 | ids; 17 | } 18 | 19 | /** 20 | * @return \DateTime 21 | */ 22 | public function getLatestDateCovered() 23 | { 24 | return $this->latestDateCovered; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/GetUserInfoResult.php: -------------------------------------------------------------------------------- 1 | accessibilityMode; 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function getCurrencySymbol() 66 | { 67 | return $this->currencySymbol; 68 | } 69 | 70 | /** 71 | * @return int 72 | */ 73 | public function getOrgAttachmentFileSizeLimit() 74 | { 75 | return $this->orgAttachmentFileSizeLimit; 76 | } 77 | 78 | /** 79 | * @return boolean 80 | */ 81 | public function getOrgDisallowHtmlAttachments() 82 | { 83 | return $this->orgDisallowHtmlAttachments; 84 | } 85 | 86 | /** 87 | * @return boolean 88 | */ 89 | public function getOrgHasPersonAccounts() 90 | { 91 | return $this->orgHasPersonAccounts; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getOrganizationId() 98 | { 99 | return $this->organizationId; 100 | } 101 | 102 | /** 103 | * @return boolean 104 | */ 105 | public function getOrganizationMultiCurrency() 106 | { 107 | return $this->organizationMultiCurrency; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function getOrganizationName() 114 | { 115 | return $this->organizationName; 116 | } 117 | 118 | /** 119 | * @return string 120 | */ 121 | public function getProfileId() 122 | { 123 | return $this->profileId; 124 | } 125 | 126 | /** 127 | * @return string 128 | */ 129 | public function getRoleId() 130 | { 131 | return $this->roleId; 132 | } 133 | 134 | /** 135 | * @return string 136 | */ 137 | public function getSessionSecondsValid() 138 | { 139 | return $this->sessionSecondsValid; 140 | } 141 | 142 | /** 143 | * @return string 144 | */ 145 | public function getUserDefaultCurrencyIsoCode() 146 | { 147 | return $this->userDefaultCurrencyIsoCode; 148 | } 149 | 150 | /** 151 | * @return string 152 | */ 153 | public function getUserEmail() 154 | { 155 | return $this->userEmail; 156 | } 157 | 158 | /** 159 | * @return string 160 | */ 161 | public function getUserFullName() 162 | { 163 | return $this->userFullName; 164 | } 165 | 166 | /** 167 | * @return string 168 | */ 169 | public function getUserId() 170 | { 171 | return $this->userId; 172 | } 173 | 174 | /** 175 | * @return string 176 | */ 177 | public function getUserLanguage() 178 | { 179 | return $this->userLanguage; 180 | } 181 | 182 | /** 183 | * @return string 184 | */ 185 | public function getUserLocale() 186 | { 187 | return $this->userLocale; 188 | } 189 | 190 | /** 191 | * @return string 192 | */ 193 | public function getUserName() 194 | { 195 | return $this->userName; 196 | } 197 | 198 | /** 199 | * @return string 200 | */ 201 | public function getUserTimeZone() 202 | { 203 | return $this->userTimeZone; 204 | } 205 | 206 | /** 207 | * @return string 208 | */ 209 | public function getUserType() 210 | { 211 | return $this->userType; 212 | } 213 | 214 | /** 215 | * @return string 216 | */ 217 | public function getUserUiSkin() 218 | { 219 | return $this->userUiSkin; 220 | } 221 | 222 | /** 223 | * @return string 224 | */ 225 | public function getOrgDefaultCurrencyIsoCode() 226 | { 227 | return $this->orgDefaultCurrencyIsoCode; 228 | } 229 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/LeadConvertResult.php: -------------------------------------------------------------------------------- 1 | metadataServerUrl; 24 | } 25 | 26 | /** 27 | * @return boolean 28 | */ 29 | public function getPasswordExpired() 30 | { 31 | return $this->passwordExpired; 32 | } 33 | 34 | /** 35 | * @return boolean 36 | */ 37 | public function getSandbox() 38 | { 39 | return $this->sandbox; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function getServerUrl() 46 | { 47 | return $this->serverUrl; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getSessionId() 54 | { 55 | return $this->sessionId; 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getUserId() 62 | { 63 | return $this->userId; 64 | } 65 | 66 | /** 67 | * @return GetUserInfoResult 68 | */ 69 | public function getUserInfo() 70 | { 71 | return $this->userInfo; 72 | } 73 | 74 | /** 75 | * Get the server instance, e.g. ‘eu1’ or ‘cs7’ 76 | * 77 | * @return string 78 | */ 79 | public function getServerInstance() 80 | { 81 | if (null === $this->serverUrl) { 82 | throw new \UnexpectedValueException('Server URL must be set'); 83 | } 84 | 85 | $match = preg_match( 86 | '/https:\/\/(?[^-]+)\.salesforce\.com/', 87 | $this->serverUrl, 88 | $matches 89 | ); 90 | 91 | if (!$match || !isset($matches['instance'])) { 92 | throw new \RuntimeException('Server instance could not be determined'); 93 | } 94 | 95 | return $matches['instance']; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/MergeResult.php: -------------------------------------------------------------------------------- 1 | id; 37 | } 38 | 39 | /** 40 | * @return boolean 41 | */ 42 | public function getSuccess() 43 | { 44 | return $this->success; 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getErrors() 51 | { 52 | return $this->errors; 53 | } 54 | 55 | public function getMergedRecordIds() 56 | { 57 | return $this->mergedRecordIds; 58 | } 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/ProcessResult.php: -------------------------------------------------------------------------------- 1 | done; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getQueryLocator() 27 | { 28 | return $this->queryLocator; 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function getRecords() 35 | { 36 | return $this->records; 37 | } 38 | 39 | /** 40 | * @return int 41 | */ 42 | public function getSize() 43 | { 44 | return $this->size; 45 | } 46 | 47 | public function getRecord($index) 48 | { 49 | if (isset($this->records[$index])) { 50 | return $this->records[$index]; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/RecordIterator.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RecordIterator implements \SeekableIterator, \Countable 16 | { 17 | /** 18 | * Salesforce client 19 | * 20 | * @var Client 21 | */ 22 | protected $client; 23 | 24 | /** 25 | * Query result 26 | * 27 | * @var QueryResult 28 | */ 29 | private $queryResult; 30 | 31 | /** 32 | * Iterator pointer 33 | * 34 | * @var int 35 | */ 36 | protected $pointer = 0; 37 | 38 | /** 39 | * Cached current domain model object 40 | * 41 | * @var mixed 42 | */ 43 | protected $current; 44 | 45 | /** 46 | * Construct a record iterator 47 | * 48 | * @param client $client 49 | * @param string $result 50 | */ 51 | public function __construct(Client $client, QueryResult $result) 52 | { 53 | $this->client = $client; 54 | $this->setQueryResult($result); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | * @return object 60 | */ 61 | public function current() 62 | { 63 | return $this->current; 64 | } 65 | 66 | /** 67 | * Get record at pointer, or, if there is no record, try to query Salesforce 68 | * for more records 69 | * 70 | * @param int $pointer 71 | * 72 | * @return object 73 | */ 74 | protected function getObjectAt($pointer) 75 | { 76 | if ($this->queryResult->getRecord($pointer)) { 77 | $this->current = $this->queryResult->getRecord($pointer); 78 | 79 | foreach ($this->current as $key => &$value) { 80 | if ($value instanceof QueryResult) { 81 | $value = new RecordIterator($this->client, $value); 82 | } 83 | } 84 | 85 | return $this->current; 86 | } 87 | 88 | // If no record was found at pointer, see if there are more records 89 | // available for querying 90 | if (!$this->queryResult->isDone()) { 91 | $this->queryMore(); 92 | 93 | return $this->getObjectAt($this->pointer); 94 | } 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | * 100 | * @return int|null 101 | */ 102 | public function key() 103 | { 104 | return $this->pointer; 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function next() 111 | { 112 | $this->pointer++; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function rewind() 119 | { 120 | $this->pointer = 0; 121 | } 122 | 123 | /** 124 | * {@inheritdoc} 125 | * 126 | * @return boolean 127 | */ 128 | public function valid() 129 | { 130 | return null != $this->getObjectAt($this->pointer); 131 | } 132 | 133 | /** 134 | * Get first object 135 | * 136 | * @return object 137 | */ 138 | public function first() 139 | { 140 | return $this->getObjectAt(0); 141 | } 142 | 143 | /** 144 | * Set query result, as it is returned from Salesforce 145 | * 146 | * @param QueryResult $result 147 | * 148 | * @return RecordIterator 149 | */ 150 | public function setQueryResult(QueryResult $result) 151 | { 152 | $this->queryResult = $result; 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * Query Salesforce for more records and rewind iterator 159 | * 160 | */ 161 | protected function queryMore() 162 | { 163 | $result = $this->client->queryMore($this->queryResult->getQueryLocator()); 164 | $this->setQueryResult($result); 165 | $this->rewind(); 166 | } 167 | 168 | /** 169 | * Get total number of records returned from Salesforce 170 | * 171 | * @return int 172 | */ 173 | public function count() 174 | { 175 | return $this->queryResult->getSize(); 176 | } 177 | 178 | /** 179 | * @param int $position 180 | */ 181 | public function seek($position) 182 | { 183 | return $this->getObjectAt($position); 184 | } 185 | 186 | /** 187 | * Get sorted result iterator for the records on the current page 188 | * 189 | * Note: this method will not query Salesforce for records outside the 190 | * current page. If you wish to sort larger sets of Salesforce records, do 191 | * so in the select query you issue to the Salesforce API. 192 | * 193 | * @param string $by 194 | * 195 | * @return \ArrayIterator 196 | */ 197 | public function sort($by) 198 | { 199 | $by = ucfirst($by); 200 | $array = $this->queryResult->getRecords(); 201 | usort($array, function($a, $b) use ($by) { 202 | // These two ifs take care of moving empty values to the end of the 203 | // array instead of the beginning 204 | if (!isset($a->$by) || !$a->$by) { 205 | return 1; 206 | } 207 | 208 | if (!isset($b->$by) || !$b->$by) { 209 | return -1; 210 | } 211 | 212 | return strcmp($a->$by, $b->$by); 213 | }); 214 | 215 | return new \ArrayIterator($array); 216 | } 217 | 218 | /** 219 | * Get the query result as returned by Salesforce 220 | * 221 | * @return QueryResult 222 | */ 223 | public function getQueryResult() 224 | { 225 | return $this->queryResult; 226 | } 227 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/SObject.php: -------------------------------------------------------------------------------- 1 | Id; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/SaveResult.php: -------------------------------------------------------------------------------- 1 | id; 45 | } 46 | 47 | /** 48 | * @return boolean 49 | */ 50 | public function isSuccess() 51 | { 52 | return $this->success; 53 | } 54 | 55 | /** 56 | * @return Error[] 57 | */ 58 | public function getErrors() 59 | { 60 | return $this->errors; 61 | } 62 | 63 | /** 64 | * @return mixed 65 | */ 66 | public function getParam() 67 | { 68 | return $this->param; 69 | } 70 | 71 | /** 72 | * @param mixed $param 73 | */ 74 | public function setParam($param) 75 | { 76 | $this->param = $param; 77 | } 78 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/SearchResult.php: -------------------------------------------------------------------------------- 1 | targetObjectId; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/SendEmailResult.php: -------------------------------------------------------------------------------- 1 | errors; 17 | } 18 | 19 | public function isSuccess() 20 | { 21 | return $this->success; 22 | } 23 | 24 | public function getParam() 25 | { 26 | return $this->param; 27 | } 28 | 29 | public function setParam($param) 30 | { 31 | $this->param = $param; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Result/UndeleteResult.php: -------------------------------------------------------------------------------- 1 | created; 20 | } 21 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Soap/SoapClient.php: -------------------------------------------------------------------------------- 1 | types) { 26 | 27 | $soapTypes = $this->__getTypes(); 28 | foreach ($soapTypes as $soapType) { 29 | $properties = array(); 30 | $lines = explode("\n", $soapType); 31 | if (!preg_match('/struct (.*) {/', $lines[0], $matches)) { 32 | continue; 33 | } 34 | $typeName = $matches[1]; 35 | 36 | foreach (array_slice($lines, 1) as $line) { 37 | if ($line == '}') { 38 | continue; 39 | } 40 | preg_match('/\s* (.*) (.*);/', $line, $matches); 41 | $properties[$matches[2]] = $matches[1]; 42 | } 43 | 44 | // Since every object extends sObject, need to append sObject elements to all native and custom objects 45 | if ($typeName !== 'sObject' && array_key_exists('sObject', $this->types)) { 46 | $properties = array_merge($properties, $this->types['sObject']); 47 | } 48 | 49 | $this->types[$typeName] = $properties; 50 | } 51 | } 52 | 53 | return $this->types; 54 | } 55 | 56 | /** 57 | * Get a SOAP type’s elements 58 | * 59 | * @param string $type Object name 60 | * @return array Elements for the type 61 | */ 62 | 63 | /** 64 | * Get SOAP elements for a complexType 65 | * 66 | * @param string $complexType Name of SOAP complexType 67 | * 68 | * @return array Names of elements and their types 69 | */ 70 | public function getSoapElements($complexType) 71 | { 72 | $types = $this->getSoapTypes(); 73 | if (isset($types[$complexType])) { 74 | return $types[$complexType]; 75 | } 76 | } 77 | 78 | /** 79 | * Get a SOAP type’s element 80 | * 81 | * @param string $complexType Name of SOAP complexType 82 | * @param string $element Name of element belonging to SOAP complexType 83 | * 84 | * @return string 85 | */ 86 | public function getSoapElementType($complexType, $element) 87 | { 88 | $elements = $this->getSoapElements($complexType); 89 | if ($elements && isset($elements[$element])) { 90 | return $elements[$element]; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Soap/SoapClientFactory.php: -------------------------------------------------------------------------------- 1 | 'Phpforce\SoapClient\Result\ChildRelationship', 19 | 'DeleteResult' => 'Phpforce\SoapClient\Result\DeleteResult', 20 | 'DeletedRecord' => 'Phpforce\SoapClient\Result\DeletedRecord', 21 | 'DescribeGlobalResult' => 'Phpforce\SoapClient\Result\DescribeGlobalResult', 22 | 'DescribeGlobalSObjectResult' => 'Phpforce\SoapClient\Result\DescribeGlobalSObjectResult', 23 | 'DescribeSObjectResult' => 'Phpforce\SoapClient\Result\DescribeSObjectResult', 24 | 'DescribeTab' => 'Phpforce\SoapClient\Result\DescribeTab', 25 | 'EmptyRecycleBinResult' => 'Phpforce\SoapClient\Result\EmptyRecycleBinResult', 26 | 'Error' => 'Phpforce\SoapClient\Result\Error', 27 | 'Field' => 'Phpforce\SoapClient\Result\DescribeSObjectResult\Field', 28 | 'GetDeletedResult' => 'Phpforce\SoapClient\Result\GetDeletedResult', 29 | 'GetServerTimestampResult' => 'Phpforce\SoapClient\Result\GetServerTimestampResult', 30 | 'GetUpdatedResult' => 'Phpforce\SoapClient\Result\GetUpdatedResult', 31 | 'GetUserInfoResult' => 'Phpforce\SoapClient\Result\GetUserInfoResult', 32 | 'LeadConvert' => 'Phpforce\SoapClient\Request\LeadConvert', 33 | 'LeadConvertResult' => 'Phpforce\SoapClient\Result\LeadConvertResult', 34 | 'LoginResult' => 'Phpforce\SoapClient\Result\LoginResult', 35 | 'MergeResult' => 'Phpforce\SoapClient\Result\MergeResult', 36 | 'QueryResult' => 'Phpforce\SoapClient\Result\QueryResult', 37 | 'SaveResult' => 'Phpforce\SoapClient\Result\SaveResult', 38 | 'SearchResult' => 'Phpforce\SoapClient\Result\SearchResult', 39 | 'SendEmailError' => 'Phpforce\SoapClient\Result\SendEmailError', 40 | 'SendEmailResult' => 'Phpforce\SoapClient\Result\SendEmailResult', 41 | 'SingleEmailMessage' => 'Phpforce\SoapClient\Request\SingleEmailMessage', 42 | 'sObject' => 'Phpforce\SoapClient\Result\SObject', 43 | 'UndeleteResult' => 'Phpforce\SoapClient\Result\UndeleteResult', 44 | 'UpsertResult' => 'Phpforce\SoapClient\Result\UpsertResult', 45 | ); 46 | 47 | /** 48 | * Type converters collection 49 | * 50 | * @var TypeConverter\TypeConverterCollection 51 | */ 52 | protected $typeConverters; 53 | 54 | /** 55 | * @param string $wsdl Path to WSDL file 56 | * @param array $soapOptions 57 | * @return SoapClient 58 | */ 59 | public function factory($wsdl, array $soapOptions = array()) 60 | { 61 | $defaults = array( 62 | 'trace' => 1, 63 | 'features' => \SOAP_SINGLE_ELEMENT_ARRAYS, 64 | 'classmap' => $this->classmap, 65 | 'typemap' => $this->getTypeConverters()->getTypemap(), 66 | 'cache_wsdl' => \WSDL_CACHE_MEMORY 67 | ); 68 | 69 | $options = array_merge($defaults, $soapOptions); 70 | 71 | return new SoapClient($wsdl, $options); 72 | } 73 | 74 | /** 75 | * test 76 | * 77 | * @param string $soap SOAP class 78 | * @param string $php PHP class 79 | */ 80 | public function setClassmapping($soap, $php) 81 | { 82 | $this->classmap[$soap] = $php; 83 | } 84 | 85 | /** 86 | * Get type converter collection that will be used for the \SoapClient 87 | * 88 | * @return TypeConverter\TypeConverterCollection 89 | */ 90 | public function getTypeConverters() 91 | { 92 | if (null === $this->typeConverters) { 93 | $this->typeConverters = new TypeConverter\TypeConverterCollection( 94 | array( 95 | new TypeConverter\DateTimeTypeConverter(), 96 | new TypeConverter\DateTypeConverter() 97 | ) 98 | ); 99 | } 100 | 101 | return $this->typeConverters; 102 | } 103 | 104 | /** 105 | * Set type converter collection 106 | * 107 | * @param TypeConverter\TypeConverterCollection $typeConverters Type converter collection 108 | * 109 | * @return SoapClientFactory 110 | */ 111 | public function setTypeConverters(TypeConverter\TypeConverterCollection $typeConverters) 112 | { 113 | $this->typeConverters = $typeConverters; 114 | 115 | return $this; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Soap/TypeConverter/DateTimeTypeConverter.php: -------------------------------------------------------------------------------- 1 | loadXML($data); 32 | 33 | if ('' === $doc->textContent) { 34 | return null; 35 | } 36 | 37 | $dateTime = new \DateTime($doc->textContent); 38 | $dateTime->setTimezone(new \DateTimeZone(date_default_timezone_get())); 39 | 40 | return $dateTime; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function convertPhpToXml($php) 47 | { 48 | return sprintf('<%1$s>%2$s', $this->getTypeName(), $php->format('Y-m-d\TH:i:sP')); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Soap/TypeConverter/DateTypeConverter.php: -------------------------------------------------------------------------------- 1 | loadXML($data); 32 | 33 | if ('' === $doc->textContent) { 34 | return null; 35 | } 36 | 37 | $dateTime = new \DateTime($doc->textContent); 38 | 39 | return $dateTime; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function convertPhpToXml($php) 46 | { 47 | return sprintf('<%1$s>%2$s', $this->getTypeName(), $php->format('Y-m-d')); 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Soap/TypeConverter/TypeConverterCollection.php: -------------------------------------------------------------------------------- 1 | add($converter); 23 | } 24 | } 25 | 26 | /** 27 | * Add a type converter to the collection 28 | * 29 | * @param TypeConverterInterface $converter Type converter 30 | * 31 | * @return TypeConverterCollection 32 | * @throws \InvalidArgumentException 33 | */ 34 | public function add(TypeConverterInterface $converter) 35 | { 36 | if ($this->has($converter->getTypeNamespace(), $converter->getTypeName())) { 37 | throw new \InvalidArgumentException( 38 | 'Converter for this type already exists' 39 | ); 40 | } 41 | 42 | return $this->set($converter); 43 | } 44 | 45 | /** 46 | * Set (overwrite) a type converter in the collection 47 | * 48 | * @param TypeConverterInterface $converter Type converter 49 | * 50 | * @return TypeConverterCollection 51 | */ 52 | public function set(TypeConverterInterface $converter) 53 | { 54 | $this->converters[$converter->getTypeNamespace() . ':' 55 | . $converter->getTypeName()] = $converter; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Returns true if the collection contains a type converter for a certain 62 | * namespace and name 63 | * 64 | * @param string $namespace Converter namespace 65 | * @param string $name Converter name 66 | * 67 | * @return boolean 68 | */ 69 | public function has($namespace, $name) 70 | { 71 | if (isset($this->converters[$namespace . ':' . $name])) { 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | /** 79 | * Get this collection as a typemap that can be used in PHP's \SoapClient 80 | * 81 | * @return array 82 | */ 83 | public function getTypemap() 84 | { 85 | $typemap = array(); 86 | 87 | foreach ($this->converters as $converter) { 88 | $typemap[] = array( 89 | 'type_name' => $converter->getTypeName(), 90 | 'type_ns' => $converter->getTypeNamespace(), 91 | 'from_xml' => function($input) use ($converter) { 92 | return $converter->convertXmlToPhp($input); 93 | }, 94 | 'to_xml' => function($input) use ($converter) { 95 | return $converter->convertPhpToXml($input); 96 | }, 97 | ); 98 | } 99 | 100 | return $typemap; 101 | } 102 | } -------------------------------------------------------------------------------- /src/Phpforce/SoapClient/Soap/TypeConverter/TypeConverterInterface.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('Phpforce\SoapClient\Client') 11 | ->disableOriginalConstructor() 12 | ->getMock(); 13 | 14 | $client 15 | ->expects($this->exactly(3)) 16 | ->method('create') 17 | ->with($this->isType('array'), $this->equalTo('Account')); 18 | 19 | $bulkSaver = new BulkSaver($client); 20 | 21 | for ($i = 0; $i < 401; $i++) { 22 | $record = new \stdClass(); 23 | $record->Name = 'An account'; 24 | $bulkSaver->save($record, 'Account'); 25 | } 26 | $bulkSaver->flush(); 27 | } 28 | 29 | public function testUpdate() 30 | { 31 | $client = $this->getClient(); 32 | 33 | $client 34 | ->expects($this->exactly(2)) 35 | ->method('update') 36 | ->with($this->isType('array'), $this->equalTo('Account')); 37 | 38 | $bulkSaver = new BulkSaver($client); 39 | 40 | for ($i = 0; $i < 400; $i++) { 41 | $record = new \stdClass(); 42 | $record->Name = 'An account'; 43 | $record->Id = 123; 44 | $bulkSaver->save($record, 'Account'); 45 | } 46 | $bulkSaver->flush(); 47 | } 48 | 49 | public function testDelete() 50 | { 51 | $tasks = array(); 52 | for ($i = 0; $i < 202; $i++) { 53 | $task = new \stdClass(); 54 | $task->Id = $i+1; 55 | $tasks[] = $task; 56 | } 57 | 58 | $client = $this->getClient(); 59 | $client->expects($this->at(0)) 60 | ->method('delete') 61 | ->with(range(1, 200)); 62 | 63 | $client->expects($this->at(1)) 64 | ->method('delete') 65 | ->with(range(201, 202)); 66 | 67 | $bulkSaver = new BulkSaver($client); 68 | foreach ($tasks as $task) { 69 | $bulkSaver->delete($task); 70 | } 71 | $bulkSaver->flush(); 72 | } 73 | 74 | public function testDeleteWithoutIdThrowsException() 75 | { 76 | $client = $this->getClient(); 77 | $bulkSaver = new BulkSaver($client); 78 | $invalidRecord = new \stdClass(); 79 | $this->setExpectedException('\InvalidArgumentException', 'Only records with an Id can be deleted'); 80 | $bulkSaver->delete($invalidRecord); 81 | } 82 | 83 | public function testUpsert() 84 | { 85 | $client = $this->getClient(); 86 | $client->expects($this->exactly(2)) 87 | ->method('upsert') 88 | ->with('Name', $this->isType('array'), 'Account'); 89 | 90 | $account = new \stdClass(); 91 | $account->Name = 'Upsert this'; 92 | $account->BillingPostalCode = '1234 AB'; 93 | $bulkSaver = new BulkSaver($client); 94 | 95 | for ($i = 0; $i < 201; $i++) { 96 | $bulkSaver->save($account, 'Account', 'Name'); 97 | } 98 | $bulkSaver->flush(); 99 | } 100 | 101 | public function testFlushEmpty() 102 | { 103 | $bulkSaver = new BulkSaver($this->getClient()); 104 | $bulkSaver->flush(); 105 | } 106 | 107 | protected function getClient() 108 | { 109 | return $this->getMockBuilder('Phpforce\SoapClient\Client') 110 | ->disableOriginalConstructor() 111 | ->getMock(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/Phpforce/SoapClient/Tests/ClientTest.php: -------------------------------------------------------------------------------- 1 | createMock(new Result\DeleteResult(), array( 17 | 'id' => '001M0000008tWTFIA2', 18 | 'success' => true 19 | )); 20 | 21 | $result = new \stdClass; 22 | $result->result = array($deleteResult); 23 | 24 | $soapClient = $this->getSoapClient(array('delete')); 25 | $soapClient->expects($this->once()) 26 | ->method('delete') 27 | ->with(array('ids' => array('001M0000008tWTFIA2'))) 28 | ->will($this->returnValue($result)); 29 | 30 | $this->getClient($soapClient)->delete(array('001M0000008tWTFIA2')); 31 | 32 | } 33 | 34 | public function testQuery() 35 | { 36 | $soapClient = $this->getSoapClient(array('query')); 37 | 38 | $result = $this->getResultMock(new Result\QueryResult, array( 39 | 'size' => 1, 40 | 'done' => true, 41 | 'records' => array( 42 | (object) array( 43 | 'Id' => '001M0000008tWTFIA2', 44 | 'Name' => 'Company' 45 | ) 46 | ) 47 | )); 48 | 49 | $soapClient->expects($this->any()) 50 | ->method('query') 51 | ->will($this->returnValue($result)); 52 | 53 | $client = new Client($soapClient, 'username', 'password', 'token'); 54 | $result = $client->query('Select Name from Account Limit 1'); 55 | $this->assertInstanceOf('Phpforce\SoapClient\Result\RecordIterator', $result); 56 | $this->assertEquals(1, $result->count()); 57 | } 58 | 59 | public function testInvalidQueryThrowsSoapFault() 60 | { 61 | $soapClient = $this->getSoapClient(array('query')); 62 | $soapClient 63 | ->expects($this->once()) 64 | ->method('query') 65 | ->will($this->throwException(new \SoapFault('C', "INVALID_FIELD: 66 | Select aId, Name from Account LIMIT 1 67 | ^ 68 | ERROR at Row:1:Column:8 69 | No such column 'aId' on entity 'Account'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names."))); 70 | 71 | $client = $this->getClient($soapClient); 72 | 73 | $this->setExpectedException('\SoapFault'); 74 | $client->query('Select NonExistingField from Account'); 75 | } 76 | 77 | public function testInvalidUpdateResultsInError() 78 | { 79 | $error = $this->createMock(new Result\Error(), array( 80 | 'fields' => array('Id'), 81 | 'message' => 'Account ID: id value of incorrect type: 001M0000008tWTFIA3', 82 | 'statusCode' => 'MALFORMED_ID' 83 | )); 84 | 85 | $saveResult = $this->createMock(new Result\SaveResult(), array( 86 | 'errors' => array($error), 87 | 'success' => false 88 | )); 89 | 90 | $result = new \stdClass(); 91 | $result->result = array($saveResult); 92 | 93 | $soapClient = $this->getSoapClient(array('update')); 94 | $soapClient 95 | ->expects($this->once()) 96 | ->method('update') 97 | ->will($this->returnValue($result)); 98 | 99 | $this->setExpectedException('\Phpforce\SoapClient\Exception\SaveException'); 100 | $this->getClient($soapClient)->update(array( 101 | (object) array( 102 | 'Id' => 'invalid-id', 103 | 'Name' => 'Some name' 104 | ) 105 | ), 'Account'); 106 | } 107 | 108 | public function testMergeMustThrowException() 109 | { 110 | $soapClient= $this->getSoapClient(array('merge')); 111 | $this->setExpectedException('\InvalidArgumentException', 'must be an instance of'); 112 | $this->getClient($soapClient)->merge(array(new \stdClass), 'Account'); 113 | } 114 | 115 | public function testMerge() 116 | { 117 | $soapClient= $this->getSoapClient(array('merge')); 118 | 119 | $mergeRequest = new Request\MergeRequest(); 120 | $masterRecord = new \stdClass(); 121 | $masterRecord->Id = '001M0000007UvSjIAK'; 122 | $masterRecord->Name = 'This will be the new name'; 123 | $mergeRequest->masterRecord = $masterRecord; 124 | $mergeRequest->recordToMergeIds = array('001M0000008uw8JIAQ'); 125 | 126 | $mergeResult = $this->createMock(new Result\MergeResult(), array( 127 | 'id' => '001M0000007UvSjIAK', 128 | 'mergedRecordIds' => array('001M0000008uw8JIAQ'), 129 | 'success' => true 130 | )); 131 | 132 | $result = new \stdClass(); 133 | $result->result = array($mergeResult); 134 | 135 | $soapClient 136 | ->expects($this->any()) 137 | ->method('merge') 138 | ->will($this->returnValue($result)); 139 | 140 | $this->getClient($soapClient)->merge(array($mergeRequest), 'Account'); 141 | } 142 | 143 | public function testWithEventDispatcher() 144 | { 145 | $response = new \stdClass(); 146 | 147 | $error = $this->createMock(new Result\Error(), array( 148 | 'fields' => array('Id'), 149 | 'message' => 'Account ID: id value of incorrect type: 001M0000008tWTFIA3', 150 | 'statusCode' => 'MALFORMED_ID' 151 | )); 152 | 153 | $saveResult = $this->createMock(new Result\SaveResult(), array( 154 | 'errors' => array($error), 155 | 'success' => false 156 | )); 157 | 158 | $response->result = array($saveResult); 159 | 160 | $soapClient = $this->getSoapClient(array('create')); 161 | $soapClient 162 | ->expects($this->once()) 163 | ->method('create') 164 | ->will($this->returnValue($response)); 165 | 166 | $client = $this->getClient($soapClient); 167 | 168 | $dispatcher = $this 169 | ->getMockBuilder('\Symfony\Component\EventDispatcher\EventDispatcher') 170 | ->disableOriginalConstructor() 171 | ->getMock(); 172 | 173 | $c = new \stdClass(); 174 | $c->AccountId = '123'; 175 | 176 | $params = array( 177 | 'sObjects' => array(new \SoapVar($c, SOAP_ENC_OBJECT, 'Contact', Client::SOAP_NAMESPACE)) 178 | ); 179 | 180 | // $dispatcher 181 | // ->expects($this->at(0)) 182 | // ->method('dispatch') 183 | // ->with('php_force.soap_client.request', new Event\RequestEvent('create', $params)); 184 | 185 | $dispatcher 186 | ->expects($this->at(1)) 187 | ->method('dispatch') 188 | ->with('phpforce.soap_client.response'); 189 | 190 | // $dispatcher 191 | // ->expects($this->at(2)) 192 | // ->method('dispatch') 193 | // ->with('php_force.soap_client.error'); 194 | 195 | $this->setExpectedException('\Phpforce\SoapClient\Exception\SaveException'); 196 | 197 | $client->setEventDispatcher($dispatcher); 198 | $client->create(array($c), 'Contact'); 199 | } 200 | 201 | protected function getClient(\SoapClient $soapClient) 202 | { 203 | return new Client($soapClient, 'username', 'password', 'token'); 204 | } 205 | 206 | protected function getSoapClient($methods) 207 | { 208 | $soapClient = $this->getMockBuilder('Phpforce\SoapClient\Soap\SoapClient') 209 | ->setMethods(array_merge($methods, array('login'))) 210 | ->setConstructorArgs(array(__DIR__.'/Fixtures/sandbox.enterprise.wsdl.xml')) 211 | ->getMock(); 212 | 213 | $result = $this->getResultMock(new LoginResult(), array( 214 | 'sessionId' => '123', 215 | 'serverUrl' => 'http://dinges' 216 | )); 217 | 218 | $soapClient 219 | ->expects($this->any()) 220 | ->method('login') 221 | ->will($this->returnValue($result)); 222 | 223 | return $soapClient; 224 | } 225 | 226 | /** 227 | * Set a protected property on an object for testing purposes 228 | * 229 | * @param object $object Object 230 | * @param string $property Property name 231 | * @param mixed $value Value 232 | */ 233 | protected function setProperty($object, $property, $value) 234 | { 235 | $reflClass = new ReflectionClass($object); 236 | $reflProperty = $reflClass->getProperty($property); 237 | $reflProperty->setAccessible(true); 238 | $reflProperty->setValue($object, $value); 239 | 240 | return $this; 241 | } 242 | 243 | protected function createMock($object, array $values = array()) 244 | { 245 | foreach ($values as $key => $value) { 246 | $this->setProperty($object, $key, $value); 247 | } 248 | 249 | return $object; 250 | } 251 | 252 | protected function getResultMock($object, array $values = array()) 253 | { 254 | $mock = $this->createMock($object, $values); 255 | 256 | $result = new \stdClass(); 257 | $result->result = $mock; 258 | 259 | return $result; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('Phpforce\\SoapClient\\Test', __DIR__); --------------------------------------------------------------------------------