Git Product home page Git Product logo

soap-client's People

Contributors

christiaan avatar ddeboer avatar ikrasnykh avatar j-d avatar nyholm avatar shaunhardy avatar simonharris avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

soap-client's Issues

SOAP namespace should be a variable

Hey, for the connections that we're making to salesforce, we need to use a different namespace. Currently, the SOAP_NAMESPACE constant holds the namespace value, which is hardcoded to 'urn:enterprise.soap.sforce.com'.

I would propose that the builder get an option to modify the namespace during composition time.

What do you think?

problem in install the pagckage

Hi when i try to install the package i get this error:
Problem 1 - Installation request for phpforce/soap-client ^0.1.0 -> satisfiable by phpforce/soap-client[0.1.0]. - phpforce/soap-client 0.1.0 requires phpforce/common dev-master -> satisfiable by phpforce/common[dev-master] but these conflict with your requirements or minimum-stability.

what should i do?

Looking for maintainers

As I've recently switched jobs, and I no longer use Salesforce, I'm looking for maintainers for this project. Are you interested in maintaining this project? Please leave a comment or send me a message.

Deprecation notices with PHP 8.1

Using the package with PHP 8.1 emits deprecation notices (this and few other ones):

Deprecated function: Return type of Phpforce\SoapClient\Result\RecordIterator::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in include() (line 15 of /var/www/html/vendor/phpforce/soap-client/src/Phpforce/SoapClient/Result/RecordIterator.php)

Throwing a SaveException does not allow access to success results

The method Client::checkResults checks the array of results returned by the Salesforce APIs and throws a SaveException when at least one of the results is not a Success. However in a catch block one can only retrieve from the exception the results that were indeed a failure but there is no way to handle those that weren't. Wouldn't it better if the SaveException had a way to get all the results of a call?

I'm not sure what's the best way here, I can only think of adding all the results in the exception no matter what (the error message of the exception is still only bound to those results that has an error).

HHVM Compatibility in Error?

travis.yml indicates this is compatible with HHVM. I'm not seeing that. I'm using HHVM 3.7. PHP5-FPM works just fine. With HHVM, LoginResult just comes back with a bunch of NULL values in the array of protected values, leading me ultimately to an error from Salesforce of "Destination URL not reset. The URL returned from login must be set in the SforceService" from a subsequent request.

I'm going to mess with this a bit more. Would be nice to solve. Wondering if this is fundamentally the culprit: http://docs.hhvm.com/manual/en/soapclient.soapclient.php .

CACHE_MEMORY|CACHE_BOTH affected by PHP bug #71931 - incompatible with PHP7

Totally not the fault of phpforce, but using the soap option CACHE_MEMORY or CACHE_BOTH currently causes segfaults on the latest versions of PHP (e.g. 7.0.11)

The upstream bug is still open - it seems to require usage from a CLI context to be triggered, but is very repeatable.

I wonder if you'd consider leaving this configuration option up to the php.ini file's soap.wsdl_cache option rather than hard-coding it in the constructor. That would allow this library to be compatible with PHP7 immediately, rather than having to wait for the upstream bug to close (and, remember, it'll never be compatible with 7.0.0 - 7.0.11 as is...)

Partner WSDL

Hi,
How do you make it work with the Partner WSDL ?

Here's a methodology for compatibility with Partner WSDL

If you are trying to use this bundle for a PARTNER type wsdl, as I ultimately did, here's a way to go about it. It would be great if this bundle did this natively.

I went this route because a.) I didn't like the bloat from BeSimple and Zendframework with another client bundle, and b.) I needed Partner WSDL support, not Enterprise.

First, extend the Phpforce client in you own bundle. I chose to keep my phpforce extended and supporting classes in a subdirectory called Phpforce. Notable aspects of this extension are an override of the SOAP_NAMESPACE constant and an override of all methods that use this constant. But also of crucial importance is the alteration to the createSoapVars function, wherein the "any" field is properly processed on an SObject.

One other note is that you'll see we're not setting the username/password/apiKey in the constructor. If you're using the Partner wsdl, flexibility in credentials seems to me to be one of the main reasons. Here I'll be using an entity called SalesforceCredentials to manage those login settings. Achieve the same thing however you want.

<?php

namespace Acme\SalesforceBundle\Phpforce;

use Acme\SalesforceBundle\Entity\SalesforceCredentials;

