HOW TO CREATE A PHP SOAP WEB SERVICE WITH NESTED COMPLEX TYPES AND COMPLEX TYPE ARRAYS

PLEASE SHARE

This blog post explains how to develop a PHP SOAP web service that produces nested complex types and complex type array as a response.

We will be using NuSOAP API to develop this SOAP service.

The demo PHP SOAP web service we are going to develop will provide information about a company stock such as daily values, CEO info, and last 3-year financials as a complex type.

In object-oriented programming terms, we can say a SOAP Complex Type is nothing but a Class. Before jumping into the code, Let’s have a look at the sample Request/Response messages of our demo web service and their corresponding class diagrams for better understanding.

SOAP REQUEST WITH COMPLEX TYPE

The demo service will accept an object of the class “StockInfoRequest” as part of the request. Here’s the class diagram representing the same. As we can see the “StockInfoRequest” is a simple class having 2 fields,

Here’s the sample SOAP request message reflecting the above class diagram,

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header/>
   <soapenv:Body>
      <getStock soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <symbol xsi:type="get:StockInfoRequest" xmlns:get="https://thedeveloperfriend.com/stock_soap_ws/GetStockService.php?WSDL">
            <!--You may enter the following 2 items in any order-->
            <symbol xsi:type="xsd:string">MSFT</symbol>
            <tradingDate xsi:type="xsd:string">01-01-2020</tradingDate>
         </symbol>
      </getStock>
   </soapenv:Body>
</soapenv:Envelope>

SOAP RESPONSE WITH COMPLEX TYPE

Our web service response will consist of 3 classes. The Stock class will be used for sending the web service response. The Stock class is having associations with CEO and YearlyFinancial classes. As per the diagram below, the Stock class is having a one-to-one relationship with the CEO class. So we can say the CEO is nested inside Stock.

The Stock class is also having a one-on-many relationship with the YearlyFinancial class. So a Stock can have a reference to an array or list of YearlyFinancial classes.

Here’s the sample SOAP response message reflecting the above class diagram,

<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="https://thedeveloperfriend.com/stock_soap_ws/GetStockService.php?WSDL">
   <SOAP-ENV:Body>
      <ns1:getStockResponse xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/">
         <return xsi:type="tns:Stock">
            <stockId xsi:type="xsd:int">1</stockId>
            <symbol xsi:type="xsd:string">MSFT</symbol>
            <open xsi:type="xsd:decimal">179.5</open>
            <high xsi:type="xsd:decimal">180</high>
            <low xsi:type="xsd:decimal">177</low>
            <close xsi:type="xsd:decimal">178.68</close>
            <ceo xsi:type="tns:CEO">
               <ceoId xsi:type="xsd:int">1</ceoId>
               <name xsi:type="xsd:string">Satya Nadella</name>
               <salary xsi:type="xsd:decimal">13.24</salary>
               <age xsi:type="xsd:int">50</age>
            </ceo>
            <lastThreeYearFinancial xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType=":[3]">
               <item xsi:type="xsd:">
                  <year xsi:type="xsd:int">2019</year>
                  <grossRevenue xsi:type="xsd:float">5.5</grossRevenue>
                  <netRevenue xsi:type="xsd:float">4.4</netRevenue>
               </item>
               <item xsi:type="xsd:">
                  <year xsi:type="xsd:int">2018</year>
                  <grossRevenue xsi:type="xsd:float">6.5</grossRevenue>
                  <netRevenue xsi:type="xsd:float">5</netRevenue>
               </item>
               <item xsi:type="xsd:">
                  <year xsi:type="xsd:int">2017</year>
                  <grossRevenue xsi:type="xsd:float">5</grossRevenue>
                  <netRevenue xsi:type="xsd:float">4</netRevenue>
               </item>
            </lastThreeYearFinancial>
         </return>
      </ns1:getStockResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

CODING THE DEMO PHP SOAP WEB SERVICE

Let’s start coding the demo web service we discussed above. First, we need to create the classes required to build our web service request and response.

Stock Class

This is the main class that will be sent as a web service response. This class has references for CEO and YearlyFinancial classes.

class Stock{
	
	public $stockId;
	public $symbol;
	public $open;
	public $high;
	public $low;
	public $close;
	public $ceo; // Reference for CEO object
	public $lastThreeYearFinancial; // Reference for array of YearlyFinancial objects
		
	function set_stockId($stockId) {$this->stockId = $stockId;}
	function get_stockId() {return $this->stockId;}
	
	function set_symbol($symbol) {$this->symbol = $symbol;}
	function get_symbol() {return $this->symbol;}
	
