I’ve been using the Zend for the last few months and I’m loving it. Here at work we were having a discussion about implementing some web services in the future so I decided to see what it takes to create some web services in PHP using Zend. I was pleasantly surprise (well not really surprised) that it was extremely quick and easy to get things up and running.
In this post, I’m going to explain how to create web services that can be accessed via AMF, XMLRPC, JSON, and REST using the Zend Framework. Hopefully these should cover most uses out there. I left SOAP out since I don’t really see myself using it any time soon. I will be communicating with a database as well to make the tutorial more informative. The db will consist of one table with information about courses such as mathematics courses.
I’m going to show you the final product first, hopefully to catch your attention so you’ll read the rest :). This is what the final product will look like. If you look at the following links in Chrome, do a view source to see the formatted response.
XMLRPC
One course: http://www.joeyrivera.com/ZendWebServices/xml-rpc/course-info/abbr/math101/
All courses: http://www.joeyrivera.com/ZendWebServices/xml-rpc/courses-info/
JSON
One course: http://www.joeyrivera.com/ZendWebServices/json/course-info/abbr/math101/
All courses: http://www.joeyrivera.com/ZendWebServices/json/courses-info/
REST
One course: http://www.joeyrivera.com/ZendWebServices/rest/course-info/?method=getCourseInfo&abbr=math102
All courses: http://www.joeyrivera.com/ZendWebServices/rest/courses-info/?method=getCoursesInfo
AMF
One course: http://www.joeyrivera.com/ZendWebServices/amf/course-info/abbr/math101
All courses: http://www.joeyrivera.com/ZendWebServices/amf/courses-info/
Now let me show you how to make this work. First thing is setting up the database. If you already have a database you can communicate with just use that, there’s nothing specific to the database I’ll be using that requires you to create it. But if you want to follow along here are the details. I’ll be creating a database called ‘sample’ with a table called ‘course’.
DROP TABLE IF EXISTS `sample`.`course`;
CREATE TABLE `sample`.`course` (
`course_id` int(10) unsigned NOT NULL auto_increment,
`abbr` varchar(10) NOT NULL,
`name` varchar(45) NOT NULL,
`title` varchar(45) NOT NULL,
`credits` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`course_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
Now that you have the table created, add some sample data:
INSERT INTO `course` VALUES
(null,'math101','Math 101','Beginner Mathematics','3'),
(null,'math102','Math 102','Advanced Mathematics','4');
Now the code. I have one Model class called Service. This is the only class that interacts with the database. For each format such as XMLRPC, JSON, etc. I created a Controller to handle that formats needs. All these controllers initialize a Zend server class if needed, call the method needed from the Service class, and return the formatted response to the browser.
This is what the Service class looks like:
<?php
/**
* Class with all the calls to the db
* IMPORTANT: The methods needs comments with params and return for XMLRPC to work!!!
*
* @author Joey Rivera
*/
class Service
{
/**
* Get all courses
*
* @return array
*/
public function getCoursesInfo()
{
return Zend_Registry::get('db')->fetchAll('select * from course');
}
/**
* Gets info for a course
*
* @param string $abbr
* @return array
*/
public function getCourseInfo($abbr)
{
return Zend_Registry::get('db')->fetchRow("select * from course where abbr = '$abbr'");
}
}
Two methods make up this class. ‘getCoursesInfo’ (plural) gets the information from all the courses in the ‘course’ table. ‘getCourseInfo’ takes in one parameter ‘abbr’ and returns the information for the course who’s abbreviation matches ‘abbr’. You can create as many methods as you like here and these are the methods your controllers will call. One thing to note, you MUST add comments to the methods for XMLRPC calls to work. It uses the variable declaration and return declaration in your comments to know what data type to expect in and out.
Now lets move on to the first format: XMLRPC.
<?php
/**
* XmlRpcController
*
* @author Joey Rivera
*/
class XmlRpcController extends Zend_Controller_Action
{
protected $_server = null;
protected $_request = null;
public function init()
{
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
$this->_server = new Zend_XmlRpc_Server();
$this->_server->setClass('Service', 'Service');
}
public function postDispatch()
{
echo $this->_server->handle($this->_request);
}
public function coursesInfoAction()
{
$this->_request = new Zend_XmlRpc_Request('Service.getCoursesInfo');
}
public function courseInfoAction()
{
$abbr = Zend_Filter::get($this->_request->getParam('abbr'), 'StripTags');
if(!isset($abbr[1])) exit;
$this->_request = new Zend_XmlRpc_Request('Service.getCourseInfo');
$this->_request->setParams(array($abbr));
}
}
The init function gets called for any action in the controller. I am turning off the layout and view since I won’t be using them for these examples. Then I initialize the proper zend server class, in this case the ‘Zend_XmlRpc_Server’ class. The next line ‘setClass’ tells the server what classes will be handling incoming requests. You can add several classes and/or functions. For this sample, I’m simply attaching one class ‘Service’ which I described earlier. ‘postDispatch’ gets called after the action for the controller is done executing. In my ‘postDispatch’ I tell the server instance to handle the incoming request.
‘coursesInfoAction’ creates a request object that calls the Service class ‘getCoursesInfo’ (plural) method. Likewise, ‘courseInfoAction’ does the same calling ‘getCourseInfo’ (singular) but makes sure the abbr variable is set. Which ever of these two is called, a server instance gets initialized, the request variable is populated, then the server handle method is called so the request is returned to the browser with the correct format. I’m doing request objects here so I can easily test and make sure these methods are working correctly by just loading the url.
All the other controller classes look very similar, each initializing a server instance if necessary and then telling the server instance to display the results correctly.
Here’s the JSON controller:
<?php
/**
* JsonController
*
* @author Joey Rivera
*/
class JsonController extends Zend_Controller_Action
{
protected $_return = null;
public function init()
{
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
}
public function postDispatch()
{
echo Zend_Json::encode($this->_return);
}
public function coursesInfoAction()
{
$request = new Service();
$this->_return = $request->getCoursesInfo();
}
public function courseInfoAction()
{
$abbr = Zend_Filter::get($this->_request->getParam('abbr'), 'StripTags');
if(!isset($abbr[1])) exit;
$request = new Service();
$this->_return = $request->getCourseInfo($abbr);
}
}
You’ll notice there is no JSON server class. All we are doing here is grabbing the data set from the Service class and calling Zend’s Zend_Json::encode to convert the data into a properly formatted JSON string and echoing it out.
Here you can see the implementation for REST:
<?php
/**
* RestController
*
* @author Joey Rivera
*/
class RestController extends Zend_Controller_Action
{
protected $_server = null;
protected $_request = null;
public function init()
{
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
$this->_server = new Zend_Rest_Server();
$this->_server->setClass('Service', 'Service');
}
public function postDispatch()
{
$this->_server->handle();
}
public function coursesInfoAction()
{
}
public function courseInfoAction()
{
$abbr = Zend_Filter::get($this->_request->getParam('abbr'), 'StripTags');
if(!isset($abbr[1])) exit;
}
}
Starting to look repetitive? Once you figure out how to make one format work, the others are very similar. Part of using REST is passing a method parameter in the query string. The method you pass is the method of the service that will be called. This is why coursesInfo and courseInfo don’t do anything but validate if necessary.
Finally if you work with Flash or Flex like I do some times, you’ll want to use AMF if you are working with complex or large data sets. Here’s the AMF example:
<?php
/**
* AmfController
*
* @author Joey Rivera
*/
class AmfController extends Zend_Controller_Action
{
protected $_server = null;
protected $_request = null;
public function init()
{
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
$this->_server = new Zend_Amf_Server();
$this->_server->setClass('Service');
}
public function postDispatch()
{
echo $this->_server->handle();
}
public function coursesInfoAction()
{
}
public function courseInfoAction()
{
}
}
If you try to access the url for amf in your browser you’ll be prompted to download a file because AMF is not a text format but a binary format that Flash and Flex read. You’ll just have to take my word on that it’s working :p or create a quick Flash/Flex app that calls this service.
This is pretty much all I have for you all today. My next post will be on creating the different format clients to call these services. Thanks for reading. If you have any questions or comments feel free to post.
EDIT: Added a new post on how to use Zend_Json_Server and how to call it: http://www.joeyrivera.com/2011/zend_json_server-and-how-to-call-it-via-json-rpc-2-0/
You can actually simplify your JSON example by using either the ContextSwitch or AjaxContext action helper; by default, the JSON context takes all variables assigned to the view and simply returns a JSON representation of them. You might also want to experiment with the JSON-RPC server (Zend_Json_Server) as a more standardized format for handling JSON requests.
Also, one note on architecture here. While many people like to intercept all calls with the MVC, with services this doesn’t make a lot of sense. The server classes act as your controllers, and the protocol they serve dictates the view; as such, using the MVC is redundant — and also adds quite a bit of overhead. I typically create a service script for each protocol I want to provide a service for, and use rewrite rules to map to them (which allows me to move them around in my architecture later if need be).
Overall, though, it’s a nice demonstration of how all of our server classes provide a common API — allowing you to re-use your service classes/domain models for a variety of protocols.
Matthew, thanks for the great feedback. I completely missed Zend_Json_Server when looking at the documentation and I’ll look into it. I also agree with the redundancy aspect of the way I’ve created these services. For a real project I would most likely do as you mentioned and keep the project more lightweight.
Dude your examples links are not working.
Thanks, they should be back up now.
Hi, very nice post. Very usefull
Very helpful !!!!
I have created so many apis using ur examples.
This is very nice example. Thank you so much for that…
I am using Zend model mapper. So, i am getting data result from model as object producted variables like below.
Array
(
[0] => Webservices_Models_Token Object
(
[_id:protected] => 1
[_token_key:protected] => 123
[_mapper:protected] =>
)
)
So i am getting error, Do you have any idea about this error.
http://stackoverflow.com/questions/12175527/zend-rest-server-is-not-reading-protected-object-variables
Could you post more of your code, I’m not sure I understand what the issue is. You are creating the zend_rest_server, then you call it and it’s breaking in the client or the server? Have you checked your php error log to see if there is any more information there?
@Joey: It’s actually server and it works fine. i just used toArray(). that saves me.
$select = $this->getDbTable()->select()->where($where);
return $this->getDbTable()->fetchAll($select)->toArray();
You doing really good job. keep sharing.