Today I implemented my first PHP Web Service and successfully used it by C# client or, more precisely, I created WCF service contract in C# and implemented it in PHP. A new option “singlewsdl” in WCF 4.5 helped me a lot. Using this option I generated the service description as a single WSDL-file without additional XSD-files containing the data contracts of the service. Then I uploaded this WSDL-file on my Web Server and written PHP code for all the methods of the service.
My service contract has a method that returns array of custom objects and receives custom object as parameter, along with a simple method that returns a string:
[ServiceContract] public interface IOnlineStore { [OperationContract] string GetStoreVersion(int product_id); [OperationContract] Product GetProductById(int product_id); [OperationContract] Product[] GetProducts(int category_id, IdRange range); } [DataContract] public struct IdRange { [DataMember] public int Start; [DataMember] public int End; } [DataContract] public class Product { [DataMember] public int Id; [DataMember] public string Name; [DataMember] public string Title; [DataMember] public float Price; }
When I started my service under Visual Studio 2012 debugger and opened “http://localhost:8733/Design_Time_Addresses/VmService/Service1/?singlewsdl” in my browser, WCF 4.5 generated the following WSDL-file:
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions name="VirtueMartService" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://tempuri.org/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"> <wsdl:types> <xs:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:import namespace="http://schemas.datacontract.org/2004/07/VmService" /> <xs:element name="GetStoreVersion"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="product_id" type="xs:int" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="GetStoreVersionResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="GetStoreVersionResult" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="GetProductById"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="product_id" type="xs:int" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="GetProductByIdResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="GetProductByIdResult" nillable="true" type="q1:Product" xmlns:q1="http://schemas.datacontract.org/2004/07/VmService" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="GetProducts"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="category_id" type="xs:int" /> <xs:element minOccurs="0" name="range" type="q2:IdRange" xmlns:q2="http://schemas.datacontract.org/2004/07/VmService" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="GetProductsResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="GetProductsResult" nillable="true" type="q3:ArrayOfProduct" xmlns:q3="http://schemas.datacontract.org/2004/07/VmService" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> <xs:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/"> <xs:element name="anyType" nillable="true" type="xs:anyType" /> <xs:element name="anyURI" nillable="true" type="xs:anyURI" /> <xs:element name="base64Binary" nillable="true" type="xs:base64Binary" /> <xs:element name="boolean" nillable="true" type="xs:boolean" /> <xs:element name="byte" nillable="true" type="xs:byte" /> <xs:element name="dateTime" nillable="true" type="xs:dateTime" /> <xs:element name="decimal" nillable="true" type="xs:decimal" /> <xs:element name="double" nillable="true" type="xs:double" /> <xs:element name="float" nillable="true" type="xs:float" /> <xs:element name="int" nillable="true" type="xs:int" /> <xs:element name="long" nillable="true" type="xs:long" /> <xs:element name="QName" nillable="true" type="xs:QName" /> <xs:element name="short" nillable="true" type="xs:short" /> <xs:element name="string" nillable="true" type="xs:string" /> <xs:element name="unsignedByte" nillable="true" type="xs:unsignedByte" /> <xs:element name="unsignedInt" nillable="true" type="xs:unsignedInt" /> <xs:element name="unsignedLong" nillable="true" type="xs:unsignedLong" /> <xs:element name="unsignedShort" nillable="true" type="xs:unsignedShort" /> <xs:element name="char" nillable="true" type="tns:char" /> <xs:simpleType name="char"> <xs:restriction base="xs:int" /> </xs:simpleType> <xs:element name="duration" nillable="true" type="tns:duration" /> <xs:simpleType name="duration"> <xs:restriction base="xs:duration"> <xs:pattern value="\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?" /> <xs:minInclusive value="-P10675199DT2H48M5.4775808S" /> <xs:maxInclusive value="P10675199DT2H48M5.4775807S" /> </xs:restriction> </xs:simpleType> <xs:element name="guid" nillable="true" type="tns:guid" /> <xs:simpleType name="guid"> <xs:restriction base="xs:string"> <xs:pattern value="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}" /> </xs:restriction> </xs:simpleType> <xs:attribute name="FactoryType" type="xs:QName" /> <xs:attribute name="Id" type="xs:ID" /> <xs:attribute name="Ref" type="xs:IDREF" /> </xs:schema> <xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/VmService" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/VmService"> <xs:import namespace="http://schemas.microsoft.com/2003/10/Serialization/" /> <xs:complexType name="Product"> <xs:sequence> <xs:element minOccurs="0" name="Id" type="xs:int" /> <xs:element minOccurs="0" name="Name" nillable="true" type="xs:string" /> <xs:element minOccurs="0" name="Price" type="xs:float" /> <xs:element minOccurs="0" name="Title" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:element name="Product" nillable="true" type="tns:Product" /> <xs:complexType name="IdRange"> <xs:annotation> <xs:appinfo> <IsValueType xmlns="http://schemas.microsoft.com/2003/10/Serialization/">true</IsValueType> </xs:appinfo> </xs:annotation> <xs:sequence> <xs:element minOccurs="0" name="End" type="xs:int" /> <xs:element minOccurs="0" name="Start" type="xs:int" /> </xs:sequence> </xs:complexType> <xs:element name="IdRange" nillable="true" type="tns:IdRange" /> <xs:complexType name="ArrayOfProduct"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" nillable="true" type="tns:Product" /> </xs:sequence> </xs:complexType> <xs:element name="ArrayOfProduct" nillable="true" type="tns:ArrayOfProduct" /> </xs:schema> </wsdl:types> <wsdl:message name="IOnlineStore_GetStoreVersion_InputMessage"> <wsdl:part name="parameters" element="tns:GetStoreVersion" /> </wsdl:message> <wsdl:message name="IOnlineStore_GetStoreVersion_OutputMessage"> <wsdl:part name="parameters" element="tns:GetStoreVersionResponse" /> </wsdl:message> <wsdl:message name="IOnlineStore_GetProductById_InputMessage"> <wsdl:part name="parameters" element="tns:GetProductById" /> </wsdl:message> <wsdl:message name="IOnlineStore_GetProductById_OutputMessage"> <wsdl:part name="parameters" element="tns:GetProductByIdResponse" /> </wsdl:message> <wsdl:message name="IOnlineStore_GetProducts_InputMessage"> <wsdl:part name="parameters" element="tns:GetProducts" /> </wsdl:message> <wsdl:message name="IOnlineStore_GetProducts_OutputMessage"> <wsdl:part name="parameters" element="tns:GetProductsResponse" /> </wsdl:message> <wsdl:portType name="IOnlineStore"> <wsdl:operation name="GetStoreVersion"> <wsdl:input wsaw:Action="http://tempuri.org/IOnlineStore/GetStoreVersion" message="tns:IOnlineStore_GetStoreVersion_InputMessage" /> <wsdl:output wsaw:Action="http://tempuri.org/IOnlineStore/GetStoreVersionResponse" message="tns:IOnlineStore_GetStoreVersion_OutputMessage" /> </wsdl:operation> <wsdl:operation name="GetProductById"> <wsdl:input wsaw:Action="http://tempuri.org/IOnlineStore/GetProductById" message="tns:IOnlineStore_GetProductById_InputMessage" /> <wsdl:output wsaw:Action="http://tempuri.org/IOnlineStore/GetProductByIdResponse" message="tns:IOnlineStore_GetProductById_OutputMessage" /> </wsdl:operation> <wsdl:operation name="GetProducts"> <wsdl:input wsaw:Action="http://tempuri.org/IOnlineStore/GetProducts" message="tns:IOnlineStore_GetProducts_InputMessage" /> <wsdl:output wsaw:Action="http://tempuri.org/IOnlineStore/GetProductsResponse" message="tns:IOnlineStore_GetProducts_OutputMessage" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="BasicHttpBinding_IOnlineStore" type="tns:IOnlineStore"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="GetStoreVersion"> <soap:operation soapAction="http://tempuri.org/IOnlineStore/GetStoreVersion" style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> <wsdl:operation name="GetProductById"> <soap:operation soapAction="http://tempuri.org/IOnlineStore/GetProductById" style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> <wsdl:operation name="GetProducts"> <soap:operation soapAction="http://tempuri.org/IOnlineStore/GetProducts" style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="VirtueMartService"> <wsdl:port name="BasicHttpBinding_IOnlineStore" binding="tns:BasicHttpBinding_IOnlineStore"> <soap:address location="http://test.com/private/vmservice.php" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
I wondered that all the responses are also sequences, it probably means that WCF support output parameters, so all PHP methods return arrays:
<?php class VmService { function GetProducts($params) { $product = new StdClass; $product->Id = 1; $product->Name = "Name"; $product->Title = "Title"; $product->Price = 20; $product1 = new StdClass; $product1->Id = 2; $product1->Name = "Name1"; $product1->Title = "Title1"; $product1->Price = 201; /* return new SoapFault("1", "Params: " . $params->category_id . " " . $params->range->Start . " " . $params->range->End);*/ /* return new SoapFault("1", "Params: " . $productArray[0]->Name . " " . $product->Name . " " . $params->range->End);*/ return array("GetProductsResult" => array($product, $product1)); } function GetProductById($params) { $product = new StdClass; $product->Id = 1; $product->Name = "Name"; $product->Title = "Title"; $product->Price = 20; throw new Exception('Something happened!'); return array("GetProductByIdResult" => $product); } function GetStoreVersion($params) { return array("GetStoreVersionResult" => "Version 00.00.01"); } } ini_set("soap.wsdl_cache_enabled","0"); $server = new SoapServer('vmservice.wsdl', array('soap_version' => SOAP_1_2)); $server->setClass("VmService"); try { $server->handle(); } catch (Exception $e) { $server->fault('Sender', "An exception has occured: " . $e->getMessage()); } ?>
C# client that uses the service reference generated from PHP Web Service looks like this:
OnlineStoreClient client = new OnlineStoreClient(); string ver = client.GetStoreVersion(27); try { Product product = client.GetProductById(25); } catch (System.ServiceModel.FaultException ex) { Console.WriteLine("Method call has failed: " + ex.Message); } Product[] products = client.GetProducts(15, new IdRange() { Start = 1, End = 2 });
That’s it. I hope this technics is useful for others. Do not forget to replace “http://test.com/private/vmservice.php” with your real service address in the WSDL-file.
To serialize generated object with BinaryFormatter, event handlers should be marked with NonSerialized attribute:
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
see http://social.msdn.microsoft.com/Forums/vstudio/en-US/e78cb7f8-0b17-4076-b9c0-1bf2e9eec028/cannot-serialize-objects-from-classes-derived-from-inotifypropertychanged