WSO2Con 2013 CFP Banner

PHP Web Services with WSDL

Discuss this article on Stack Overflow
By Dimuthu Gamage
  • 20 Mar, 2008
  • Level:  Introductory
  • Reads: 50886

In this introductory tutorial, Dimuthu Gamage explains how you can use the contract first model, which is also called WSDL mode, to write your Web services and clients using WSO2 WSF/PHP.

Applies To

WSO2 WSF/PHP 1.2.0 or higher
Environment Windows or Linux

 

Introduction

In writing a Web service, you can choose from one of the two available approaches to developing them. They are,

  1. code-first approach
  2. contract-first approach.

In the code first approach,  you will initially code the interface you hope to publish as a Web Service. Then you generate the WSDL that becomes the contract (at least the major part of the contract), based on the written code. In contract-first approach, you start with the WSDL and then generate the code using a programming language. From the given approaches you need to selectively choose the one that best suits your problem environment. You can find discussions on these topics in these articles[1].

In writing Web services in PHP, WSO2 WSF/PHP (Web Services Framework for PHP) helps you, regardless of the approach you choose to follow. If you choose the contract-first approach, the framework is able to provide you with a PHP friendly interface for the development of the WSDL.

i.e.

  • When you call a PHP function with the same name as the name of the Web service's operation, it will automatically invoke the service operation.
  • You can use familiar PHP types instead of unfamiliar XML-schema types as arguments for your PHP function.

 

In this tutorial, I will illustrate how you can develop a very simple Web service and a client using the Contract-first approach.

The following are the steps to follow:

 

Table of Contents

 

Designing the WSDL

We will take a scenario where exam results are published through a Web service, where students themselves or any other organization interested in finding out about student grades (for the purpose of awarding scholarships or opportunities for internships, lets say) would be able to access results without hassle.

Since we chose the contract-first approach, we first need to design the WSDL. Assume we name the service as "ExamResult" under the namespace "http://HightSchool.edu/". Then we have to choose the service operation and it's input and output messages.

Here is my operation:

ExamResult(studentName:String, subject:String, year:int, semester:int) : String

After you complete designing the service Interface, you should write the WSDL which is the standard way for representing a Web service's interface. Since it is a tedious job to be done by hand, we advice you choose from thousands of tools like Axis2 Java2WSDL[2] or WSF/PHP itself. For the service mentioned above, the WSDL should look like the following..

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:ns1="http://org.apache.axis2/xsd"
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:ns0="http://HighSchool.edu"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
targetNamespace="http://HighSchool.edu">
<wsdl:types> <xs:schema xmlns:ns="http://HighSchool.edu" xmlns:ns1="http://HighSchool.edu/xsd" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://HighSchool.edu"> <xs:element name="ExamResult"> <xs:complexType> <xs:sequence> <xs:element name="studentName" type="xs:string"/> <xs:element name="subject" type="xs:string"/> <xs:element name="year" type="xs:int"/> <xs:element name="semester" type="xs:int"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="ExamResultResponse"> <xs:complexType> <xs:sequence> <xs:element name="result" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> </wsdl:types> <wsdl:message name="ExamResultRequest"> <wsdl:part name="parameters" element="ns0:ExamResult"/> </wsdl:message> <wsdl:message name="ExamResultResponse"> <wsdl:part name="parameters" element="ns0:ExamResultResponse"/> </wsdl:message> <wsdl:portType name="ExamResultPortType"> <wsdl:operation name="ExamResult"> <wsdl:input message="ns0:ExamResultRequest" wsaw:Action="urn:ExamResult"/> <wsdl:output message="ns0:ExamResultResponse" wsaw:Action="urn:ExamResultResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="ExamResultSOAP12Binding" type="ns0:ExamResultPortType"> <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/> <wsdl:operation name="ExamResult"> <soap12:operation soapAction="urn:ExamResult" style="document"/> <wsdl:input> <soap12:body use="literal"/> </wsdl:input> <wsdl:output> <soap12:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="ExamResult"> <wsdl:port name="ExamResultSOAP12port_http" binding="ns0:ExamResultSOAP12Binding"> <soap12:address location="http://localhost:8080/axis2/services/ExamResult"/> </wsdl:port> </wsdl:service> </wsdl:definitions>

 
 

 

Explanation of the WSDL

If this is the fist time you are seeing a WSDL then the chance is that you will not understand anything at all. But if you look carefully, you will find that there are components that are human readable. Although this tutorial is not targeted to explaining WSDL in details, I will enlighten you on some crucial points that are needed to follow up the rest of the tutorial.

In the following section, I will explain how to extract operations defined in the WSDL and what input parameters are to be passed in order to build a proper request message.

First, in order to understand what the operations and argumentsnare, check 'wsdl:portType' element in the WSDL.

