SOAP Error in PHP: OperationFormatter encountered

2019-03-06 00:08发布

问题:

Original Question:

I'm trying to get data from SOAP API wsdl link. My code as below. But I get this error. Can anyone please help with this?

Error Message: OperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope'

<?php
$api_link = 'https://www.my-api-link.com/RateAPI.svc/SSL?wsdl';

//setting xml request to api
$request = '<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/" xmlns:ezr="http://schemas.datacontract.org/2004/07/EzremitAPI.Entities">
              <soapenv:Body>
                 <tem:GetLocalRates>
                     <tem:credentials>
                         <ezr:AgentCode>####</ezr:AgentCode>
                         <ezr:HashedPassword>####</ezr:HashedPassword>
                         <ezr:Username>####</ezr:Username>
                     </tem:credentials>
                     <tem:payincurcode>####</tem:payincurcode>
                     <tem:transferType>####</tem:transferType>
                 </tem:GetLocalRates>
              </soapenv:Body>
            </soapenv:Envelope>';

try {
$client = new SoapClient($api_link, array('cache_wsdl' => WSDL_CACHE_NONE, 'soap_version' => SOAP_1_2, 'reliable' => 1.2 , 'useWSA' => TRUE ) );
$soapaction = "http://tempuri.org/IRateAPI/GetLocalRates";
$client->soap_defencoding = 'UTF-8';
// Apply WSA headers
$headers = array();
$headers[] = new SoapHeader('http://www.w3.org/2005/08/addressing', 'To', 'https://www.my-api-link.com/RateAPI.svc/SSL?wsdl', true);
$headers[] = new SoapHeader('http://www.w3.org/2005/08/addressing', 'Action', 'http://tempuri.org/IRateAPI/GetLocalRates', true);
$client->__setSoapHeaders($headers);

$response = $client->GetLocalRates(new SoapVar($request, XSD_ANYXML));
print_r($response);

}
  catch(Exception $e) {
    echo $e->getMessage();
}
?>

Edit 1 (Code amended as per to 1st comment)

Results:

http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/faults:Receivera:InternalServiceFaultOperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope'OperationFormatter encountered an invalid Message body. Expected to find node type 'Element' with name 'GetLocalRates' and namespace 'http://tempuri.org/'. Found node type 'Element' with name 'soapenv:Envelope' and namespace 'http://www.w3.org/2003/05/soap-envelope' at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object[] parameters, Boolean isRequest)
 at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest)
 at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object[] parameters)
 at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
 at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)System.Runtime.Serialization.SerializationException

Edit 2:

$client->__getTypes() Results:

array(8) {
  [0]=>
  string(84) "struct EzCredential {
 string AgentCode;
 string HashedPassword;
 string Username;
}"
  [1]=>
  string(58) "struct ArrayOfCurrencyRate {
 CurrencyRate CurrencyRate;
}"
  [2]=>
  string(187) "struct CurrencyRate {
 decimal AgentMargin;
 string CurrencyCode;
 string CurrencyDescription;
 decimal FromAmount;
 decimal Rate;
 string RateType;
 decimal ToAmount;
 string Trantype;
}"
  [3]=>
  string(95) "struct GetLocalRates {
 EzCredential credentials;
 string payincurcode;
 string transferType;
}"
  [4]=>
  string(74) "struct GetLocalRatesResponse {
 ArrayOfCurrencyRate GetLocalRatesResult;
}"
  [5]=>
  string(8) "int char"
  [6]=>
  string(17) "duration duration"
  [7]=>
  string(11) "string guid"
}

$client->__getFunctions() Results:

array(1) {
  [0]=>
  string(62) "GetLocalRatesResponse GetLocalRates(GetLocalRates $parameters)"
}

Fixed: Used below instead of the XML envelope. Thanks a lot, @Marcel. You are a great saviour.

