Asterisk, and other worldly endeavours.

A blog by Leif Madsen

Consuming SOAP complexType webservice with PHP


I recently had a client request that I communicate with a webservice via SOAP in order to do some credit card authorization for an Asterisk project they were implementing. After a couple of days of reading several posts I found via Google (which funny enough weren’t exactly what I was looking for, but gave me JUST enough information to finally start putting it all together), I had something that worked.

In order to potentially save someone the hassle of having to figure out how to consume a complexType in SOAP via PHP (and not using NuSoap as many of the posts pointed out), I’m writing this down. As with many of my posts, there is probably just enough information here that will be useful for me in the future should I need to do this again. If this helps someone else out, then great!

Note: I am not a programmer. What I’m showing may be entirely the WRONG approach to solving this issue, but it solves it for me. Feel free to provide better examples by commenting on this article. In addition, there is the potential I may be using the wrong terminology. Please feel free to correct me, and I will then update the article to reflect anything deemed to be incorrect.

First, you need to install the php-soap module for your PHP installation. This could require you to recompile PHP with SOAP support, or if you’re using a package based distro (like me), you may just need to do something like:

# yum install php-soap

In order to give you just enough of a reference point to figure out what I’m trying to do here, the following snippet of XML code is from the webservice I’m trying to consume. It is the complextype which I need to perform request against, and defines how the object should be passed. I’ll then show the result. This is *not* the entire WSDL.

First we’re looking at the element named ‘Purchase’. This element contains a complexType object named oPurchaseRequest and receives a request of the type PurchaseRequestType.

<s:element name="Purchase">
 <s:complexType>
   <s:sequence>
     <s:element minOccurs="0" maxOccurs="1" name="oPurchaseRequest" type="tns:PurchaseRequestType"/>
   </s:sequence>
 </s:complexType>
</s:element>

The structure of the PurchaseRequestType is as follows. This is how we need to structure our complexType.

<s:complexType name="PurchaseRequestType">
 <s:sequence>
   <s:element minOccurs="0" maxOccurs="1" name="MembershipNumber" type="s:string"/>
   <s:element minOccurs="0" maxOccurs="1" name="SessionID" type="s:string"/>
   <s:element minOccurs="0" maxOccurs="1" name="Password" type="s:string"/>
   <s:element minOccurs="0" maxOccurs="1" name="Gender" type="s:string"/>
   <s:element minOccurs="1" maxOccurs="1" name="RatePlanID" type="s:int"/>
   <s:element minOccurs="0" maxOccurs="1" name="CCNumber" type="s:string"/>
   <s:element minOccurs="1" maxOccurs="1" name="CCExpireMonth" type="s:int"/>
   <s:element minOccurs="1" maxOccurs="1" name="CCExpireYear" type="s:int"/>
   <s:element minOccurs="1" maxOccurs="1" name="CCCode" type="s:int"/>
   <s:element minOccurs="1" maxOccurs="1" name="Zip" type="s:int"/>
  </s:sequence>
 </s:complexType>

The magic really happens in the following code. It is how I built the object in order to be consumed by the webservice. It works by first creating a new standard class, and then creating another standard class inside the oPurchaseRequest object (which is the name of the complexType that the Purchase element is expecting). Then you build the oPurchaseRequest by adding the elements outlined by the PurchaseRequestType complexType, and assigning values to them.

$search_query = new StdClass();
$search_query->oPurchaseRequest = new StdClass();
$search_query->oPurchaseRequest->MembershipNumber = $MembershipNumber;
$search_query->oPurchaseRequest->SessionID = $SessionID;
$search_query->oPurchaseRequest->Password = $Password;
$search_query->oPurchaseRequest->Gender = $Gender;
$search_query->oPurchaseRequest->RatePlanID = $RatePlanID;
$search_query->oPurchaseRequest->CCNumber = $CCNumber;
$search_query->oPurchaseRequest->CCExpireMonth = $CCExpireMonth;
$search_query->oPurchaseRequest->CCExpireYear = $CCExpireYear;
$search_query->oPurchaseRequest->CCCode = $CCCode;
$search_query->oPurchaseRequest->Zip = $Zip;

The entire snippet of code is available below.

// We can take in our arguments from the console if we're using PHP CLI
$MembershipNumber = $argv[1];
$SessionID = $argv[2];
$Password = $argv[3];
$Gender = $argv[4];
$RatePlanID = $argv[5];
$CCNumber = $argv[6];
$CCExpireMonth = $argv[7];
$CCExpireYear = $argv[8];
$CCCode = $argv[9];
$Zip = $argv[10];