<wsdl:portType name="ExamResultPortType">
     <wsdl:operation name="ExamResult">
         <wsdl:input message="ns0:ExamResultRequest" wsaw:Action="urn:ExamResult"/>
         <wsdl:output message="ns0:ExamResultResponse" wsaw:Action="urn:ExamResultResponse"/>
     </wsdl:operation>
</wsdl:portType>

You can see here, that the operation named "ExamResult" is associated with an input message and an output message. In here, it does not reveal the format of any of the messages. In order to have an idea on the format, you will need to go one level up in the WSDL,There we meet the 'wsdl:message' element. There you will see, that it doesn't say anything particular other than referring to another layer above, which is the wsdl:types section. There the request message is referred to the schema element "n0:ExamResult" and the output message is referred to the element "ns0:ExamResultResponse" . This is the part of the WSDL that gives you the format of messages.

For an example, if you take "n0:ExamResult" schema element, it would be something like this,

          <xs:element name="ExamResult">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="studentName" type="xs:string"/>
                        <xs:element name="subject" type="xs:string"/>
                        <xs:element name="year" type="xs:int"/>
                        <xs:element name="semester" type="xs:int"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>

If you are still confused, I can show you an XML that is explained by the above schema element:

      <ExamResult xmlns="http://HighSchool.edu">
         <studentNamer>Hiro</studentNamer>
         <subject>Maths</subject>
         <year>2008</year>
         <semester>3</semester>
      </ExamResult>

Here "http://HighSchool.edu" is the namespace URI of all the elements in the XML. This comes from the 'targetNamespace' attribute defined in the XML Schema component of the WSDL. If you don't understand what it is for, do not worry, as you are not expected to worry about namespaces, when you write code with WSF/PHP. Instead what you need to worry about is, what elements it has and their types.

Here is a list of them:

studentName string
subject string
year int
semester int

This list is the same as the set of parameters you need to provide when you are calling the Web service operation. Then how about the set of parameters you have in your response. Similar to the request message, the format of the response is also defined in the schema. In this case it is "n0:ExamResultResponse".

 

           <xs:element name="ExamResultResponse">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="result" type="xs:string"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>

As you can see it only has one element named 'result' with the type 'string'.

Following figure explains the scenario I have explained abovee..

The Scenario description

 

Writing the Service

So now, we have an idea, as to what the operation name and what its input and output parameters are. We will now go straight into implementing the service. First, lets have a look at the code I wrote to provide this service.

<?php
/**
* Service.php
*/
/* This is where your logic is
Remeber in the WSDL we had "ExamResult" operation with name argument */
function ExamResult($studentName, $subject, $year, $semester) { $exam_results = array( "Maths" => array("Hiro" => "A+", "Clair" => "A+", "Peter" => "A+", "Mohinder" => "A+", "Ando" => "A", "Niki" => "B", "Sylar" => "F"), "Science" => array("Hiro" => "B+", "Clair" => "B+", "Peter" => "B+", "Mohinder" => "B+", "Ando" => "B", "Niki" => "C", "Sylar" => "-")); /* This is the default result */ $result = "-"; if($year != 2007 && $semester != 2) { $result = "-"; } else if($exam_results[$subject] != NULL && is_array($exam_results[$subject])) { $result = $exam_results[$subject][$studentName]; } /* Remember in the response we had 'result' element */ return array("result"=> $result); } /* Map of the service operation "ExamResult" to php function "ExamResult" */ $operations = array("ExamResult" => "ExamResult"); /* just tell your function parameters should be in mixed format,
that is here parameter will be the string with the name in it*/
$opParams = array("ExamResult" => "MIXED"); /* Created the WSService */ $svr = new WSService(array("wsdl" => "ExamResult.wsdl", "operations" => $operations, "opParams" => $opParams)); /* Reply the client */ $svr->reply()?>

Here, the ExamResult function is where you put your operational logic. It gives you the variables $studentName, $subject, $year and $semester filled with the values sent by the client. Since this code is written for the purpose of the tutorial, I'm keeping all results in an array. But in a more practical environment, results will be most probably held in a database, where you query results using the given parameters. Right at the end, I have assigned the result to the $result variable. Note how I return the result:

return array("result"=> $result);

Here, "result" is for the response XML element to wrap the actual result. (We have discussed this in an earlier section under the response schema).

When creating the WSService object, I have given three arguments within the constructor. They are,

"wsdl" location of the WSDL
"operations" this is a mapping of the service operation to the php function. for this example both are "ExamResult"
"opParams" This is to indicate that, in the php function which maps to the service operation (i.e. ExamResult function), arguments should be simple PHP types corresponding to the input message. if you do not explicitly set this, the argument to the function will be an object of WSMesssage.

In order to deploy the service, you may place the service script in the htdocs directory  in the Apache server. I have named my script 'service.php' and therefore my service endpoint is http://localhost/Service.php.

You don't need to restart the Apache server after you place this script.. Just go to the browser and type "http://localhost/Service.php". This will give you a list of services that are deployed already and their operations. Confirm your service listing.

 

Writing the Client