	function set_open($open) {$this->open = $open;}
	function get_open() {return $this->open;}
	
	function set_high($high) {$this->high = $high;}
	function get_high() {return $this->high;}
	
	function set_low($low) {$this->low = $low;}
	function get_low() {return $this->low;}
	
	function set_close($close) {$this->close = $close;}
	function get_close() {return $this->close;}
	
	function set_ceo($ceo) {$this->ceo = $ceo;} //Accepts CEO object as a parameter
	function get_ceo() {return $this->ceo;}// Returns CEO object
	
	//Accepts array of  YearlyFinancial objects as a parameter
	function set_lastThreeYearFinancial($lastThreeYearFinancial) {$this->lastThreeYearFinancial = $lastThreeYearFinancial;}
	function get_lastThreeYearFinancial() {return $this->lastThreeYearFinancial;} //Returns array of  YearlyFinancial objects
	
}

CEO Class

This CEO class is referenced in the Stock class. So we can say that the CEO class is nested inside the Stock class.

class CEO{
	public $ceoId;
	public $name;
	public $salary;
	public $age;
	
	function set_ceoId($ceoId) {$this->ceoId = $ceoId;}
	function get_ceoId() {return $this->ceoId;}
	
	function set_name($name) {$this->name = $name;}
	function get_name() {return $this->name;}
	
	function set_salary($salary) {$this->salary = $salary;}
	function get_salary() {return $this->salary;}
	
	function set_age($age) {$this->age = $age;}
	function get_age() {return $this->age;}
	
}

YearlyFinancial Class

The Stock class has a reference to an array of YearlyFinancial objects. Stock and YearlyFinancial classes have a one-to-many relationship.

class YearlyFinancial{
	
	public $year;
	public $grossRevenue;
	public $netRevenue;
	
	function set_year($year) {$this->year = $year;}
	function get_year() {return $this->year;}
	
	function set_grossRevenue($grossRevenue) {$this->grossRevenue = $grossRevenue;}
	function get_grossRevenue() {return $this->grossRevenue;}
	
	function set_netRevenue($netRevenue) {$this->netRevenue = $netRevenue;}
	function get_netRevenue() {return $this->netRevenue;}
	
}

StockInfoRequest Class

This class represents web service requests. Any client calling this web service should pass this object.

class StockInfoRequest{
	public $symbol;
	public $tradingDate;
	
	function set_symbol($symbol) {$this->symbol = $symbol;}
	function get_symbol() {return $this->symbol;}
	
	function set_tradingDate($tradingDate) {$this->tradingDate = $tradingDate;}
	function get_tradingDate() {return $this->tradingDate;}
}

BUILDING RESPONSE OBJECTS WITH DUMMY DATA

The following methods will generate the dummy data required for our web service response,

// this method returns the array of objects of YearlyFinancial class
function getYearlyFinancials(){
	
	$arrayOfYearlyFinancialArray = array();
	
	$yearlyFinancial_1 = new YearlyFinancial();
	$yearlyFinancial_1->set_year(2019);
	$yearlyFinancial_1->set_grossRevenue(5.5);
	$yearlyFinancial_1->set_netRevenue(4.4);
	array_push($arrayOfYearlyFinancialArray, $yearlyFinancial_1);
	
	$yearlyFinancial_2 = new YearlyFinancial();
	$yearlyFinancial_2->set_year(2018);
	$yearlyFinancial_2->set_grossRevenue(6.5);
	$yearlyFinancial_2->set_netRevenue(5.0);
	array_push($arrayOfYearlyFinancialArray, $yearlyFinancial_2);
	
	$yearlyFinancial_3 = new YearlyFinancial();
	$yearlyFinancial_3->set_year(2017);
	$yearlyFinancial_3->set_grossRevenue(5.0);
	$yearlyFinancial_3->set_netRevenue(4.0);
	array_push($arrayOfYearlyFinancialArray, $yearlyFinancial_3);
	
	return $arrayOfYearlyFinancialArray;
	
}

function getCEO($ceoId){
	
	$ceo = new CEO();
	$ceo->set_ceoId(1);
	$ceo->set_name('Satya Nadella');
	$ceo->set_salary(13.24);
	$ceo->set_age(50);
	return $ceo;
	
}

