Zend Framework agevola lo sviluppatore fornendo tutti gli strumenti necessari alla creazione di servizi REST. In questo articolo vedremo come creare dei web services RestFul utilizzando un particolare tipo di route, controller e plugin.

  1. Premessa
  2. Inizializzazione Rest Route
  3. Plugin necessari
  4. Il controller Rest
  5. Il Modello
  6. I Metodi Head e Options
  7. Testare il tutto
  8. Credits


Premessa

Per il corretto funzionamento di tutti gli esempi il namespace ‘My’ deve essere registrato nell’applicazione, o in alternativa è possibile utilizzarne un altro facendo le opportune sostituzioni nel codice di esempio.
La url locale su cui ho testato il tutto è “www.zfproject.local”, eventuali riferimenti a questa dovranno essere sostituiti con la url della vostra applicazione.
E’ buona norma modularizzare i web service in funzione della loro versione, es. v1, v2, v3, ecc.. in modo da poter gestire differenti versioni parallele.

  
autoloaderNamespaces[] = "My_"

Inizializzazione Rest Route

Grazie al componente Zend_Rest_Route è possibile specificare quali siano le risorse che gestiranno le richieste al web service o in alternativa abilitarla globalmente per l’intera applicazione.

    
    /**
     * Inizializzazione RestRoute 
     * per il controller "service"
     *
     * @return void
     */
    protected function _initRestRoute() 
    {
        $this->bootstrap("frontController");
        $frontController = Zend_Controller_Front::getInstance();
        $restRoute = new Zend_Rest_Route($frontController, array(), array(
            "default" => array("services"),
        ));
        $frontController->getRouter()->addRoute("rest", $restRoute);
    }

O in alternativa per l”intera applicazione

   
    /**
     * Inizializzazione RestRoute 
     * per l"intera applicazione
     *
     * @return void
     */
    protected function _initRestRoute() 
    {
        $this->bootstrap("frontController");
        $frontController = Zend_Controller_Front::getInstance();
        $restRoute = new Zend_Rest_Route($frontController);
        $frontController->getRouter()->addRoute("default", $restRoute);
    }

Per questo tutorial utilizzerò la gestione dei web service solo per il service controller, ovvero la prima versione degli esempi di sopra, le richieste saranno quindi da farsi a /services.


Plugin necessari

Per intercettare i parametri PUT è necessario abilitare il PutHandler Plugin. Questo lo si può fare aggiungendo una linea nel file application.ini.

resources.frontController.plugins.PutHandler = "Zend_Controller_Plugin_PutHandler"

Il controller Rest

Al fine della creazione di un controller specifico per gestire le richieste di tipo Rest, Zend Framework offre il componente Zend_Rest_Controller. Questa è una classe astratta con dei metodi astratti, get, head, post, put, delete ( e index ), ognuno dei quali riceverà le richieste dipendentemente dal tipo di richiesta HTTP. Ogni dato restituito dovrà essere accompagnato da un codice HTTP identificativo dell’esito della richiesta effettuata, ad esempio 200 (OK), 404 (NOT FOUND) e così via come da protocollo specificato nell’RFC2616.
Questi codici e relativo messaggio possono essere trovati anche nella classe Zend_Http_Response.

Nota: i metodi contenuti nel RestController vanno a creare il sistema denominato CRUD ( Create, Read, Update, Delete ), dove ogni metodo di richiesta corrisponde ad una specifica azione di inserimento, lettura, aggiornamento ed eliminazione, come dallo schema seguente METODO => AZIONE

  • POST => CREATE, crea un record
  • GET => READ, legge un record
  • PUT => UPDATE, aggiorna un record
  • DELETE => DELETE, elimina un record

Di seguito il codice del ServiceController, da notare che per questo tutorial ho implementato l’utilizzo di Zend_Rest_Server solo per le richieste di tipo GET (chiamando il metodo ‘sayHello’ del modello), mentre per gli altri metodi di richiesta ho impostato un body ed un response code direttamente nella action, sarà quindi vostro il compito di creare ulteriori metodi del modello per gestire le altre richieste.

<?php
/**
 * Controller test web services
 * 
 * @author Sergio Rinaudo
 */