use Phpforce\SoapClient\Soap\SoapClient;
use Phpforce\SoapClient\Client as BaseClient;

class Client extends BaseClient
{

    const SOAP_NAMESPACE = 'urn:partner.soap.sforce.com';

    public function __construct(SoapClient $soapClient)
    {
        parent::__construct($soapClient, null, null, null);
    }

    public function setCredentials(SalesforceCredentials $salesforce) {
        $this->username = $salesforce->getUsername();
        $this->password = $salesforce->getPassword();
        $this->token = $salesforce->getApiKey();
    }

    public function login($username, $password, $token)
    {
        try {
            $result = $this->soapClient->login(array(
                'username'  => $username,
                'password'  => $password.$token
            )); 
        } catch (\SoapFault $soapFault) {
            return $soapFault;
        }

        $this->setLoginResult($result->result);
        return $result->result;
    }

    public function getLoginResult()
    {
        if (null === $this->loginResult) {
            $loginResult = $this->login($this->username, $this->password, $this->token);
            if (get_class($loginResult) == 'SoapFault') {
               return $loginResult;
            }
        }

        return $this->loginResult;
    }

    public function merge(array $mergeRequests, $type)
    {
        foreach ($mergeRequests as $mergeRequest) {
            if (!($mergeRequest instanceof Request\MergeRequest)) {
                throw new \InvalidArgumentException(
                    'Each merge request must be an instance of MergeRequest'
                );
            }

            if (!$mergeRequest->masterRecord || !is_object($mergeRequest->masterRecord)) {
                throw new \InvalidArgumentException('masterRecord must be an object');
            }

            if (!$mergeRequest->masterRecord->Id) {
                throw new \InvalidArgumentException('Id for masterRecord must be set');
            }

            if (!is_array($mergeRequest->recordToMergeIds)) {
                throw new \InvalidArgumentException('recordToMergeIds must be an array');
            }

            $mergeRequest->masterRecord = new \SoapVar(
                $this->createSObject($mergeRequest->masterRecord, $type),
                SOAP_ENC_OBJECT,
                $type,
                self::SOAP_NAMESPACE
            );
        }

        return $this->call(
            'merge',
            array('request' => $mergeRequests)
        );
    }

    protected function createSoapVars(array $objects, $type)
    {
        $soapVars = array();

        foreach ($objects as $object) {

            $sObject = $this->createSObject($object, $type);

            // REALLY IMPORTANT ALTERATION. (PARTNER WSDL COMPATIBILITY).
            if (isset($sObject->any)) {                
                foreach ($sObject->any as $key => $value) {
                    $sObject->{$key} = $value;
                }
                unset($sObject->any);
            }

            $xml = '';
            if (isset($sObject->fieldsToNull)) {
                foreach ($sObject->fieldsToNull as $fieldToNull) {
                    $xml .= '<fieldsToNull>' . $fieldToNull . '</fieldsToNull>';
                }
                $fieldsToNullVar = new \SoapVar(new \SoapVar($xml, XSD_ANYXML), SOAP_ENC_ARRAY);
                $sObject->fieldsToNull = $fieldsToNullVar;
            }

            $soapVar = new \SoapVar($sObject, SOAP_ENC_OBJECT, $type, self::SOAP_NAMESPACE);
            $soapVars[] = $soapVar;
        }

        return $soapVars;
    }

    protected function setSoapHeaders(array $headers)
    {
        $soapHeaderObjects = array();
        foreach ($headers as $key => $value) {
            $soapHeaderObjects[] = new \SoapHeader(self::SOAP_NAMESPACE, $key, $value);
        }

        $this->soapClient->__setSoapHeaders($soapHeaderObjects);
    }

    protected function setSessionId($sessionId)
    {
        $this->sessionHeader = new \SoapHeader(
            self::SOAP_NAMESPACE,
            'SessionHeader',
            array(
                'sessionId' => $sessionId
            )
        );
    }

}

The Partner API is going to respond to queries with an "all" key full of namespaced xml, which needs to be converted. Processing those to a proper SObject requires borrowing a class from the Salesforce PHP Toolkit.

<?php

/*
 * This class taken from the Official PHP Salesforce Toolkit.
 * Added preceding slash to stdClass for compatibility
 * https://github.com/developerforce/Force.com-Toolkit-for-PHP/blob/master/soapclient/SforceBaseClient.php
 */

namespace Acme\SalesforceBundle\Phpforce;

