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