//This method returns the Stock object that will be sent as a web service response.
function getStock($symbol){
	
	$stock = new Stock();
	$stock->set_stockId(1);
	$stock->set_symbol('MSFT');
	$stock->set_open(179.50);
	$stock->set_high(180.00);
	$stock->set_low(177.00);
	$stock->set_close(178.68);
	$stock->set_ceo(getCEO(1)); //Here we attach an object of CEO class to Stock
	$stock->set_lastThreeYearFinancial(getYearlyFinancials()); //Here we attach an array of YearlyFinancial objects to Stock
	return $stock;
	
}

INITIALIZING PHP SOAP WEB SERVICE

The following lines of code will initialize the web service (Please change the hostname & directory if required),

$server = new soap_server();
$namespace = 'http://localhost/stock_soap_ws/GetStockService.php?WSDL';
$server->configureWSDL('GetStockService', $namespace);

ADDING COMPLEX TYPES FOR THE CLASSES

We need to add complex types for each of the classes we discussed above.

Add a complex type that represents the CEO class,

$server->wsdl->addComplexType('CEO','complexType','struct','all','',
array(
	'ceoId' => array('name' => 'ceoId','type' => 'xsd:int'),
	'name' => array('name' => 'name','type' => 'xsd:string'),
	'salary' => array('name' => 'salary','type' => 'xsd:decimal'),
	'age' => array('name' => 'age','type' => 'xsd:int')
	)
);

Add a complex type that represents the YearlyFinancial class,

$server->wsdl->addComplexType('YearlyFinancial','complexType','struct','all','',
array(
	'year' => array('name' => 'year','type' => 'xsd:int'),
	'grossRevenue' => array('name' => 'grossRevenue','type' => 'xsd:decimal'),
	'netRevenue' => array('name' => 'netRevenue','type' => 'xsd:decimal')
	)
);

We are now adding a complex type that represents an Array of YearlyFinancial type created above. The array data type is denoted as “tns:YearlyFinancial[]

$server->wsdl->addComplexType('ArrayOfYearlyFinancial','complexType','array','','SOAP-ENC:Array',
        array(),
        array(
            array(
                'ref' => 'SOAP-ENC:arrayType',
                'wsdl:arrayType' => 'tns:YearlyFinancial[]'
            )
        )
);

We are now adding a complex type for the Stock class that will be sent as a response. Note that this complex type has references for other complex types such as CEO and ArrayOfYearlyFinancial

$server->wsdl->addComplexType('Stock','complexType','struct','all','',
array(
	'stockId' => array('name' => 'stockId','type' => 'xsd:int'),
	'symbol' => array('name' => 'symbol','type' => 'xsd:string'),
	'open' => array('name' => 'open','type' => 'xsd:decimal'),
	'high' => array('name' => 'high','type' => 'xsd:decimal'),
	'low' => array('name' => 'low','type' => 'xsd:decimal'),
	'close' => array('name' => 'close','type' => 'xsd:decimal'),
	'ceo' => array('name' => 'ceo','type' => 'tns:CEO'), //Entry for CEO Complex Type
	'lastThreeYearFinancial' => array('name' => 'lastThreeYearFinancial','type' => 'tns:ArrayOfYearlyFinancial')//Entry for ArrayOfYearlyFinancial Complex Type
	)
);


Finally, we need to add a complex type to the class StockInfoRequest. This will be used for sending web service requests.

$server->wsdl->addComplexType('StockInfoRequest','complexType','struct','all','',
array(
	'symbol' => array('name' => 'symbol','type' => 'xsd:string'),
	'tradingDate' => array('name' => 'tradingDate','type' => 'xsd:string')
	)
);

Here we register the request and response objects,

$server->register("getStock",
	array('symbol' => 'tns:StockInfoRequest'), //Request Object Type
    array('return' => 'tns:Stock') //Response Object Type
);

Start the web service with the following code,

if ( !isset( $HTTP_RAW_POST_DATA ) ) $HTTP_RAW_POST_DATA =file_get_contents( 'php://input' );
$server->service($HTTP_RAW_POST_DATA);

COMPLETE CODE

Here’s the complete code,

GetStockService.php

<?php
/*
This code demonstrates how to create a SOAP web service that will return nested complex data types and an array of complex data types. 
This demo web service will return information about a stock such as daily values, CEO info and last 3 year financials.
*/
require_once "lib/nusoap.php";


/*
This is the main class Stock which will be sent as a web service response.
This class has references for CEO and YearlyFinancial classes
*/
class Stock{
	
	public $stockId;
	public $symbol;
	public $open;
	public $high;
	public $low;
	public $close;
	public $ceo; // Reference for CEO object
	public $lastThreeYearFinancial; // Reference for array of YearlyFinancial objects
		