class SObject {
    public $type;
    public $fields;
//  public $sobject;

    public function __construct($response=NULL) {
        if (!isset($response) && !$response) {
            return;
        }

        foreach ($response as $responseKey => $responseValue) {
            if (in_array($responseKey, array('Id', 'type', 'any'))) {
                continue;
            }
            $this->$responseKey = $responseValue;
        }

        if (isset($response->Id)) {
            $this->Id = is_array($response->Id) ? $response->Id[0] : $response->Id;
        }

        if (isset($response->type)) {
            $this->type = $response->type;
        }

        if (isset($response->any)) {
            try {
                //$this->fields = $this->convertFields($response->any);
                // If ANY is an object, instantiate another SObject
                if ($response->any instanceof stdClass) {
                    if ($this->isSObject($response->any)) {
                        $anArray = array();
                        $sobject = new SObject($response->any);
                        $anArray[] = $sobject;
                        $this->sobjects = $anArray;
                    } else {
                        // this is for parent to child relationships
                        $this->queryResult = new QueryResult($response->any);
                    }

                } else {
                    // If ANY is an array
                    if (is_array($response->any)) {
                        // Loop through each and perform some action.
                        $anArray = array();

                        // Modify the foreach to have $key=>$value
                        // Added on 28th April 2008
                        foreach ($response->any as $key=>$item) {
                            if ($item instanceof stdClass) {
                                if ($this->isSObject($item)) {
                                    $sobject = new SObject($item);
                                    // make an associative array instead of a numeric one
                                    $anArray[$key] = $sobject;
                                } else {
                                    // this is for parent to child relationships
                                    //$this->queryResult = new QueryResult($item);
                                    if (!isset($this->queryResult)) {
                                        $this->queryResult = array();
                                    }
                                    array_push($this->queryResult, new QueryResult($item));
                                }
                            } else {
                                //$this->fields = $this->convertFields($item);

                                if (strpos($item, 'sf:') === false) {
                                    $currentXmlValue = sprintf('<sf:%s>%s</sf:%s>', $key, $item, $key);
                                } else {
                                    $currentXmlValue = $item;
                                }

                                if (!isset($fieldsToConvert)) {
                                    $fieldsToConvert = $currentXmlValue;
                                } else {
                                    $fieldsToConvert .= $currentXmlValue;
                                }
                            }
                        }

                        if (isset($fieldsToConvert)) {
                            // If this line is commented, then the fields becomes a stdclass object and does not have the name variable
                            // In this case the foreach loop on line 252 runs successfuly
                            $this->fields = $this->convertFields($fieldsToConvert);
                        }

                        if (sizeof($anArray) > 0) {
                            // To add more variables to the the top level sobject
                            foreach ($anArray as $key=>$children_sobject) {
                                $this->fields->$key = $children_sobject;
                            }
                            //array_push($this->fields, $anArray);
                            // Uncommented on 28th April since all the sobjects have now been moved to the fields
                            //$this->sobjects = $anArray;
                        }

                        /*
                           $this->fields = $this->convertFields($response->any[0]);
                           if (isset($response->any[1]->records)) {
                           $anArray = array();
                           if ($response->any[1]->size == 1) {
                           $records = array (
                           $response->any[1]->records
                           );
                           } else {
                           $records = $response->any[1]->records;
                           }
                           foreach ($records as $record) {
                           $sobject = new SObject($record);
                           array_push($anArray, $sobject);
                           }
                           $this->sobjects = $anArray;
                           } else {
                           $anArray = array();
                           $sobject = new SObject($response->any[1]);
                           array_push($anArray, $sobject);
                           $this->sobjects = $anArray;
                           }
                         */
                    } else {
                        $this->fields = $this->convertFields($response->any);
                    }
                }
            } catch (Exception $e) {
                var_dump('exception: ', $e);
            }
        }
    }

    function __get($name) { return (isset($this->fields->$name))? $this->fields->$name : false; }
    function __isset($name) { return isset($this->fields->$name); }

