Web service is a software system designed to support interoperable machine-to-machine interaction over a network. In the context of Web applications, it usually refers to a set of APIs that can be accessed over the Internet and executed on a remote system hosting the requested service. For example, a Flex-based client may invoke a function implemented on the server side running a PHP-based Web application. Web service relies on SOAP as its foundation layer of the communication protocol stack.
Yii provides CWebService and CWebServiceAction to simplify the work of implementing Web service in a Web application. The APIs are grouped into classes, called service providers. Yii will generate for each class a WSDL specification which describes what APIs are available and how they should be invoked by client. When an API is invoked by a client, Yii will instantiate the corresponding service provider and call the requested API to fulfill the request.
Note: CWebService relies on the PHP SOAP extension. Make sure you have enabled it before trying the examples displayed in this section.
As we mentioned above, a service provider is a class defining the methods that can be remotely invoked. Yii relies on doc comment and class reflection to identify which methods can be remotely invoked and what are their parameters and return value.
Let's start with a simple stock quoting service. This service allows a
client to request for the quote of the specified stock. We define the
service provider as follows. Note that we define the provider class
StockController
by extending CController. This is not required.
class StockController extends CController
{
/**
* @param string the symbol of the stock
* @return float the stock price
* @soap
*/
public function getPrice($symbol)
{
$prices=array('IBM'=>100, 'GOOGLE'=>350);
return isset($prices[$symbol])?$prices[$symbol]:0;
//...return stock price for $symbol
}
}
In the above, we declare the method getPrice
to be a Web service API by
marking it with the tag @soap
in its doc comment. We rely on doc comment
to specify the data type of the input parameters and return value.
Additional APIs can be declared in the similar way.
Having defined the service provider, we need to make it available to
clients. In particular, we want to create a controller action to expose the
service. This can be done easily by declaring a CWebServiceAction action
in a controller class. For our example, we will just put it in
StockController
.
class StockController extends CController
{
public function actions()
{
return array(
'quote'=>array(
'class'=>'CWebServiceAction',
),
);
}
/**
* @param string the symbol of the stock
* @return float the stock price
* @soap
*/
public function getPrice($symbol)
{
//...return stock price for $symbol
}
}
That is all we need to create a Web service! If we try to access the
action by URL http://hostname/path/to/index.php?r=stock/quote
, we will
see a lot of XML content which is actually the WSDL for the Web service we
defined.
Tip: By default, CWebServiceAction assumes the current controller is the service provider. That is why we define the
getPrice
method inside theStockController
class.
To complete the example, let's create a client to consume the Web service
we just created. The example client is written in PHP, but it could be in
other languages, such as Java
, C#
, Flex
, etc.
$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote');
echo $client->getPrice('GOOGLE');
Run the above script in either Web or console mode, and we shall see 350
which is the price for GOOGLE
.
When declaring class methods and properties to be remotely accessible, we need to specify the data types of the input and output parameters. The following primitive data types can be used:
xsd:string
;xsd:int
;xsd:float
;xsd:boolean
;xsd:date
;xsd:time
;xsd:dateTime
;xsd:string
;xsd:struct
;xsd:anyType
.If a type is not any of the above primitive types, it is considered as a
composite type consisting of properties. A composite type is represented in
terms of a class, and its properties are the class' public member variables
marked with @soap
in their doc comments.
We can also use array type by appending []
to the end of a primitive or
composite type. This would specify an array of the specified type.
Below is an example defining the getPosts
Web API which returns an array
of Post
objects.
class PostController extends CController
{
/**
* @return Post[] a list of posts
* @soap
*/
public function getPosts()
{
return Post::model()->findAll();
}
}
class Post extends CActiveRecord
{
/**
* @var integer post ID
* @soap
*/
public $id;
/**
* @var string post title
* @soap
*/
public $title;
public static function model($className=__CLASS__)
{
return parent::model($className);
}
}
In order to receive parameters of composite type from client, an application needs to declare the mapping from WSDL types to the corresponding PHP classes. This is done by configuring the classMap property of CWebServiceAction.
class PostController extends CController
{
public function actions()
{
return array(
'service'=>array(
'class'=>'CWebServiceAction',
'classMap'=>array(
'Post'=>'Post', // or simply 'Post'
),
),
);
}
......
}
By implementing the IWebServiceProvider interface, a service provider can intercept remote method invocations. In IWebServiceProvider::beforeWebMethod, the provider may retrieve the current CWebService instance and obtain the name of the method currently being requested via CWebService::methodName. It can return false if the remote method should not be invoked for some reason (e.g. unauthorized access).
Found a typo or you think this page needs improvement?
Edit it on github !
hosting not supporting SOAP
If your host is not supporting SOAP, you can use a controller instead.
You can use an action for each function and encpsulate the result with json:
/** * @param string the symbol of the stock * @return float the stock price */ public function actionGetPrice($symbol) { CJSON::encode($symbol); }
This is an acceptable workaround. Of corse there are no type check, and the result is not in xml but a JSON encoded, but this system at least allow 2 application to collaborate.
Consuming Web Service
if you have enabled the URL manager in config/main.php like this:
... 'urlManager'=>array( 'urlFormat'=>'path', 'rules'=>array( '<controller:\w+>/<id:\d+>'=>'<controller>/view', '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>', '<controller:\w+>/<action:\w+>'=>'<controller>/<action>', ...
then you need to write the SOAP URL like this:
$client=new SoapClient('http://hostname/path/to/index.php/stock/quote'); // ^^^^ echo $client->getPrice('GOOGLE');
Hope this helps.
SOAP partially works (when enableCsrfValidation is true)
I used the example as specified in this page.
The http://hostname/path/to/index.php/stock/quote showed me the xml, but I failed to invoke the getPrice operation.
When examining the result I saw that the result contained:
Error 400...
The CSRF token could not be verified.
This was because enableCsrfValidation was set to true in /protected/config/main.php:
'request'=>array( 'enableCookieValidation'=>true, ),
Here is a solution for this problem.
Thanks to Angel De La Noche for pointing me there.
Function ("getNewFunction") is not a valid method for this service
if you use the main wiki example then all is working fine, but if you add a new function on Ws controller then on client side this error is shown:
Function ("getNewFunction") is not a valid method for this service
i found the solution on this post but is not in english, so, here is the solution in english language:
to avoid this error when new function were added to the web service then add
this two lines on your client side ws consumer (before you call "new SoapClient.."):
ini_set ( 'soap.wsdl_cache_enable' , 0 );
ini_set ( 'soap.wsdl_cache_ttl' , 0 );
XML Parsing Error: junk after document element
if you have those errors:
XML Parsing Error: junk after document element
Location: http://127.0.0.1/index.php?r=api/quote
Line Number 35, Column 1:
You should comment debug mode in index.php
//defined('YII_DEBUG') or define('YII_DEBUG',true);
Signup or Login in order to comment.