	function set_stockId($stockId) {$this->stockId = $stockId;}
	function get_stockId() {return $this->stockId;}
	
	function set_symbol($symbol) {$this->symbol = $symbol;}
	function get_symbol() {return $this->symbol;}
	
	function set_open($open) {$this->open = $open;}
	function get_open() {return $this->open;}
	
	function set_high($high) {$this->high = $high;}
	function get_high() {return $this->high;}
	
	function set_low($low) {$this->low = $low;}
	function get_low() {return $this->low;}
	
	function set_close($close) {$this->close = $close;}
	function get_close() {return $this->close;}
	
	function set_ceo($ceo) {$this->ceo = $ceo;} //Accepts CEO object as a parameter
	function get_ceo() {return $this->ceo;}// Returns CEO object
	
	//Accepts array of  YearlyFinancial objects as a parameter
	function set_lastThreeYearFinancial($lastThreeYearFinancial) {$this->lastThreeYearFinancial = $lastThreeYearFinancial;}
	function get_lastThreeYearFinancial() {return $this->lastThreeYearFinancial;} //Returns array of  YearlyFinancial objects
	
}

//This CEO class is referenced in Stock class.So we can say CEO class is nested inside Stock class.
class CEO{
	public $ceoId;
	public $name;
	public $salary;
	public $age;
	
	function set_ceoId($ceoId) {$this->ceoId = $ceoId;}
	function get_ceoId() {return $this->ceoId;}
	
	function set_name($name) {$this->name = $name;}
	function get_name() {return $this->name;}
	
	function set_salary($salary) {$this->salary = $salary;}
	function get_salary() {return $this->salary;}
	
	function set_age($age) {$this->age = $age;}
	function get_age() {return $this->age;}
	
}
/*
Stock class is having a reference to an array of YearlyFinancial objects. 
So Stock and YearlyFinancial classes have a one-to-many relationship.
*/
class YearlyFinancial{
	
	public $year;
	public $grossRevenue;
	public $netRevenue;
	
	function set_year($year) {$this->year = $year;}
	function get_year() {return $this->year;}
	
	function set_grossRevenue($grossRevenue) {$this->grossRevenue = $grossRevenue;}
	function get_grossRevenue() {return $this->grossRevenue;}
	
	function set_netRevenue($netRevenue) {$this->netRevenue = $netRevenue;}
	function get_netRevenue() {return $this->netRevenue;}
	
}

//This class represents web service request
class StockInfoRequest{
	public $symbol;
	public $tradingDate;
	
	function set_symbol($symbol) {$this->symbol = $symbol;}
	function get_symbol() {return $this->symbol;}
	
	function set_tradingDate($tradingDate) {$this->tradingDate = $tradingDate;}
	function get_tradingDate() {return $this->tradingDate;}
}

// this method returns the array of objects of YearlyFinancial class
function getYearlyFinancials(){
	
	$arrayOfYearlyFinancialArray = array();
	
	$yearlyFinancial_1 = new YearlyFinancial();
	$yearlyFinancial_1->set_year(2019);
	$yearlyFinancial_1->set_grossRevenue(5.5);
	$yearlyFinancial_1->set_netRevenue(4.4);
	array_push($arrayOfYearlyFinancialArray, $yearlyFinancial_1);
	
	$yearlyFinancial_2 = new YearlyFinancial();
	$yearlyFinancial_2->set_year(2018);
	$yearlyFinancial_2->set_grossRevenue(6.5);
	$yearlyFinancial_2->set_netRevenue(5.0);
	array_push($arrayOfYearlyFinancialArray, $yearlyFinancial_2);
	
	$yearlyFinancial_3 = new YearlyFinancial();
	$yearlyFinancial_3->set_year(2017);
	$yearlyFinancial_3->set_grossRevenue(5.0);
	$yearlyFinancial_3->set_netRevenue(4.0);
	array_push($arrayOfYearlyFinancialArray, $yearlyFinancial_3);
	
	return $arrayOfYearlyFinancialArray;
	
}

function getCEO($ceoId){
	
	$ceo = new CEO();
	$ceo->set_ceoId(1);
	$ceo->set_name('Satya Nadella');
	$ceo->set_salary(13.24);
	$ceo->set_age(50);
	return $ceo;
	
}

//This method returns the Stock object that will be sent as a web service response.
function getStock($symbol){
	
	$stock = new Stock();
	$stock->set_stockId(1);
	$stock->set_symbol('MSFT');
	$stock->set_open(179.50);
	$stock->set_high(180.00);
	$stock->set_low(177.00);
	$stock->set_close(178.68);
	$stock->set_ceo(getCEO(1)); //Here we attach an object of CEO class to Stock
	$stock->set_lastThreeYearFinancial(getYearlyFinancials()); //Here we attach an array of YearlyFinancial objects to Stock
	return $stock;
	
}