    /**
     * Parse the "any" string from an sObject.  First strip out the sf: and then
     * enclose string with <Object></Object>.  Load the string using
     * simplexml_load_string and return an array that can be traversed.
     */
    function convertFields($any) {
        $str = preg_replace('{sf:}', '', $any);

        $array = $this->xml2array('<Object xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$str.'</Object>', 2);

        $xml = new \stdClass();
        if (!count($array['Object']))
            return $xml;

        foreach ($array['Object'] as $k=>$v) {
            $xml->$k = $v;
        }

        //$new_string = '<Object xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$new_string.'</Object>';
        //$new_string = $new_string;
        //$xml = simplexml_load_string($new_string);
        return $xml;
    }

    /**
     * 
     * @param string $contents
     * @return array
     */
    function xml2array($contents, $get_attributes=1) {
        if(!$contents) return array();

        if(!function_exists('xml_parser_create')) {
            //print "'xml_parser_create()' function not found!";
            return array('not found');
        }
        //Get the XML parser of PHP - PHP must have this module for the parser to work
        $parser = xml_parser_create();
        xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
        xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 );
        xml_parse_into_struct( $parser, $contents, $xml_values );
        xml_parser_free( $parser );

        if(!$xml_values) return;//Hmm...

        //Initializations
        $xml_array = array();
        $parents = array();
        $opened_tags = array();
        $arr = array();

        $current = &$xml_array;

        //Go through the tags.
        foreach($xml_values as $data) {
            unset($attributes,$value);//Remove existing values, or there will be trouble

            //This command will extract these variables into the foreach scope
            // tag(string), type(string), level(int), attributes(array).
            extract($data);//We could use the array by itself, but this cooler.

            $result = '';
            if ($get_attributes) {
                switch ($get_attributes) {
                    case 1:
                        $result = array();
                        if(isset($value)) $result['value'] = $value;

                        //Set the attributes too.
                        if(isset($attributes)) {
                            foreach($attributes as $attr => $val) {
                                if($get_attributes == 1) $result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
                                /**  :TODO: should we change the key name to '_attr'? Someone may use the tagname 'attr'. Same goes for 'value' too */
                            }
                        }
                        break;

                    case 2:
                        $result = array();
                        if (isset($value)) {
                            $result = $value;
                        }

                        //Check for nil and ignore other attributes.
                        if (isset($attributes) && isset($attributes['xsi:nil']) && !strcasecmp($attributes['xsi:nil'], 'true')) {
                            $result = null;
                        }
                        break;
                }
            } elseif (isset($value)) {
                $result = $value;
            }

            //See tag status and do the needed.
            if($type == "open") {//The starting of the tag '<tag>'
                $parent[$level-1] = &$current;

                if(!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag
                    $current[$tag] = $result;
                    $current = &$current[$tag];

                } else { //There was another element with the same tag name
                    if(isset($current[$tag][0])) {
                        array_push($current[$tag], $result);
                    } else {
                        $current[$tag] = array($current[$tag],$result);
                    }
                    $last = count($current[$tag]) - 1;
                    $current = &$current[$tag][$last];
                }

            } elseif($type == "complete") { //Tags that ends in 1 line '<tag />'
                //See if the key is already taken.
                if(!isset($current[$tag])) { //New Key
                    $current[$tag] = $result;

                } else { //If taken, put all things inside a list(array)
                    if((is_array($current[$tag]) and $get_attributes == 0)//If it is already an array...
                            or (isset($current[$tag][0]) and is_array($current[$tag][0]) and ($get_attributes == 1 || $get_attributes == 2))) {
                        array_push($current[$tag],$result); // ...push the new element into that array.
                    } else { //If it is not an array...
                        $current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
                    }
                }

            } elseif($type == 'close') { //End of tag '</tag>'
                $current = &$parent[$level-1];
            }
        }

        return($xml_array);
    }

    /*
     * If the stdClass has a done, we know it is a QueryResult
     */
    function isQueryResult($param) {
        return isset($param->done);
    }

    /*
     * If the stdClass has a type, we know it is an SObject
     */
    function isSObject($param) {
        return isset($param->type);
    }
}

Next turn this into a service in your service.xml. I did elect to store the wsdl locally as Acme/SalesforceBundle/Phpforce/partner.wsdl.xml, reflected here.

    acme.salesforce_client: 
        class: Acme\SalesforceBundle\Phpforce\Client
        arguments: [ @acme.salesforce.client.soap.client ]

    acme.salesforce.client.soap.client:
        factory_service: acme.salesforce.soap.client.factory
        factory_method: factory
        arguments: [ "%kernel.root_dir%/../src/Acme/SalesforceBundle/Phpforce/partner.wsdl.xml" ]
        class: Phpforce\SoapClient\Soap\SoapClient

    acme.salesforce.soap.client.factory:
        class: Phpforce\SoapClient\Soap\SoapClientFactory

