The code in WSClientServiceDescription::invoke() suggests to me that it should be possible to call web service methods using named paramaters, but we're not having much luck getting it to work. Without being able to use named parmaters, code becomes pretty much illegible when there are lots of paramaters for a method.

For example. An 'update_profile' method that takes paramaters od first_name, last_name, address... and maybe 20 others.

We can call this like

$service->update_profile('Tom', 'KP', 'Address line 1' ... [20 other paramaters])

It works, but impossible to read. What if I only want to update one of those 20 paramaters, and its near the end of the sequential list? Better, would be something like this:

$service->update_profile(array('favorite_color' => 'red'));

Here, the code is instantly readable, and and is not filled with a bunch of NULL values just to get to some paramater near the end.

Things we have tried, with no success:

$service->update_profile(array('favorite_color' => 'red'));
$service->update_profile('favorite_color' => 'red');
$service->update_profile('favorite_color', 'red');

What am I missing here? Is it just not possible right now? If not, then what does the code in invoke() do:

      // Assign named arguments and hidden parameters.
      foreach ($this->operations[$operation]['parameter'] as $param => $info) {
        if (isset($arguments[$param])) {
          $named_arguments[$param] = $arguments[$param];
          unset($arguments[$param]);
          unset($remaining_params[$param]);
        }
        elseif ($info['type'] == 'hidden') {
          $named_arguments[$param] = $info['default value'];
          unset($remaining_params[$param]);
        }
      }

How would $arguments[$param] ever be set? The indexes of $arguments are always numeric.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

klausi’s picture

Is it a SOAP service or a REST service? Are the operation parameters configured? Did you check the operation in the UI that it lists all parameters with their names?

mrfelton’s picture

It is a REST service. Not 100% sure what you mean by "Are the operation parameters configured?", but yes - all the parameters are defined. This service description was not built through the UI, it was built in code. But when I look at it in the UI, everything looks perfect. Here is an example of one of the operation descriptions:

  // Operation: Prospect ------------------------------------------------------
  $operation = array();
  $operation['label'] = 'Create Prospect';
  $operation['url'] = 'prospects';
  $operation['type'] = 'POST';
  $operation['parameter'] = example_api_profile_dataypes();
  $operation['parameter']['terms_of_membership_id'] = array(
    'type' => 'integer',
    'label' => 'Terms of Membership ID',
  );
  $operation['parameter']['url_landing'] = array(
    'type' => 'uri',
    'label' => 'URL landing',
    'optional' => TRUE,
  );
  $operation['parameter']['user_id'] = array(
    'type' => 'integer',
    'label' => 'User ID',
    'optional' => TRUE,
  );
  $operation['parameter']['reporting_code'] = array(
    'type' => 'text',
    'label' => 'Reporting Code',
    'optional' => TRUE,
  );
  $operation['parameter']['ip_address'] = array(
    'type' => 'text',
    'label' => 'IP Address',
    'optional' => TRUE,
  );
  $operation['parameter']['user_agent'] = array(
    'type' => 'text',
    'label' => 'User agent',
    'optional' => TRUE,
  );
  $operation['parameter']['referral_host'] = array(
    'type' => 'uri',
    'label' => 'Referral host',
    'optional' => TRUE,
  );
  $operation['parameter']['joint'] = array(
    'type' => 'boolean',
    'label' => 'Join',
    'optional' => TRUE,
  );
  $operation['parameter']['preferences'] = array(
    'type' => 'wsclient_example_api_preferences',
    'label' => 'Preferences',
    'optional' => TRUE,
  );
  $operation['parameter']['api_key'] = array(
    'type' => 'text',
    'label' => 'API Key',
  );
  $operation['result'] = array('type' => 'wsclient_example_api_prospect_result', 'label' => 'Prospect result');
  $service->operations['prospect'] = $operation;

How can I call this operation in code, only passing the referral_host parameter?

longwave’s picture

