How to implement WCF service in PHP

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.

1 Response to How to implement WCF service in PHP

  1. dmitriano says:

    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

Leave a Reply

Your email address will not be published. Required fields are marked *