All right, so now a few important things in a controller. Login (successfully), do a query, and do an update.

//... Some containeraware controller method
//... or pass @acme.salesforce_client to another service as an argument

use Acme\SalesforceBundle\Phpforce\SObject;

$this->client = $this->container->get('acme.salesforce_client'); 
$this->client->setCredentials($salesforceCredentials);

$loginResult = $this->client->getLoginResult();        

if (get_class($loginResult) == 'SoapFault') {
    // $error_string = $loginResult->getMessage();
    // add $error_string to FlashBag or whatever.
}
else {

    // First will query for a Lead object with xyz email address.
    // But we'll also ask for all fields, including custom, that we got when we did a describe call on the Lead.

    $email = '[email protected]';

    $query = 'select Id, isConverted, isDeleted';

    foreach($filtered_field_array_taken_from_describe['fields'] as $field) {
        $query .= ', ' . $field['name'];
    }

    $query .= ' from Lead where Email = \'' . $email . '\' limit 5';

    $result = $this->client->query($query);

    foreach ($result as $record) {

        $sObject = new SObject($record);

        // Now do what you will with fields, like $sObject->fields->IsConverted;
        // Or what you will with $record->getId(); (which might be an array)

    }

    // Now an update call
    // Here updating a lead, with fake $postData from wherever.
    // the $Id presumably would come from the query above.

    $lead = new \stdClass();
    $lead->Id = $Id;
    $lead->type = 'Lead';
    $lead->any = new \stdClass();

    $postData = array('FirstName' => 'Joe', 'LastName' => 'Smith');

    foreach ($postData as $key => $value) {
        $lead->any->$key = $value;
    }

     $response = $this->client->update(array($lead), 'sObject');

}

//...

Thanks for the great Salesforce bundles, David.

Id not specified in an update call

Applying the fix in #9 exposed a bug in the PHP SOAP library where SoapClient::__getTypes() does not support extensions in the XMLSchema when used in WSDL mode.

https://bugs.php.net/bug.php?id=45404

Since every object in the WSDL extends sObject, which is where the Id is declared, any record update will fail with "Id not specified in an update call".

Creating Accounts / Leads

Hi

Does anyone have an example of using the create() SOAP call?

I'm trying to create a new Account/Lead, but unsure what I need to send as part of the call

Thanks

Documentation

A quick getting started guide showing how these classes relate to the official toolkit would be really helpful.

convertLead method should call checkResult too

convertLead returns a LeadConvertResult array but it seems like that array is not checked using the checkResult method like other similar calls in the library? Is there any special reason to not doing so?

Metadata or Tooling compatibility?

This might be an easy question, but I've not been able to find a definitive answer in the code (yet)

Does this support the Metadata or Tooling API's as well as the Enterprise? We need some functionality available in Metadata/Tooling's SOAP that isn't available for Enterprise (updating picklists through the API)

Thanks,

-DF

Dependency management

See: phpforce/common#2

Can this module get a release made which includes a stable tagged version of the common library? Relying on dev-master is making life difficult ๐Ÿ˜„

Element {}item invalid at this location

Hello,

Can you help me please?
I have problem with connection to salesforce, when i use query i have this error Element {}item invalid at this location

vendor\phpforce\soap-client\src\Phpforce\SoapClient\Client.php (line 560)
In method
$requestEvent = new Event\RequestEvent($method, $params); $this->dispatch(Events::REQUEST, $requestEvent); try { $result = $this->soapClient->$method($params); } catch (\SoapFault $soapFault) { $faultEvent = new Event\FaultEvent($soapFault, $requestEvent); $this->dispatch(Events::FAULT, $faultEvent); throw $soapFault;

my code

class Salesforce
{

const USERNAME_SF = "username";
const TOKEN_SF = "token";
const PASSWORD_SF = "pass";
private $client;

private  function login()
{
	if(!is_null($this->client)) return;
	// check if l'utilisateur existe mot de pass dynamique
$auth= new \Phpforce\SoapClient\ClientBuilder(
	  __DIR__ . '/../../config/wsdl/PartnerWSDL.xml',
	  self::USERNAME_SF,
	  self::PASSWORD_SF,
	  self::TOKEN_SF
	);

$this->client = $auth->build();

}

public function get_information()
{
	$this->login();
	$results = $this->client->query('select Id from Contact');
}
}

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.