You can call the invoke() method yourself and pass a set of named arguments, something like

    $service = wsclient_service_load('service_name');
    $result = $service->invoke('method', array(
      'param1' => 'value1',
      'param2' => 'value2',
      'param3' => 'value3',
      'param4' => 'value4',
    ));

I have not tested this with optional arguments.

It would however be better if the magic invocation (e.g. calling $service->method() directly) could accept a named array.

emptyvoid’s picture

Thanks everyone for posting your code examples it really helped point me in the right direction. Too bad this type of information isn't in the documentation or the read me. :(

I found when using the SOAP client, depending on how the WSDL was defined I had to wrap arguments within multiple arrays to get a correct response. This was especially true for complex types.

Calling a operation directly didn't work for me, if I used invoke it seems to provide better results.

So here is an examples:


function shop_service_productinformation($productId, $returnObject = TRUE) {
    $status = '';
    $service = wsclient_service_load('shop_bridge');

    try {
        //$result = $service->ProductInformation((int)$productId);
        $result = $service->invoke('ProductInformation', array('ProductInformation' => array('productId' => $productId)));
        if ($returnObject) {
            $status = $result;
        } else {
            $status .= "<pre>";
            $status .= print_r($result, TRUE);
            $status .= "</pre>";
        }

    } catch (Exception $e) {
        $status .= ' : ERROR = '.$e->getMessage().'<br />';
    }

    return $status;
}

In this example I wrote a function that calls the service description then passes a productId to the operation. For some reason I have to wrap the parameter within a array with exactly the same name as the operation.


