Let’s take a quick look at something that’s kind of neat in Zend Framework. I’ve been doing some work with Adobe on some articles and one of them was on working with mobile clients with Flash. Well, me being the masochist I did more. What I did was write an example that worked as a full website, an Ajax website, a Flash service and an XML-RPC service.
Setting the Stage
First I started with a Zend_Db row instance and it’s corresponding table instance.
1 2 3 4 5 6 7 8 9 10 11 12 | // row class Model_Peak extends Zend_Db_Table_Row_Abstract { /* plus a bunch of getters and setters */ } // table class Model_DbTable_Peak extends Zend_Db_Table_Abstract { protected $_rowClass = 'Model_Peak'; protected $_name = 'peaks_location'; } |
Then I wrote a mapper, which accessed the DB models and exposed the functionality I wanted to provide for all of the different types of requests
1 2 3 4 5 6 7 8 9 | class Mapper_Peaks { public function getStates() { /* TODO */ } public function getRanges($state) { /* TODO */ } public function getMountains($state, $range) { /* TODO */ } } |
After that I wrote a controller that handled the web and JSON-bootstrap pages.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | class IndexController extends Zend_Controller_Action { public function indexAction() { $mapper = new Mapper_Peaks(); $this->view->states = $mapper->getStates(); } public function rangesAction() { $this->view->state = $this->_request->getParam('state'); $mapper = new Mapper_Peaks(); $this->view->ranges = $mapper->getRanges( $this->view->state ); } public function mountainsAction() { $mapper = new Mapper_Peaks(); $this->view->state = $this->_request->getParam('state'); $this->view->range = $this->_request->getParam('range'); $this->view->mountains = $mapper->getMountains( $this->view->state, $this->view->range ); } public function jsonrpcAction() {} public function serviceAction() { $serviceHandler = $this->getRequest()->getParam('serviceHandler'); if ($serviceHandler instanceof Zend_Server_Interface) { $smd = $this->_request->getParam('smd'); if ($smd) { echo $smd; exit; } $out = $serviceHandler->handle(); ob_clean(); echo $out; exit; } throw new Zend_Controller_Response_Exception('Not Found', 404); } } |
Zend_Server
Now for the fun part. Notice serviceAction()? That’s where the Zend_Server_* stuff comes in. In a plugin, which I will show you in a bit, I attached an instance of Zend_Server_Interface. Then in the controller I simple told it to do its thing, with the exception of handing the Service Mapping Description for JSON-RPC ($smd). That’s it. Because Zend_Json_Server, Zend_XmlRpc_Server and Zend_Amf_Server all implement the Zend_Server_Interface interface that’s all I needed to do.
But since everything is going to one place you need to do a bunch of freaky logic to get the right one, right? Nope. It’s actually quite easy. What I did was create a plugin that detected the request type, instantiated the required server instance and attached it to the request after setting the request object to execute the serviceAction() method instead of the requested one. In other words, you could send your request to /34gas/q43ar/13/asdfg/as and it would still go to the serviceAction() method. Here’s the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class Ctx_Controller_Plugin_ServicePlugin extends Zend_Controller_Plugin_Abstract { public function routeShutdown ( Zend_Controller_Request_Abstract $request) { $serviceHandler = null; if ($request->isXmlHttpRequest() && $request->isPost()) { $serviceHandler = new Zend_Json_Server(); $serviceHandler->getServiceMap()->setDojoCompatible(true); } else if ($request->isFlashRequest()) { $serviceHandler = new Zend_Amf_Server(); /* Note: This may be different depending on the SAPI you are using */ } else if (strpos($request->CONTENT_TYPE, 'text/xml') === 0) { $serviceHandler = new Zend_XmlRpc_Server( 'http://' .$_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] ); } else if($request->isXmlHttpRequest()) { $serviceHandler = new Zend_Json_Server(); $smd = $serviceHandler->getServiceMap(); $smd->setDojoCompatible(true); $request->setParam('smd', $smd); $view = new Zend_View(); $serviceHandler->setTarget($view->url()) ->setEnvelope(Zend_Json_Server_Smd::ENV_JSONRPC_2); } if ($serviceHandler) { foreach (glob(APPLICATION_PATH.'/mappers/*.php') as $dir) { $name = substr(basename($dir), 0, -4); $class = 'Mapper_' . $name; $serviceHandler->setClass($class, $name); } $request->setParam('serviceHandler', $serviceHandler); $request->setControllerName('index'); $request->setActionName('service'); } } } |
Looks like a lot, right? Actually there’s not much there. Here’s the logic flow.
- Is it an XMLHTTP Request and is it a POST? Create the Json server
- Is it an AMF request? Create the AMF server
- Is it an XmlRpc request? Create the XmlRpc server
- Is it an XMLHTTP Request and is it a GET? Create the Service Map (for JSON-RPC 2.0)
- If a service handler has been created add all of the application’s mappers, attach the service handler to the request and redirect to the service action.
And with that you have an application that can serve content for multiple different types of service with almost no effort on your part. At least.. if you copy and paste this code.
Have a good Friday!!!
Comments
No comments yet...