//Initialize SOAP service with WSDL ...
$server = new soap_server();
$namespace = 'http://localhost/stock_soap_ws/GetStockService.php?WSDL';
$server->configureWSDL('GetStockService', $namespace);

//Adding a complex type that represents the CEO class
$server->wsdl->addComplexType('CEO','complexType','struct','all','',
array(
	'ceoId' => array('name' => 'ceoId','type' => 'xsd:int'),
	'name' => array('name' => 'name','type' => 'xsd:string'),
	'salary' => array('name' => 'salary','type' => 'xsd:decimal'),
	'age' => array('name' => 'age','type' => 'xsd:int')
	)
);

//Adding a complex type that represents the YearlyFinancial class
$server->wsdl->addComplexType('YearlyFinancial','complexType','struct','all','',
array(
	'year' => array('name' => 'year','type' => 'xsd:int'),
	'grossRevenue' => array('name' => 'grossRevenue','type' => 'xsd:decimal'),
	'netRevenue' => array('name' => 'netRevenue','type' => 'xsd:decimal')
	)
);

//Adding a complex type that represents an Array of YearlyFinancial objetcs
$server->wsdl->addComplexType('ArrayOfYearlyFinancial','complexType','array','','SOAP-ENC:Array',
        array(),
        array(
            array(
                'ref' => 'SOAP-ENC:arrayType',
                'wsdl:arrayType' => 'tns:YearlyFinancial[]'
            )
        )
);

//Adding a complex type that represents the Stock class
$server->wsdl->addComplexType('Stock','complexType','struct','all','',
array(
	'stockId' => array('name' => 'stockId','type' => 'xsd:int'),
	'symbol' => array('name' => 'symbol','type' => 'xsd:string'),
	'open' => array('name' => 'open','type' => 'xsd:decimal'),
	'high' => array('name' => 'high','type' => 'xsd:decimal'),
	'low' => array('name' => 'low','type' => 'xsd:decimal'),
	'close' => array('name' => 'close','type' => 'xsd:decimal'),
	'ceo' => array('name' => 'ceo','type' => 'tns:CEO'), //Entry for CEO Complex Type
	'lastThreeYearFinancial' => array('name' => 'lastThreeYearFinancial','type' => 'tns:ArrayOfYearlyFinancial')//Entry for ArrayOfYearlyFinancial Complex Type
	)
);

//Adding a complex type that represents the StockInfoRequest class (for web service request)
$server->wsdl->addComplexType('StockInfoRequest','complexType','struct','all','',
array(
	'symbol' => array('name' => 'symbol','type' => 'xsd:string'),
	'tradingDate' => array('name' => 'tradingDate','type' => 'xsd:string')
	)
);

//Register the web service operation
$server->register("getStock",
	array('symbol' => 'tns:StockInfoRequest'), //Request Object Type
    array('return' => 'tns:Stock') //Response Object Type
);

if ( !isset( $HTTP_RAW_POST_DATA ) ) $HTTP_RAW_POST_DATA =file_get_contents( 'php://input' );
$server->service($HTTP_RAW_POST_DATA);


?>

DEMO SOAP WEB SERVICE ONLINE

The demo SOAP web service we developed here can be accessed online. Here’s the WSDL URL,
https://thedeveloperfriend.com/stock_soap_ws/GetStockService.php?wsdl

PROJECT ON GITHUB

This demo project can be downloaded from this GitHub repository,
https://github.com/rajeshkumarraj82/php_soap_web_service_with_nested_complex_types_and_complex_type_arrays

AN ISSUE WITH NUSOAP

When I was first trying to request the web service i got an error,

Recoverable fatal error:  Object of class YearlyFinancial could not be converted to string in C:\xampp\htdocs\stock_soap_ws\lib\nusoap.php on line 6132

This was caused by a debug log in the nusoap.php. So I commented the following line in nusoap.php (Line No = 6132)

//$this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");

CONSUMING THE DEMO WEB SERVICE

If you are interested to read about how to consume the demo web service we developed in this post, please visit https://www.thedeveloperfriend.com/soap-web-services/consuming-a-soap-web-service-having-complex-types-in-php-using-wsdl2phpgenerator-tool/

Thank you for reading this post. Please do let me know if you have any queries or suggestions.

Follow Me

Leave a Reply

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

+ 25 = 35