After you confirm that you have deployed your service succesfully, get ready to write a client for it. This would be to consume the Web service. Here also, we provide the WSDL to help WSF/PHP build the request message and parse the response message.

<?php

/* 
* Client.php
*/
/* create the WSClient with the given WSDL and my service endpoint
Note: Here im overwriting the endpoint declared in the WSDL */
$wsclient = new WSClient( array( "wsdl" => "ExamResult.wsdl",
"
to" => "http://localhost/Service.php")); /* we need to take the proxy object to call the wsdl operation */ $proxy = $wsclient->getProxy(); /* Right here I'm calling the ExamResult function with argument "Hiro"
Remeber in the WSDL we had "ExamResult" operation with name argument */
$ret_val = $proxy->ExamResult(array("studentName" => "Hiro", "subject" => "Maths", "year" => 2007, "semester" => "2")); /* Retrive the response. Just to recall, response had the element 'return' */ echo $ret_val["result"]."\n"; ?>

In the constructor for the WSClient, I have included both a WSDL location mapped to "wsdl" and a service endpoint mapped to "to". If your service endpoint is the same as the one in the WSDL, you can ignore the "to" setting. Check the location attribute in soap12:address element to check what the service endpoint defined in the WSDL is:

   <wsdl:service name="ExamResult">
        <wsdl:port name="ExamResultSOAP12port_http" binding="ns0:ExamResultSOAP12Binding">
            <soap12:address location="http://localhost:8080/axis2/services/ExamResult"/>
        </wsdl:port>
    </wsdl:service>

If you set "to" value in the WSClient constructor, this will override the service endpoint specified in the WSDL.

Then you access the proxy object from the $wsclient using getProxy() function. The $proxy is the object that allows you to invoke the service operation, just the same way you call a simple php function with the arguments that you provide to form the request message. The key "result" in the response Hash Map will return your final result.

This section demonstrated how you can write a simple client for a given WSDL. You used arrays to input request arguments and access response arguments, but, there is another way you can do the same thing. There, instead of arrays, you will be using php classes.

 

Writing the Client with Class Map API

Here the class you map to the input message will be,

class ExamResult
{
    public $name;
    public $subject;
    public $year;
    public $semester;
}

Note that you have declared public variables with the same names as the parameters of the input message. Similarly the output message will be mapped to the following class:

class ExamResultResponse
{
    public $result;
}

Here is the complete code for the same consumer using Class MAP API.

<?php
/* 
* Client_ClassMap.php
*/
/* Class ExamResult maps to the following request Message */ class ExamResult { public $name; public $subject; public $year; public $semester; } /* Class HelloWordResponse maps to the following response Message */ class ExamResultResponse { public $result; } /*
* Additionally you have to create the class map (i.e. Schema element => PHP Class Name */
$class_map = array("ExamResult"=> "ExamResult", "ExamResultResponse"=> "ExamResultResponse"); /* create the WSClient with the given WSDL and my service endpoint
Note: Here im overwriting the endpoint declared in the WSDL */
$wsclient = new WSClient( array( "classmap" => $class_map, "wsdl" => "ExamResult.wsdl",
"
to" => "http://localhost/Service.php")); /* we need to take the proxy object to call the wsdl operation */ $proxy = $wsclient->getProxy(); /* prepare the input */ $input = new ExamResult(); $input->studentName = "Hiro"; $input->subject = "Maths"; $input->year= "2007"; $input->semester = "2"; /* Right here I'm calling the ExamResult function with argument as the ExamResult instance
Remeber in the WSDL we had "ExamResult" operation with name argument */
$ret_val = $proxy->ExamResult($input); /* Retrive the response. Just to recall, our response is an instance of ExamResultResponse */ echo $ret_val->result."\n"; ?>

In order to tell WSF/PHP, that you are using class map API, you have to provide one more argument to the WSClient constructor. That is,

"classmap" Map of Schema Element to the PHP Class. (In this case the PHP Classes have to have the same names of the schema elements)

So, in contrast to the earlier case, you can see that you provide a class object filled with your request data to the function and that your output is  also a class object filled with response data. At a glance this may not seem like much of a difference with the earlier method, but, imagine you already have a class object filled data, then it would be really convenient to set that, as the input to the service - rather than explicitly converting that to a hash map. So class mapping gives you the flexibility to use PHP classess according to your very specific requirements.

 

Conclusion

This tutorial is a step by step guide to using WSO2 WSF/PHP API to build your services and consumers starting from a WSDL. (i.e. Contract-first approach). The tutorial introduces the WSDL mode API of the WSO2 Web Service Framework for PHP, and how to write your own Web services and clients with minimum effort using a given WSDL.

 

References

 

Author

Dimuthu Gamage is a Software Engineer at WSO2. He is a commiter of the Apache Web Services project and a developer of the WSF/PHP and WSF/Ruby projects. dimuthu at wso2 dot com.

WSO2Con 2014 USA