$requestParams = array( 'credentials' => array('AgentCode' => $acode,
                                               'HashedPassword' => $hpass,
                                               'Username' => $uname),
                  'payincurcode' => $ccode,
                  'transferType' => $ttype
              );

$response = $client->GetLocalRates( $requestParams );

回答1:

Thank you in advance that you have updated your question with the missing data. This example is a native php example that shows how to work with soap functions and types in an object orientated way.

1. Types and classes

As you can see in your types array, there are several types declared as struct. If you like to say so, structs can be called PHP classes. So lets make a single php class out of every struct.

class EzCredentialStruct
{
    public $AgentCode;

    public $HashedPassword;

    public $Username;

    public function getAgentCode() : string
    {
        return $this->AgentCode;
    }

    public function setAgentCode(string $AgentCode) : EzCredentialStruct
    {
        $this->AgentCode = $AgentCode;
        return $this;
    }

    public function getHashedPassword() : string
    {
        return $this->HashedPassword;
    }

    public function setHashedPassword(string $HashedPassword) : EzCredentialStruct
    {
        $this->HashedPassword = $HashedPassword;
        return $this;
    }

    public function getUsername() : string
    {
        return $this->Username;
    }

    public function setUsername(string $Username) : EzCredentialStruct
    {
        $this->Username = $Username;
        return $this;
    }
}

class GetLocalRatesStruct
{
    public $credentials;

    public $payincurcode;

    public $transferType;

    public function getCredentials() : EzCredentialStruct
    {
        return $this->credentials;
    }

    public function setCredentials(EzCredentialStruct $credentials) : GetLocalRatesStruct
    {
        $this->credentials = $credentials;
        return $this;
    }

    public function getPayincurcode() : string
    {
        return $this->payincurcode;
    }

    public function setPayincurcode(string $payincurcode) : GetLocalRatesStruct
    {
        $this->payincurcode = $payincurcode;
        return $this;
    }

    public function getTransferType() : string
    {
        return $this->transferType;
    }

    public function setTransferType(string $transferType) : GetLocalRatesStruct
    {
        $this->transferType = $transferType;
        return $this;
    }
}

These two classes are examples of all structs from your types array. So write down all your structs as classes. You will notice the benefit later.

2. Soap Client and the classmap option

Now, as we hace declared the used types of the webservice as classes, we can initiate the soap client. It is important to initiate the soap client with the right options. Always wrap up the client in a try/catch block.

try {
    $options = [
        'cache_wsdl' => WSDL_CACHE_NONE, 
        'soap_version' => SOAP_1_2,
        'trace' => true,
        'classmap' => [
            'EzCredential' => 'EzCredentialStruct',
            'GetLocalRates' => 'GetLocalRatesStruct',
        ],
    ];

    $wsdl = 'path/to/the/webservice/endpoint?wsdl';

    $client = new \SoapClient(
        $wsdl,
        $options,
    );
} catch (\SoapFault $e) {
    // error handling
}

AS you can see there is a classmap key in the options array. The classmap routes types to specific php classes. In this example we only use the two example type classes we defined earlier. The soap client can now automatically create the xml string the webservice needs.

3. Put it all together

Now, as we have all we need for a proper soap request, our code should look as follows.

$credentials = (new EzCredentialStruct())
    ->setAgentCode($agentCode)
    ->setHashedPassword($hashedPassword)
    ->setUsername($username);

$request = (new GetLocalRatesStruct())
    ->setCredentials($credentials)
    ->setPayincurcode($code)
    ->setTransferType($transferType);

$result = $client->GetLocalRates($request);

Conclusion

At first glance, this approach may look like more work. But it makes sense to separate the types from the functions and programming classes for each struct the webservice declares. The call of the webservice function will be easier, because we only pass an object as a parameter. No more weird array constructs. Everything is in its place and can be reused.

PS: This code is not tested. It is not recommended using it in production. This code is intended as an example only. It may contain errors.