class ServicesController 
    extends Zend_Rest_Controller
{
    /**
     * @var Zend_Rest_Server
     */
    protected $_server;
    
    /**
     * (non-PHPdoc)
     * @see Zend_Controller_Action::init()
     */
    public function init()
    {  
        $this->_server = new Zend_Rest_Server();
        $this->_server>setClass("Application_Model_Test");
        $this->getHelper("viewRenderer")>setNoRender();
        $this->getHelper("layout")>disableLayout();

        parent::init();
    }
    
    /**
     * (non-PHPdoc)
     * @see Zend_Rest_Controller::indexAction()
     */
    public function indexAction()
    {
        $this->getResponse()->setBody("Listato delle Risorse");
        $this->getResponse()->setHttpResponseCode(200);
    }

    /**
     * (non-PHPdoc)
     * @see Zend_Rest_Controller::getAction()
     */
    public function getAction()
    {
        $this->_server>handle(array(
            "method" => "sayHello",
    		"who" => $this->getParam("who"),
    		"when" => $this->getParam("when")
        ));
    }
    
    /**
     * (non-PHPdoc)
     * @see Zend_Rest_Controller::postAction()
     */
    public function postAction()
    {
        $this->getResponse()->setBody("Risorsa creata");
        $this->getResponse()->setHttpResponseCode(201);
    }
    
    /**
     * (non-PHPdoc)
     * @see Zend_Rest_Controller::putAction()
     */
    public function putAction()
    {
        $this->getResponse()->setBody(sprintf("Risorsa #%s Aggiornata", $this->_getParam("id")));
        $this->getResponse()->setHttpResponseCode(201);
    }
    
    /**
     * (non-PHPdoc)
     * @see Zend_Rest_Controller::deleteAction()
     */
    public function deleteAction()
    {
        $this->getResponse()->setBody(sprintf("Risorsa #%s Eliminata", $this->_getParam("id")));
        $this->getResponse()->setHttpResponseCode(200);
    }
}


Il Modello

Internamente al RestController usiamo una istanza di Zend_Rest_Server a cui assegneremo la classe Application_Model_Test che si occuperà di restituire i dati desiderati nonchè impostare l’HTTP code in base alla riuscita o meno della richiesta.

<?php
/**
 * Test 
 *
 * @author Sergio Rinaudo
 */
class Application_Model_Test 
    //extends My_Db_Table
{
    /**
	 * Say Hello
	 * In questo metodo settiamo il responseCode in base all"esito della chiamata
     *
     * @param string $who
     * @param string $when
     * @return SimpleXMLElement
     */
    public function sayHello($who, $when)
    {
        $front = Zend_Controller_Front::getInstance();
        $front->getResponse()->setHttpResponseCode(200);
        
        return array(
        	"msg" => "Hey " . $who . "! Hope you\"re having a good " . $when, 
        	"status" => false
        );
    }
}


I Metodi HEAD e OPTIONS

HEAD e OPTIONS sono altri due metodi di richiesta HTTP piuttosto conosciuti, quindi ne aggiungeremo la gestione nella nostra risorsa web service, ma prima vediamo nel dettaglio a cosa servono

  • HEAD: questo metodo è del tutto identico a GET con la differenza che NON DEVE restituire un corpo del messaggio nella risposta. Le meta-informazioni contenute nelle intestazioni HTTP in risposta ad una richiesta HEAD, DOVREBBERO essere identiche alle informazioni inviate in risposta ad una riciesta GET. Questo metodo può essere utilizzato per ottenere meta-informazioni a proposito dell’entità implicata nella richiesta senza trasferirne il corpo. Questo metodo è spesso usato per testate la validità di un link hypertext, l’accessibilità, e modifiche recenti ( vedi rfc2616 sezione 9.4).
  • OPTIONS: questo metodo rappresenta una richiesta per informazioni a proposito delle opzioni di comunicazione disponibili alla richiesta/risposta identificate da una Request-URI. Questo metodo permette al client di determinare le opzioni e/o i requisiti associati ad una risorsa, o le capacità del server, senza dover chiamare la risorsa a cui si è interessati ( vedi rfc2616 sezione 9.2).

Per aggiungere la gestione di questi metodi, creeremo una nuova classe astratta, My_Rest_Controller che estenda Zend_Rest_Controller. Modificheremo quindi il nostro ServiceController che estenda My_Rest_Controller piuttosto di Zend_Rest_Controller.

<?php
/**
 * Aggiunge gestione dei metodi HEAD e OPTIONS nella nostra risorsa
 * 
 * @author Sergio Rinaudo
 */