function shop_service_submitorder($firstName, $lastName, $address1, $address2, $city, $zip, $returnObj = TRUE) {
    
    $orderArr = array(
        'FirstName' => $firstName,
        'LastName' => $lastName,
        'Address1' => $address1,
        'Address2' => $address2,
        'City' => $city,
        'Zip' => $zip,
    );

$status = '';
    $service = wsclient_service_load('shop_bridge');

    if ($OrderSubmission !== FALSE) {
        try {
            $result = $service->invoke('SubmitOrder', array('SubmitOrder' => array('order' => $orderArr)));
            if ($returnObject) {
                $status = $result;
            } else {
                $status .= "<pre>";
                $status .= print_r($result, TRUE);
                $status .= "</pre>";
            }

        } catch (Exception $e) {
            $status .= ' : ERROR = '.$e->getMessage().'<br />';
        }
    } else {
        if ($returnObject) {
            return FALSE;
        } else {
            $status .= ' : ERROR = Object does not exist!<br />';
        }
    }

    return $status;

The WSDL this is based on:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://example.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" targetNamespace="http://example.com/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <s:schema elementFormDefault="qualified" targetNamespace="http://example.com/">
      <s:element name="ProductInformation">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="productId" type="s:int"/>
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="ProductInformationResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="ProductInformationResult" type="tns:ProductInformationResult"/>
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:complexType name="ProductInformationResult">
        <s:complexContent mixed="false">
          <s:extension base="tns:Result">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="Product" type="tns:ProductInformation"/>
            </s:sequence>
          </s:extension>
        </s:complexContent>
      </s:complexType>
      <s:complexType name="Result" abstract="true">
        <s:sequence>
          <s:element minOccurs="1" maxOccurs="1" name="Success" type="s:boolean"/>
          <s:element minOccurs="0" maxOccurs="1" name="ErrorMessage" type="s:string"/>
        </s:sequence>
      </s:complexType>
      <s:complexType name="ProductInformation">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="SKU" type="s:string"/>
          <s:element minOccurs="1" maxOccurs="1" name="Price" type="s:decimal"/>
        </s:sequence>
      </s:complexType>
      <s:element name="SubmitOrder">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="order" type="tns:OrderSubmission"/>
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:complexType name="OrderSubmission">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="1" name="FirstName" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="LastName" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="Address1" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="Address2" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="City" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="State" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="Zip" type="s:string"/>
        </s:sequence>
      </s:complexType>
      <s:element name="SubmitOrderResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="SubmitOrderResult" type="tns:OrderSubmissionResult"/>
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:complexType name="OrderSubmissionResult">
        <s:complexContent mixed="false">
          <s:extension base="tns:Result">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="Submission" type="tns:OrderSubmission"/>
            </s:sequence>
          </s:extension>
        </s:complexContent>
      </s:complexType>
      <s:element name="ProductInformationResult" nillable="true" type="tns:ProductInformationResult"/>
    </s:schema>
  </wsdl:types>
  <wsdl:message name="ProductInformationSoapIn">
    <wsdl:part name="parameters" element="tns:ProductInformation"/>
  </wsdl:message>
  <wsdl:message name="ProductInformationSoapOut">
    <wsdl:part name="parameters" element="tns:ProductInformationResponse"/>
  </wsdl:message>
  <wsdl:message name="SubmitOrderSoapIn">
    <wsdl:part name="parameters" element="tns:SubmitOrder"/>
  </wsdl:message>
  <wsdl:message name="SubmitOrderSoapOut">
    <wsdl:part name="parameters" element="tns:SubmitOrderResponse"/>
  </wsdl:message>
  <wsdl:message name="ProductInformationHttpGetIn">
    <wsdl:part name="productId" type="s:string"/>
  </wsdl:message>
  <wsdl:message name="ProductInformationHttpGetOut">
    <wsdl:part name="Body" element="tns:ProductInformationResult"/>
  </wsdl:message>
  <wsdl:message name="ProductInformationHttpPostIn">
    <wsdl:part name="productId" type="s:string"/>
  </wsdl:message>
  <wsdl:message name="ProductInformationHttpPostOut">
    <wsdl:part name="Body" element="tns:ProductInformationResult"/>
  </wsdl:message>
  <wsdl:portType name="DrupalCommerceIntegrationSoap">
    <wsdl:operation name="ProductInformation">
      <wsdl:input message="tns:ProductInformationSoapIn"/>
      <wsdl:output message="tns:ProductInformationSoapOut"/>
    </wsdl:operation>
    <wsdl:operation name="SubmitOrder">
      <wsdl:input message="tns:SubmitOrderSoapIn"/>
      <wsdl:output message="tns:SubmitOrderSoapOut"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:portType name="DrupalCommerceIntegrationHttpGet">
    <wsdl:operation name="ProductInformation">
      <wsdl:input message="tns:ProductInformationHttpGetIn"/>
      <wsdl:output message="tns:ProductInformationHttpGetOut"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:portType name="DrupalCommerceIntegrationHttpPost">
    <wsdl:operation name="ProductInformation">
      <wsdl:input message="tns:ProductInformationHttpPostIn"/>
      <wsdl:output message="tns:ProductInformationHttpPostOut"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="DrupalCommerceIntegrationSoap" type="tns:DrupalCommerceIntegrationSoap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="ProductInformation">
      <soap:operation soapAction="http://example.com/ProductInformation" style="document"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="SubmitOrder">
      <soap:operation soapAction="http://example.com/SubmitOrder" style="document"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:binding name="DrupalCommerceIntegrationSoap12" type="tns:DrupalCommerceIntegrationSoap">
    <soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="ProductInformation">
      <soap12:operation soapAction="http://example.com/ProductInformation" style="document"/>
      <wsdl:input>
        <soap12:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap12:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="SubmitOrder">
      <soap12:operation soapAction="http://example.com/SubmitOrder" style="document"/>
      <wsdl:input>
        <soap12:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap12:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:binding name="DrupalCommerceIntegrationHttpGet" type="tns:DrupalCommerceIntegrationHttpGet">
    <http:binding verb="GET"/>
    <wsdl:operation name="ProductInformation">
      <http:operation location="/ProductInformation"/>
      <wsdl:input>
        <http:urlEncoded/>
      </wsdl:input>
      <wsdl:output>
        <mime:mimeXml part="Body"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:binding name="DrupalCommerceIntegrationHttpPost" type="tns:DrupalCommerceIntegrationHttpPost">
    <http:binding verb="POST"/>
    <wsdl:operation name="ProductInformation">
      <http:operation location="/ProductInformation"/>
      <wsdl:input>
        <mime:content type="application/x-www-form-urlencoded"/>
      </wsdl:input>
      <wsdl:output>
        <mime:mimeXml part="Body"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="DrupalCommerceIntegration">
    <wsdl:port name="DrupalCommerceIntegrationSoap" binding="tns:DrupalCommerceIntegrationSoap">
      <soap:address location="http://example.com/DrupalCommerceIntegration.asmx"/>
    </wsdl:port>
    <wsdl:port name="DrupalCommerceIntegrationSoap12" binding="tns:DrupalCommerceIntegrationSoap12">
      <soap12:address location="http://example.com/DrupalCommerceIntegration.asmx"/>
    </wsdl:port>
    <wsdl:port name="DrupalCommerceIntegrationHttpGet" binding="tns:DrupalCommerceIntegrationHttpGet">
      <http:address location="http://example.com/DrupalCommerceIntegration.asmx"/>
    </wsdl:port>
    <wsdl:port name="DrupalCommerceIntegrationHttpPost" binding="tns:DrupalCommerceIntegrationHttpPost">
      <http:address location="http://example.com/DrupalCommerceIntegration.asmx"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

I hope this helps some one..

erinclerico’s picture

FileSize
570 bytes

If you look a few lines down the assignment of '$named_arguments[$param]' (currently at line 73) reads differently:

    foreach ($this->global_parameters as $param => $info) {
      if (!isset($named_arguments[$param])) {
        $named_arguments[$param] = $info['default value'];
      }
    }

So I reason that line 55 that currently reads:

$named_arguments[$param] = $arguments[$param];

Should read:

$named_arguments[$param] = $info['default value'];

This patch solved the problem for me.

othermachines’s picture

Status: Active » Needs review
FileSize
2.31 KB

@erinclerico - Unfortunately the patch in #5 won't do because $info['default value'] is not always what you want.

I'm attaching a patch that will permit a set of named parameters to be passed into the magic invocation (calling $webservice->method() directly).

I've also added a bit of code that will unset unused parameters. This issue is being discussed over here: Issue #8655847: The provided argument for parameter is empty. I thought it might be OK to combine them since they are so closely related.

** Edit: Maybe not such a hot idea to combine these two. See patch in #7.

othermachines’s picture

FileSize
1.54 KB

Posting a new patch that only deals with this issue (using named parameters).

Test results:


  1. $status = $service->get_jobs('2210', NULL, NULL, '4');

    Array
    (
        [series] => 2210
        [minsalary] => 
        [locationname] => 
        [numberofjobs] => 4
    )
    
  2. $status = $service->get_jobs(array('series' => '2210', 'numberofjobs' => '4'));

    Array
    (
        [series] => 2210
        [numberofjobs] => 4
        [minsalary] => 
        [locationname] => 
    )
    
  3. $status = $service->invoke('get_jobs', array('series' => '2210', 'minsalary' => '', 'locationname' => '', 'numberofjobs' => '4'));

    Array
    (
        [series] => 2210
        [minsalary] => 
        [locationname] => 
        [numberofjobs] => 4
    )
    
druth’s picture

@emptyvoid comment #4 thank you for posting this. This helped me!

Dan

TheLioness22’s picture

@emptyvoid, #4 -- example code was just what I needed. I was missing the extra array. I couldn't get this to submit right with the "magic" function, so went down the $service->invoke route. Couldn't figure out why it wasn't working until I came across this thread and wrapped my array in another array with the key matching the service call function/method. Agree with you that this should be in the documentation somewhere -- this wasn't intuitively discovered. Thanks again!