PHP  »  Articoli  »  Programmazione Php 

API pubbliche e private per applicazioni in PHP

di: Gabriele Farina     26 Ottobre 2006

La scorsa settimana mi sono occupato di introdurre il concetto di plugin in modo teorico e successivamente mostrare un'implementazione molto semplice per fornire alla propria applicazione la capacità di accettare plugin per estendere il framework. Scrivere applicazioni plugin-capable è ormai un'operazione che si può considerare di routine, quasi un vero e proprio obbligo se si desidera rendere pubblico e facilmente personalizzabile il proprio prodotto. In questo articolo cercherò invece di introdurvi alle metodologie utili per creare applicazioni in grado di esporre ad utenti esterni le proprie funzionalità.

Il concetto di API pubblica non è affatto una novità: il mondo dei Web service è nato e si è sviluppato al fine di promuovere e migliorare questo concetto; ormai è da considerarsi normale che un'applicazione di successo fornisca delle API per permettere ai propri utenti (o addirittura a chiunque) di sfruttare servizi più o meno complessi che una volta erano privati e fruibili solamente attraverso l'applicazione stessa. Basti pensare a Google o Flickr, che fanno delle API pubbliche uno degli strumenti più interessanti per chi desidera sfruttare questi immensi database di informazioni per i propri scopi, ludici o economici.

I protocolli di comunicazione

Con il tempo e con l'introduzione dei Web service sono andati ad affermarsi diversi protocolli di comunicazione che potremmo definire standard: tra questi possiamo ricordare SOAP, XML-RPC, XML e JSON; ognuno di questi standard ha i suoi pro ed i suoi contro, ed un servizio che punta ad essere il più cross platform possibile dovrebbe riuscire a comunicare sfruttando il maggior numero di protocolli implementabili. Spesso però non si hanno di queste esigenze, oppure si opta per soluzioni più semplici ma allo stesso tempo vincenti: JSON è un protocollo di comunicazione che è nato (in realtà si è affermato) successivamente alla prorompente entrata nel mondo dello sviluppo web della tecnologia AJAX, e pare essere una buona soluzione se si desidera far fruire le API ad applicazioni client; SOAP ed XML-RPC sono invece i due protocolli più utilizzati nell'implementazione dei webservice: a fronte di una maggior complessità, il primo dei due risulta molto più versatile e sta via via soppiantando il secondo; infine abbiamo tutta quella serie di soluzioni proprietarie che sfruttano chiamate XML semplici, header GET/POST o altre soluzioni simili.

Per l'esempio che andrò ad illustrare ho optato per l'utilizzo di XML semplice sia per le richieste sia per le risposte.

Il server per le API pubbliche

Come primo esempio illustrerò come implementare un semplice server capace di esporre delle API accessibili da chiunque voglia utilizzare le funzionalità esposte. Il protocollo di comunicazione utilizzato sarà XML sia per le richieste che per le risposte.

<?php

abstract class Service
{
  abstract public function getServicesSignatures();
}

final class Type
{
  const INT   = 'int';
  const STRING  = 'string';
}

class ServiceServer
{
  public $service_dir;
  
  public function __construct($service_dir)
  {
    $this->service_dir = $service_dir;
  }
  
  public function run()
  {
    $data = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : file_get_contents("php://input");
    if(!$data || strlen($data) == 0)
    {
      $this->dumpServices();
      return false;
    }
    
    try
    {
      $dom = new DomDocument();
      $dom->loadXML($data);
      
      $method = $dom->firstChild->nodeName;
      $arguments = array();
      foreach($dom->firstChild->childNodes as $child)
      {
        $arguments[$child->nodeName] = $child->textContent;
      }
      
      list($service, $method) = explode('.', $method);
      $service = $this->loadService($service);
      $signatures = $service->getServicesSignatures();
      
      if(!isset($signatures[$method]))
        $this->reportError("Metodo ".$method." non implementato dal servizio ".$service);
      
      $signature = $signatures[$method];
      foreach($arguments as $key => $value)
      {
        switch($signature[$key])
        {
          case Type::INT:
            $value = intval($value);
          break;
        }
        
        $arguments[$key] = $value;
      }
      
      $result = call_user_func_array(array($service, $method), $arguments);
      $this->reportResult("<Std.Result>".$result."</Std.Result>");
      
    }catch(Exception $e)
    {
      $this->reportError($e->__toString());
    }
    
    return true;
  }
  
  private function dumpServices()
  {
    echo "<h2>Lista dei servizi disponibili</h2><ul>";
    foreach($this->listServices() as $service => $signatures)
    {
      foreach($signatures as $method => $signature)
      {
        $params = array();
        foreach($signature as $name => $type)
          $params[] = $type." ".$name;
        echo "<li>".$service.".".$method."(".implode(", ", $params).")</li>";
      }
    }
    echo "</ul>";
  }
  
  private function reportError($e)
  {
    $xml = array("Std.Error", $e);
    $this->reportResult($xml);
  }
  
  private function reportResult($result)
  {
    header('Content-Type', 'text/xml');
    header('Content-Length', strlen($result));
    
    echo $result;
  }
  
  public function listServices()
  {
    $services = array();
    foreach(glob($this->service_dir."/*.php") as $service_file)
    {
      $service_name = basename($service_file, ".php");
      
      $service = $this->loadService($service_name);
      $services[$service_name] = $service->getServicesSignatures();
    }
    
    return $services;
  }
  
  public function loadService($name)
  {
    $service_file = $this->service_dir."/".$name.".php";
    if(!file_exists($service_file))
      throw Exception("Il servizio ".$name." non può essere caricato!");
      
    require_once $service_file;
    
    if(!class_exists($name))
      throw Exception("Il servizio caricato (".$name.") non può essere instanziato!");
    
    return new $name;
  }
}

$server = new ServiceServer(dirname(__FILE__).'/services');
$server->run();

?>

Abbiamo definito tre classi che abbiamo evidenziato in rosso nel codice:

  • La classe astratta Service che serve per rappresentare un servizio; ogni servizio implementato dovrà esporre il metodo getServicesSignatures che si occuperà di restituire una array multidimensionale contenente la definizione dei metodi esposti dal servizio;
  • La classe Type che definisce due tipi di dato accettabili dai metodi;
  • La classe ServiceServer che si occupa di accettare le chiamate in arrivo, effettuare il parsing dell'XML inviato, caricare il servizio richiesto ed eventualmente eseguirlo. In caso di errore o eccezioni verrà restituito il nodo XML Std.Error contenente la stringa di errore; nel caso di risultato corretto restituiremo Std.Result;

Il sistema scelto è molto semplice ma flessibile; non sono stati implementati tutti i controlli possibili e le ottimizzazioni, ma il sistema è ben strutturato ed facilmente utilizzabile.

Guide PHP

Guida Yii Framework

Come creare applicazioni Web in modo semplice e veloce con il...

Guida Zend Framework

Diventate professionisti dello sviluppo Web. Zend Framework è lo...

Guida Applicazioni Facebook con PHP

Come realizzare un'applicazione per Facebook. Dalle basi della...

Altre guide

Newsletter @PHP

Ogni lunedì, direttamente nella tua e-mail: script, articoli, guide e tutorial su PHP, MySQL e Apache.

Iscriviti alla newsletter

Altre newsletter

Corsi in aula

Corso PHP per Webmaster

11 Giugno 2012 a Milano
Disponibilità: 7 Posti

Corso Google AdWords Base

25 Giugno 2012 a Milano
Disponibilità: 7 Posti

Corso Google AdWords Base

05 Giugno 2012 a Roma
Disponibilità: 7 Posti