abstract class My_Rest_Controller extends Zend_Rest_Controller
{
    /**
     * (non-PHPdoc)
     * @see Zend_Rest_Controller::headAction()
     */
    public function headAction()
    {
        // qui è necessaria logica di verifica cache header della richiesta
        $this->getResponse()->setBody(null);
    }

    /**
     * Gestione del metodo OPTIONS
     * 
     * @return void
     */
    public function optionsAction()
    {
        $this->getResponse()->setBody(null);
        $this->getResponse()->setHeader("Allow", "OPTIONS, HEAD, INDEX, GET, POST, PUT, DELETE");
    }
}


Testare il tutto

Per testare i servizi abbiamo diverse soluzioni, una per esempio è creare un controller che effettui delle chiamate di prova, chiamato ad esempio RestClientController.

<?php
/**
 * Controller test web services
 * 
 * @author Sergio Rinaudo
 */
class RestClientController 
    extends Zend_Controller_Action
{
    /**
     * @var Zend_Rest_Client
     */
    protected $_client;
    
    public function init() 
    {
        $this->_client = new Zend_Rest_Client("http://www.zfproject.local"); // modificare in base alle vostre esigenze
        $this->getHelper("viewRenderer")->setNoRender();
        $this->getHelper("layout")->disableLayout();
    }
    
    /**
     * Performs an HTTP GET request.
     * 
     * @return void
     */
    public function testGetAction() 
    {
        $a = $this->_client->restGet("/services/who/me/when/now");
        $this->getResponse()->setHeader("Content-Type", "application/xml", true);
        echo $a->getBody();
    }
    
    /**
    * Performs an HTTP POST request.
     *
    * @return void
    */
    public function testPostAction() 
    {
        $a = $this->_client->restPost('/services');
        $this->getResponse()->setHeader("Content-Type", "application/xml", true);
        echo $a->getBody();
    }
    
    /**
     * Performs an HTTP PUT request.
     * 
     * @return void
     */
    public function testPutAction() 
    {
        $a = $this->_client->restPut('/services/1');
        $this->getResponse()->setHeader("Content-Type", "application/xml", true);
        echo $a->getBody();
    }
    
    /**
     * Performs an HTTP DELETE request.
     * 
     * @return void
     */
    public function testDeleteAction() 
    {
        $a = $this->_client->restDelete('/services/1');
        $this->getResponse()->setHeader("Content-Type", "application/xml", true);
        echo $a->getBody();
    }
}

Un’altra possibilità è utilizzare da shell di linux il comando curl per eseguire le richieste. Su Windows possiamo utilizzare un’emulatore, ad esempio bash di Git.

curl -v http://www.zfproject.local/services
curl -v -X GET http://www.zfproject.local/services/who/you/when/day
curl -v -X POST http://www.zfproject.local/services
curl -v -X PUT -d '' http://www.zfproject.local/services/1
curl -v -X DELETE http://www.zfproject.local/services/1
curl -v -X HEAD http://www.zfproject.local/services
curl -v -X OPTIONS http://www.zfproject.local/services


Credits

Per scrivere questo tutorial mi sono ispirato ai seguenti articoli

ed ai vari commenti.

1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5,00 out of 5)
Loading...Loading...

1 commento

  1. Ciao, complimenti per l’ottima guida e tutto molto chiaro, ma non riesco ad implementarlo nel mio progetto.
    Mi spiego meglio ho un progetto in produzione zf 1.12 + doctrine 1.2.
    Ho seguito alla perfezione la tua guida tranne che per una cosa, ho modificato il metodo all’interno del `Bootsrap` perche’ come ti dicevo avendo gia’ un progetto volevo mettere rest all’interno di un modulo chiamato `api`, quindi ho modificato il tuo metodo `_initRestRoute()` con il seguente:

    protected function _initRestRoute()
    {
    $frontController = Zend_Controller_Front::getInstance ();
    $restRoute = new Zend_Rest_Route($frontController, array(), array(‘api’));
    $frontController->getRouter ()->addRoute(‘rest’, $restRoute);
    }

    ma nel momento in cui vado ad effettuare un test del tipo:
    http://baseurl/api/foo/1

    ricevo l’errore che non riesce a trovare la risorsa `1` quindi per capirci e come se andasse a cercare la action di nome 1!!! Quindi mi chiedo come e’ possibile! Sembrerebbe che le configurazioni della route nel bootstrap non se le fila proprio! :S

    Sapresti dirmi come mai?
    Grazie

Lascia un Commento

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *

È possibile utilizzare questi tag ed attributi XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>