// Create the object we'll pass back over the SOAP interface. This is the MAGIC!
$search_query = new StdClass();
$search_query->oPurchaseRequest = new StdClass();
$search_query->oPurchaseRequest->MembershipNumber = $MembershipNumber;
$search_query->oPurchaseRequest->SessionID = $SessionID;
$search_query->oPurchaseRequest->Password = $Password;
$search_query->oPurchaseRequest->Gender = $Gender;
$search_query->oPurchaseRequest->RatePlanID = $RatePlanID;
$search_query->oPurchaseRequest->CCNumber = $CCNumber;
$search_query->oPurchaseRequest->CCExpireMonth = $CCExpireMonth;
$search_query->oPurchaseRequest->CCExpireYear = $CCExpireYear;
$search_query->oPurchaseRequest->CCCode = $CCCode;
$search_query->oPurchaseRequest->Zip = $Zip;

// setup some SOAP options
echo "Setting up SOAP options\n";
$soap_options = array(
        'trace'       => 1,     // traces let us look at the actual SOAP messages later
        'exceptions'  => 1 );


// configure our WSDL location
echo "Configuring WSDL\n";
$wsdl = "https://locationofservices.tld/asterisk.asmx?WSDL";


// Make sure the PHP-Soap module is installed
echo "Checking SoapClient exists\n";
if (!class_exists('SoapClient'))
{
        die ("You haven't installed the PHP-Soap module.");
}

// we use the WSDL file to create a connection to the web service
echo "Creating webservice connection to $wsdl\n";
$webservice = new SoapClient($wsdl,$soap_options);

echo "Attempting Purchase\n";
try {
        $result = $webservice->Purchase($search_query);

        // save our results to some variables
        $TransactionID = $result->PurchaseResult->TransactionID;
        $ResponseCode = $result->PurchaseResult->ResponseCode;
        $ResponseDetail = $result->PurchaseResult->ResponseDetail;
        $AddMinutes = $result->PurchaseResult->AddMinutes;

        // perform some logic, output the data to Asterisk, or whatever you want to do with it.

} catch (SOAPFault $f) {
        // handle the fault here
}

echo "Script complete\n\n";

You’ll notice that our results are passed back in the ‘try’ statement. We assign the values passed back to some variables we could use. The layout of the result is similar to the request. It is laid out as follows in the WSDL.

<s:element name="PurchaseResponse">
 <s:complexType>
  <s:sequence>
   <s:element minOccurs="0" maxOccurs="1" name="PurchaseResult" type="tns:PurchaseResponseType"/>
  </s:sequence>
 </s:complexType>
</s:element>

<s:complexType name="PurchaseResponseType">
 <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" name="TransactionID" type="s:string"/>
  <s:element minOccurs="1" maxOccurs="1" name="ResponseCode" type="tns:ResponseCodes"/>
  <s:element minOccurs="0" maxOccurs="1" name="ResponseDetail" type="s:string"/>
  <s:element minOccurs="1" maxOccurs="1" name="AddMinutes" type="s:int"/>
 </s:sequence>
</s:complexType>

Hope that helps!

Written by Leif Madsen

2009/08/04 at 12:57 pm

Posted in Programming

Tagged with , , , , ,

37 Responses

Subscribe to comments with RSS.

  1. Hey, Leif, it appears to be that we got same type of project (mine is money transfer via IVR made in Asterisk with authorization via SOAP in PHP), so you saved me a lot of time researching. Thanks a lot.

    MiguelColombia

    2009/08/05 at 6:35 pm

    • Glad I could help out! Let me know if you have any issues.

      Leif.

      Leif Madsen

      2009/08/05 at 7:54 pm

  2. Hello Lief,

    That snippet was very helpful to me as well although I feel like you do:

    if I write a WSDL that imports schema definitions in, shouldn’t I be able to use those definitions in the schemas to declare the needed (top level complexType) variables rather than just creating the schema definitions all over again in PHP?

    I’m still searching if this is possible with PHP SOAP extension.

    Have you learned anything new?

    AJ

    2009/08/28 at 3:37 pm

    • I haven’t learned anything new as I haven’t had to work with the AGI since I wrote it. If you learn anything new about a better way to do this, please do let me know!

      Leif Madsen

      2009/08/29 at 9:46 am

      • Thanks, sure thing. I do know that the schemas I imported via the WSDL don’t show up as any kind of PHP classes, objects or type definitions as you’d expect.

        I guess I’ll have to do validation of the schemas with PHP DOM or some other PHP XML library.

        I’ll post it when I’m done.

        AJ

        2009/08/31 at 9:47 am

  3. Thanks for the web page, it was exactly what I was looking for – how could I create a variable of the right format to pass as a paramter type that was defined in a WSDL file to access a SOAP service.

    Can’t prove that it works for me yet as I still have another hurdle to jump – one of the data type names has a – in it, and PHP doesn’t like constructs like $param->name-part it thinks it is a subtraction part way through an identifier.

    Now to scratch my head as to how I can get a value into a name-part identifier 😦

    The equivalent here would be if you had to do something like

    $search_query->oPurchaseRequest->Zip-Code = $zip

    in order to obey your WSDL specification. any ideas ?

    dp.

    David Pascoe

    2009/09/10 at 12:55 am

  4. In case someone else comes here wondering how to solve this problem, the syntax you need is

    $search_query->oPurchaseRequest->{‘Zip-Code’} = $zip

    and then you will be fine.

    davidp.

    David Pascoe

    2009/09/10 at 1:34 am

    • Wow, thanks David! I had no idea how to do that 🙂 I’m certainly not a PHP programmer by trade, and just happened to work out what I needed from reading enough blog posts. I’m sure that will help out a bunch of people. Thanks for following up!

      Leif Madsen

      2009/10/20 at 6:58 am

  5. Hi :

    Which PHP SOAP version you where using?

    Thanks
    Nil

    Nil

    2009/10/19 at 1:03 pm

    • It was mostly likely whatever the latest version was in CentOS 5.3, which I think is like PHP 5.3 or something like that?

      Leif Madsen

      2009/10/19 at 3:10 pm

  6. Thanks Leif. Your posting helped me a lot. You explained things pretty well .Maybe you can post a link to your blog on PHP.net too. By the way, the pics are funny 🙂

    Nil

    2009/10/19 at 4:51 pm

    • Thanks Nil! I will certainly try and find the time to post it to PHP.net in the appropriate place. Thanks again!

      Leif Madsen

      2009/10/20 at 6:59 am

  7. Hi,
    Just want to confirm.. Is it possible to create the type locally and then send that over to the service?

    Thanks,
    James

    Web Developer

    2009/10/20 at 10:08 am

  8. I had to do something similar for a client but the web service was implemented by a third party and it was making me rip my hair. Finally I understood the relation between Purchase, oPurchaseRequest and its elements and the webservice call.
    It worked fine using an array instead of StdClass objects but I don’t think that was the problem.

    Thx for this post Leif.

    David Medina

    2010/02/19 at 2:47 pm

  9. I came across this post and comments in my research which provided many useful clues to my implementation.

    Here are some notes and things I learned while implementing a PHP app that consumes a WCF Service:

    WCF Services Expose Endpoints. It can be one or it can be many. As a PHP developer, you cannot control

    which endpoints the service exposes.

    WCF service MUST expose a basicHttpBinding endpoint for a PHP app to consume it. This is the only type of

    endpoint that will work with PHP SOAP Client.

    In PHP, use WSDL mode for SoapClient.
    wsdl URL is same even if WCF service exposes multiple end points

    If the WCF Service exposes multiple endpoints:
    – set the ‘location’ item of the options array of the SoapClient constructor to point to the basic

    endpoint.

    Sample Code:

    // return WSDL URL – could grab data from config
    // file or DB. For example, returns url string
    function GetWsdl()
    {
    return ‘https://www.myserver.com/services/datalookup/Service.svc?wsdl’;
    }

    // return URL – could grab data from config
    // file or DB. For example, returns url string
    function GetUrl()
    {
    // use the basicHttpBinding endpoint
    // check WSDL for URL
    return ‘https://www.myserver.com/services/datalookup/Service.svc/basic’;
    }

    function VerifySoapClient()
    {
    $result = true;

    if (!class_exists(‘SoapClient’))
    {
    $result = false;
    }

    return $result;
    }

    function GetVehicleMakes($data)
    {
    if (!VerifySoapClient())
    {
    // notification that SoapClient not installed
    }

    // setup soap options
    // traces let us look at the actual SOAP messages later
    // location – specify the url of the specific WCF endpoint to use
    $soap_options = array(‘trace’ => true,
    ‘exceptions’ => true,
    ‘location’ => GetUrl(),
    );

    // Set the URL of the WSDL
    // for WCF, this will be the same for any endpoint
    $wsdl = GetWsdl();

    $soapClient = new SoapClient($wsdl, $soap_options);

    try
    {

    // Setup Params for service call
    // in the associative array, the ids must
    // match the name of the params specified in
    // the WSDL for the operation being invoked
    $params = array(‘coId’ => 2232,
    ‘inquiryData’ => $data
    );

    // perform the web service operation
    $makeList = $soapClient->GetVehicleMakeList($params);

    // service returns an array of VehicleMakeServiceItem items
    // this function returns an array of vehicle Make strings.
    // e.g. Audi, Ford, Toyota, etc.
    // create a new array, cycle through each item in the
    // service lookup result populating $carmakes

    $carmakes = array();

    foreach($makeList->VehicleMakeServiceItem as $makeItem)
    {
    $carmakes[] = $makeItem->Make;
    }

    return $carmakes;

    } catch (SOAPFault $f) {
    // handle the fault here
    }

    }

    Danny B.

    2010/04/01 at 3:36 pm

  10. Do you know any problems with PHP 5.1.6?? in my case the code
    $result = $webservice->Purchase($search_query);
    doesn’t run, I don´t know why. Help me please.

    webroy

    2010/05/19 at 6:17 pm

  11. Hi

    This is really good information. How would one call upon a web services interface that takes more than one parameter and the parameters are a mixture of Complex Type, std:string and std:int ?

    For example:

    Any help will be greatly appreciated,

    regards

    Rajan

    Rajan

    2010/08/24 at 6:09 am

  12. Rajan

    2010/08/24 at 6:10 am

    • Your code didn’t know up — try wrapping it in the following (remove the ^^^ from the beginning):

      [^^^sourcecode language=”XML”]

      [^^^/sourcecode]

      Leif Madsen

      2010/08/24 at 9:38 am

  13. How to pass parameters to XML using PHP with NUSOAP. Very urgent plz

    Hi,

    I do not know how to pass parameters using soapclient with call method in PHP.

    ———————————————————————-
    Description:
    This command is used to authenticate the user. Most of the biz functions require authentication in order to function. If the session is already logged in then a subsequent successful login will switch the session to the new login agent

    Operation type:
    Request-response. The endpoint receives a message, and sends a correlated message.

    SOAP action: urn:UMARKETSCWS/login

    Input: login (soap:body, use = literal)

    ——————————————————————————

    xml format here:

    The agent that is logging in.

    The login password. The password is cryptographically hashed to further protect it. Please see the document “PD-SC-SOAP-UM-FS-07-10-A7 UMarket SOAP API Functional Specification.pdf” for details on how to generate the pin.

    ————————————-

    this is my php code
    ——————–
    $client = new soapclientw(‘https://somedomin.com/services/umarketsc?wsdl’, true);

    $loginRequest = array(‘sessionid’=>’1238384849′,’initiator’ =>’admin’,’pin’ =>’admin983′);

    $loginResult = $client->call(array(‘loginRequest’ => $loginRequest));

    print_r(‘

    ');
    print_r($loginResult);
    print_r('

    ‘);

    —————————————————————-

    Is it correct array format?

    I need to login our client server. But i can not. there is two type login and loginRequest in XML file in above.

    Please how can send paramters to XML file using call() method using soapcleint. Check XML file above. I want send three parameters to login method. This task very urgent for me. I hope you understand and reply soon friends.

    SOAP:action is login.

    Thanks WIlliam

    William Robert

    2010/11/16 at 11:33 am

  14. Above Post contniue …….

    xml format here:

    The agent that is logging in.

    The login password. The password is cryptographically hashed to further protect it. Please see the document “PD-SC-SOAP-UM-FS-07-10-A7 UMarket SOAP API Functional Specification.pdf” for details on how to generate the pin.

    ————————————-

    William Robert

    2010/11/16 at 11:35 am

  15. Hello everyone,

    I need to create a interface asterisk to the Web Service PHP.
    So look is possible by entering the following command in the Asterisk dialplan exten => _X., 1, AGI (agi: / / IP: PORT / Testing | $ {EXTEN} | {VAR2}). What I need is that through the Asterisk I can read and change the “Lights, ” but do not know if I have to call this function in index.php, in the soap-Client.php, or is the soap-server.php. I’ve tested it and if I manually change the index.php works, but I doubt how to adapt this command to that need.

    Can anyone give a help? Someone did something similar?

    Allthe

    2010/12/28 at 2:57 pm

  16. […] Consuming SOAP complexType webservice with PHP August 2009 21 comments […]

  17. hi all, am having a problem passing a parameter to a method in java web service. Here is the wsdl file http://208.45.248.77:7983/gistme/ws/
    keyword.wsdl, the parameter to pass is define as

    the kword namespace is at http://
    http://www.gistme.com/ws/model/kword. Myproblem now is how
    do i pass the Parameter GetKeywordByKeywordRequest in PHP 5 soap.

    jideobi

    2011/01/16 at 12:14 am

  18. any idea why this isnt working?

    searchCriteria = new StdClass();

    $search_query->searchCriteria->ZipCode = $Zip;

    // configure our WSDL location

    $wsdl = “http://ontheborder.com//restaurantlocator/restaurantlocator.asmx?WSDL”;

    // we use the WSDL file to create a connection to the web service

    $webservice = new SoapClient($wsdl);

    try {
    $result = $webservice->FetchRestaurantsWithGeocoding($search_query);

    // save our results to some variables
    $ZipResult = $result->FetchRestaurantsWithGeocodingResult->Restaurants;

    print_r($ZipResult);

    } catch (SOAPFault $f) {
    // handle the fault here

    }

    ?>

    branch

    2011/07/20 at 10:34 am

  19. Hi Leif, simple yet brilliant info. Before reading your post I spent a lot of time reading up uselesss stuff. Thank you mate

    Clive Cussler

    2012/03/13 at 11:13 am

    • Glad it helped! This post is pretty old now, but it was the most complete set of information at the time. Not sure if things have gotten any better, but I had to piece a lot of stuff together from multiple incomplete posts in order to get something that I could come back to and follow. There have been a lot of good follow up comments as well from various people. This is still one of my top 3 posts in terms of views per week (https://leifmadsen.wordpress.com/2009/08/27/creating-vcards-with-qr-codes/ is the top one, this is number two.)

      Leif Madsen

      2012/03/13 at 12:45 pm

  20. Thank you for this information Leif. I have to do something similar except that in the WSDL for the service I must consume, “maxOccurs” for oPurchaseRequest is unlimited. Would you know how to create an array of in your case “oPurchaseRequest” elements? If I create a single element, everything is fine, however if I create an array, PHP encodes the SOAP message as “1…2…….. The web service called doesn’t understand the elements and treats the contents as NULL.

    krb

    2012/03/19 at 8:29 am

    • Sorry, unfortunately I haven’t used SOAP in PHP since the project I was using it on, which I think about almost 4 years ago now, so I won’t be able to answer your question.

      Leif Madsen

      2012/03/19 at 8:34 am

    • Apologies – the XML tags didn’t post properly. What happens with arrays is I get something like:

      <oPurchaseRequest>
      <item>
      <TransactionID>1</TransactionID>
      .
      .
      .
      </item>
      </oPurchaseRequest>

      <oPurchaseRequest>
      <item>
      <TransactionID>2</TransactionID>
      .
      .
      .
      </item>
      </oPurchaseRequest>

      and the web service at the other end does not understand the ‘item’ element.

      krb

      2012/03/19 at 8:48 am

      • Try wrapping source code in [^^^sourcecode lang=”XML”] and closing with [^^^/sourcecode] (without the leading ^^^ though).

        <oPurchaseRequest>
          <item>
            <TransactionID>1</TransactionID>
          </item>
        </oPurchaseRequest>
        

        Leif Madsen

        2012/03/19 at 9:07 am

  21. hey, great article and i have been google in for days to find similar to this for my soap – javascript- php solution

    Yusuf

    2012/03/19 at 11:44 am

  22. Oddly enough, this post had several ‘&gt’ where I meant to have a >, so I’ve fixed those in the post.

    Leif Madsen

    2012/03/21 at 1:44 pm

  23. Hi,
    i tried same thing but getting following error

    do you have any idea about below error

    SOAP-ERROR: Parsing WSDL: Couldn’t load from ‘http://xxx.xxx..asmx?WSDL’ : failed to load external entity “http://xxx.xxx..asmx.asmx?WSDL”

    gyaan

    2012/06/18 at 6:36 am

    • Sorry, it’s been a few years since I’ve had to work in this area. I saw .asmx.asmx.WDSL in your quote though — looks like you might be specifying an extra .asmx in there somewhere.

      Leif Madsen

      2012/06/18 at 7:17 am

      • hi,
        thanks for quick replay.
        that was my typing error.
        that is not a issue .

        gyaan

        2012/06/18 at 7:32 am

  24. […] Ich hatte hier https://leifmadsen.wordpress.com/2009…vice-with-php/ auch noch gelesen, dass man Complex Types so übergeben sollte, was haltet ihr davon? […]


Comments are closed.