venerdì, agosto 30, 2013

PHP - Passaggio di parametri


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Passaggio di parametri

Quando si richiama una funzione è possibile passarle dei parametri. Gli esempi che seguono possono essere applicati anche, ovviamente, alle funzioni membro delle classi.
Tutti gli esempi vengono presentati con il relativo test Lime, in modo da abituarsi all'idea dello Unit Testing, di cui abbiamo parlato.

Passaggio per valore

function foobar($a)
{
  $a++;
  return $a;
}

$t=new lime_test(1, new lime_output_color());
$t->is(foobar(5), 6, 'foobar() returns the value incremented by one');

Impostazione di un valore di default


function foobar($a=4)
{
  $a++;
  return $a;
}

$t=new lime_test(2, new lime_output_color());

$t->is(foobar(5), 6, 'foobar() returns the value incremented by one');
$t->is(foobar(), 5, 'foobar() takes 4 as default, and returns 5');

Controllo dei parametri


function foobar($a)
{
  if (!is_integer($a))
  {
    throw new Exception('Function foobar accepts only integers as parameter');
  }
  $a++;
  return $a;
}

$t=new lime_test(3, new lime_output_color());

$t->is(foobar(5), 6, 'foobar() returns the value incremented by one');
try
{
  $n=foobar('abc');
  $t->fail('foobar() does not throw an exception with a string parameter');
}
catch(Exception $e)
{
  $t->pass('foobar() throws an exception with a string parameter');
}

try
{
  $n=foobar(1.2);
  $t->fail('foobar() does not throw an exception with a float parameter');
}
catch(Exception $e)
{
  $t->pass('foobar() throws an exception with a float parameter');
}

Oggetti come parametri


Il controllo del tipo è automatico per gli oggetti, se si indica esplicitamente a che classe devono appartenere:

class BazBar
{
  private $v;
  public function __construct($v)
  {
    $this->v=$v;
  }
  public function getV()
  {
    return $this->v;
  }
  
  public function incV()
  {
    $this->v++;
    return $this;
  }
}

class ExtraBazBar extends BazBar
{
}

function foobar(BazBar $a)
{
  $a->incV();
  return $a->getV();
}

$t=new lime_test(2, new lime_output_color());

$t->is(foobar(new BazBar(5)), 6, 'foobar() accepts a BazBar object');

$t->is(foobar(new ExtraBazBar(5)), 6, 'foobar() accepts an ExtraBazBar object');

Array di parametri


function foobar($parameters=array())
{
  $v=$parameters['value']+$parameters['inc'];
  return $v;
}

$t=new lime_test(1, new lime_output_color());

$t->is(foobar(array('value'=>5, 'inc'=>1)), 6, 'foobar() accepts an array of parameters');

Passaggio per riferimento


function foobar(&$v)
{
  $v++;
  return $v;
}

$t=new lime_test(1, new lime_output_color());

$k=5;
foobar($k);
$t->is($k, 6, 'foobar() changes the value of the variable passed as parameter');

Altre cose utili


In alcuni casi potrebbe essere utile fare ricorso alle funzioni func_num_args()func_get_args(), ecc. Consultare le relative pagine del manuale.

PHP - Unit testing


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.
Adesso consiglierei di usare PHPUnit anziché Lime.

Unit testing

La pratica della predisposizione dei test unitari (unit testing) permette di semplificare le modifiche, semplificare l'integrazione e supportare la documentazione. Buoni motivi per adottarla...

Lime

Lime fa parte del framework Symfony, ma può essere utilizzato anche in maniera isolata. Documentazione al riguardo è rintracciabile nelle pagine del progetto (la guida rimane valida per quanto riguarda Lime, anche se la versione Symfony 1.2 è deprecata). Il codice di Lime è disponibile nel deposito SVN di Symfony.

Uso di Lime

Un semplice esempio di partenza di unit test con Lime è il seguente:

<pre>
<?php
require('../../lib/lime.php');
// impostare il percorso a seconda di dov'è il file

ini_set('error_reporting', E_ALL);

function __autoload($className)
{
    $filename=$className.'.class.php';
    include($filename);
}

$t=new lime_test(4, new lime_output_color());

$calculator = new Calculator();

$t->cmp_ok($calculator->getOperand(0), '===', false, '->getOperand() returns false for an unitialized value');

$t->isa_ok($calculator->setOperand(0,10), 'Calculator', '->setOperand() returns a Calculator object');

$t->is($calculator->getOperand(0), 10, '->getOperand() returns the correct value for an initialized value');

try
{
    $calculator->setOperator('_');
    $t->fail('->setOperator() allows a non valid operator');
}
catch (Exception $e)
{
    $t->pass('->setOperator() throws an exception with a non valid operator');
}


L'output dovrebbe assomigliare al seguente:
1..4
ok 1 - ->getOperand() returns false for an unitialized value
ok 2 - ->setOperand() returns a Calculator object
ok 3 - ->getOperand() returns the correct value for an initialized value
ok 4 - ->setOperator() throws an exception with a non valid operator
# Looks like everything went fine. 

Esercizio

Modificare la classe Calculator aggiungendo nuove funzioni membro e predisporre i relativi test.

PHP - Basi di HTTP


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Basi di HTTP


È molto importante conoscere alcune cose fondamentali di ciò che avviene quando un browser e un server web comunicano tra loro.
Un esercizio interessante è di usare programmi come curl, che consentono di registrare la comunicazione in un file di testo da analizzare con calma.

Il metodo GET

Ad esempio, quando il browser invia una richiesa a google.com con una richiesta simile alla seguente

GET / HTTP/1.1
User-Agent: curl/7.19.5 (i486-pc-linux-gnu) libcurl/7.19.5 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.15
Host: google.com
Accept: */*

viene rediretto a www.google.com:

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Date: Tue, 02 Mar 2010 20:15:36 GMT
Expires: Thu, 01 Apr 2010 20:15:36 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0

<HTML><HEAD><meta http-equiv="content-type" content="text/html;c
harset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Mov
ed</H1> The document has moved <A HREF="http://www.google.com/">
here</A>.
</BODY></HTML>

A questo punto fa la richiesta a www.google.com:

GET / HTTP/1.1
User-Agent: curl/7.19.5 (i486-pc-linux-gnu) libcurl/7.19.5 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.15
Host: www.google.com
Accept: */*

e riceve un ulteriore reindirezzamento a www.google.it, con l'invito, questa volta, a memorizzare dei cookies:

HTTP/1.1 302 Found
Location: http://www.google.it/
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie: PREF=ID=bd21c8242c754edb:TM=1267563471:LM=1267563471S=NwB1nU_SnFL-QObq; expires=Thu, 01-Mar-2012 20:57:51 GMT; path=/; domain=.google.it
Set-Cookie: NID=32=R74iZCyObDJF0HSpezx6-94w2O9BpeXSR6mD1uOFT4DuGoMY0J6hhQ-1qEXbs04rWbCSkrrch18q5ATv5jnakX-dDPKbCI2U6rpsG5MR05j0a-NmMDasXR9JdK_pRYK3; expires=Wed, 01-Sep-2010 20:16:35 GMT; path=/; domain=.google.com; HttpOnly

Date: Tue, 02 Mar 2010 20:16:35 GMT
Server: gws
Content-Length: 218
X-XSS-Protection: 0

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>302 Moved</TITLE></HEAD><BODY> <H1>302 Moved</H1> The document has moved <A HREF="http://www.google.it/">here</A>.
</BODY></HTML>

La richiesta delle pagine successive avverrà quindi inserendo nella richiesta i cookies:

GET / HTTP/1.1
User-Agent: curl/7.19.5 (i486-pc-linux-gnu) libcurl/7.19.5 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.15
Host: www.google.it
Accept: */*
Cookie: PREF=ID=bd21c8242c754edb:TM=1267563471:LM=1267563471S=NwB1nU_SnFL-QObq
La risposta spesso arriva al browser "spezzettata" (chunked), in modo che non sia necessario informarlo in anticipo della dimensione del file in arrivo (Content-Length)

HTTP/1.1 200 OK
Date: Tue, 02 Mar 2010 20:16:46 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
...Transfer-Encoding: chunked

1000
<!doctype html><html><head><meta http-equiv="content-type" conte
nt="text/html; charset=ISO-8859-1"><title>Google</title><script>
...
ada
ubmit value="Mi sento fortunato" class=lsb></td><td nowrap width
...
rt=(f=(new Date).getTime());.})();.</script>
0

Ogni singolo chunk viene preceduto dalla dimensione in byte (espressa in esadecimale) dello stesso. La fine della trasmissione è contrassegnata dal valore 0.
(Di questo spezzettamente si occupa direttamente il server web, come sviluppatori PHP non dobbiamo preoccuparcene...)

Il metodo POST

Cosa succede invece in caso di POST? Quando compiliamo il modulo di ricerca di www.php.net inviamo dei dati in questo modo:

POST /search.php HTTP/1.1
User-Agent: curl/7.19.5 (i486-pc-linux-gnu) libcurl/7.19.5 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.15
Host: www.php.net
Accept: */*
Content-Length: 29
Content-Type: application/x-www-form-urlencoded

pattern=functions&show=manual

La risposta che riceviamo potrebbe essere una pagina web, ma è buona pratica ridirigere il browser ad un'altra pagina da richiedere con il metodo GET (ed è quello che succede nel caso concreto).

Sulla differenza tra metodo GET e POST è utile la lettura del documento Methods GET and POST in HTML forms - what's the difference? di Jukka "Yucca" Korpela.

Refresh della pagina

Capita spesso di trovare delle pagine web che fanno scattare il refresh della pagina (spesso, ma non solo, per far scaricare un file). Questo di solito viene ottenuto indicando, all'interno dell'elemento META un contenuto simile al seguente:

<html>
<head>
<meta http-equiv="Refresh" content="0;URL=http://www.example.com/" />
</head>
<body>
<script type="text/javascript" language="javascript">
<!--
location.replace("http://www.example.com/")
-->
</script>
</body>
</html>

Nella risposta del server web compare comunque l'intestazione HTTP appropriata, che viene riportata nella pagina web per sicurezza.

Expires: Sun, 28 Mar 2010 09:19:06 GMT
Date: Sun, 21 Mar 2010 09:19:06 GMT
Refresh: 0;URL=http://www.example.com/
...

Nota: il numero prima dell'URL indica dopo quanti secondi il browser è invitato a fare il refresh della pagina.

Approfondimenti

Oltre ai metodi GET e POST, il protocollo HTTP prevede i metodi HEAD, PUT e DELETE (vedi RFC 2616). I browser non supportano direttamente i metodi PUT e DELETE (se si specificano questi come metodi vengono effettivamente fatte richieste con GET), ma è possibile la loro implementazione tramite Javascript.

Le applicazioni che usano in maniera coerente i metodi GET, POST, PUT e DELETE vengono denominate RESTful. Un'ottima introduzione all'argomento è data in RESTful Web services: The basics di Alex Rodriguez.

HEAD

Con il metodo HEAD vengono chieste solo le metainformazioni su una risorsa, non la risorsa stessa.

PUT

Con il metodo PUT viene richiesta l'esecuzione di un'operazione di inserimento o aggiornamento, invocando un URI in cui la risorsa è chiaramente identificata.
Ad esempio,

PUT /picture/123

è diverso rispetto a 

POST /picture/new

in quanto nel primo caso l'URI riflette la conoscenza, da parte dello user-agent, di come verrà identificata la risorsa, mentre nel secondo si affida al server la decisione su cosa fare dei dati ricevuti.

DELETE

Con il metodo DELETE si chiede l'eliminazione di una risorsa.

Esercizio

Predisporre una pagina web con una form HTML per effettuare la ricerca di una funzione nel sito www.php.net.
Esercitarsi nella predisposizione di form di base e verificare con phpinfo() quali sono i dati ricevuti con il metodo GET e con il metodo POST.

PHP - OOP


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

OOP - Esempio di base

<?php
class WebPage
{
  public function setText($text)
  {
    $this->text=$text;
  }
  public function showPage()
  {
    echo $this->text;
  }
}
$mypage=new WebPage();
$mypage->setText('hello');
$mypage->showpage();

Notare che:
  • manca (volutamente) la chiusura del codice php
  • la pagina prodotta è mooolto scarna (per una sola riga di testo bisognerebbe aggiungere qualche header HTTP, tra l'altro...)
  • tutto il codice è in un unico file, quando sarebbe bene avere invece il codice della classe in un file separato.

Calcolatrice

Vediamo un esempio un po' più complesso:
<?php
class Calculator
{
    private    $operands;
    private    $operator;

    function __construct()
    {
        $this->operands=array();
    }
    
    public function setOperand($n, $v)
    {
        $this->operands[$n]=$v;
        return $this;
    }
    
    public function getOperands()
    {
        return $this->operands;
    }
    
    public function getOperand($n)
    {
        $operands=$this->getOperands();
        if (array_key_exists($n, $this->getOperands()))
        {
            return $operands[$n];
        }
        else
        {
            return false;
        }
    }
    
    public function setOperator($v)
    {
        if (!in_array($v, array('+', '-', '*', '/')))
        {
            throw new InvalidArgumentException('Value ' . $v . ' is not allowed');
        }
        
        $this->operator=$v;
        return $this;
    }

    public function getOperator()
    {
        return $this->operator;
    }
}

Cose da notare:
  • costruttore con nome predefinito
  • definizione di variabili membro private
  • lancio di eccezione
  • interfaccia fluente

Uso della classe:

<?php
ini_set('error_reporting', E_ALL);

function __autoload($className) 
{
    $filename=$className.'.class.php';
    include($filename);
}

$calculator = new Calculator();
$calculator->setOperand(1, 5);
$calculator->setOperand(2, 10);
print_r($calculator->getOperands());
unset($calculator);

Cose da notare:
  • autocaricamento della classe (si evitano includeinclude_oncerequirerequire_once e amenità del genere)
  • visualizzazione ricorsiva di un array (a scopo di debug)
  • deallocazione della memoria utilizzata dall'oggetto (unset)

L'interfaccia fluente dei metodi setters ci consente di scrivere anche cose come queste, più leggibili:

$calculator = new Calculator();
$calculator
->setOperand(1, 5)
->setOperand(2, 10)
->setOperator('*');

echo sprintf("Operator: '%s'\n", $calculator->getOperator());


Esercizio

  1. aggiungere una funzione membro setOperands() che riceve un array di valori come parametro
  2. aggiungere una funzione membro getResult() che restituisce il risultato dell'operazione
  3. definire il comportamento in caso di valori non coerenti (cosa deve succedere se tento una divisione per zero? e se chiedo il risultato prima di aver impostato operandi e operatore?)
  4. definire una serie di casi di test

PHP - Gestione delle stringhe


Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Gestione delle stringhe

Alcune cose di base per chi proviene da esperienza con altri linguaggi.
<?php
    $name="Mario";
    $surname="Rossi";
    $file="picture1.jpeg";
    // usare una delle seguenti istruzioni
:

    $output="$name $surname ha visto l'immagine $file"; // possibile
    $output=$name . ' ' . $surname . ' ha visto l\'immagine ' . $file; // meglio
    $output=sprintf('%s %s ha visto l\'immagine %s',
        $name, $surname, $file); // ancora megli
o    $output=__('%name% %surname% saw the image %filename%', array(
        '%name%'=>$name,
        '%surname%'=>$surname,
        '%filename%'=>$file,
        ));  // molto meglio (ma dipende dal framework e dagli strumenti i18n


Da notare:
  • sostituzione dei nomi delle variabili con il loro valore nel caso di stringhe racchiuse da virgolette doppie
  • uso del backslash per l'escape dell'apice singolo
  • uso di sprintf o degli strumenti i18n

Quando si usa sprintf, se la stringa è racchiusa da virgolette doppie si possono usare sequenze speciali per andare a capo, per tabulare, ecc. (es. "foo\nbar\tbaz").

Le funzioni per le stringhe sono troppo numerose per essere citate. Conviene tenere sotto mano la pagina del manuale di PHP prima di mettersi a reinventare la ruota...
Ad esempio, può essere conveniente usare str_replace() per fare un trova e sostituisci...

Esercizio

Leggere un file di testo con la funzione file(), scorrere gli elementi dell'array e fare delle operazioni su tutti gli elementi (ad esempio un trova e sostituisci). Concatenare l'array modificato ottenuto con implode() e presentarlo in output.

Si consideri il caso in cui si vuole far comparire un messaggio di benvenuto ad un utente. Il modello di testo di messaggio potrebbe essere memorizzato in un file welcome_template.txt simile al seguente:

Ciao, %name%,
Benvenuto nel sito web %website%,
Oggi è il %date%.
#Questo è un commento e non va visualizzato.
#Ricordarsi di impostare il nome del sito web nel file di configurazione.

La nostra applicazione dovrà impostare alcune variabili, leggere il file e operare dei trova e sostituisci per rimpiazzare  i segnaposto:

$rows=file('welcome_template.txt');
$name='Mario';
$website='PHP Developers In Action';
$date=// aggiungere il codice per trovare la data
/* aggiungere il codice per il trova e sostituisci... */
echo implode("\n", $rows);

PHP - Introduzione agli array

Questo post fa parte di una serie preparata qualche anno fa per delle lezioni su PHP.

Introduzione agli array

Gli array standard possono essere indicizzati da stringhe o da numeri. Ogni elemento di un array può essere un valore normale, un altro array o un oggetto (istanza di classe).

<?php

    $friends=Array(
        'Arthur',
        'Charlie',
        'Ben',
    );

    $friends[]='Donald';

    print_r($friends);
    sort($friends);
    print_r($friends);
    var_dump($friends);
    echo serialize($friends);

Cose da notare:

  • inizializzazione esplicita (in particolare, si veda la virgola anche dopo l'ultimo elemento, conviene che ci sia per future aggiunte)
  • aggiunta di un elemento all'array
  • funzioni varie per la gestione degli array

È lecito naturalmente anche inizializzare un array o un suo elemento con una chiave esplicitamente fornita, e valorizzarlo con oggetti o altri array:

<?php
    $images=Array(
        'bob' => new FamilyPicture('bob'),
        'cat' => new AnimalPicture('cat'),
    );

    $images['andrew']=Array(
        new FamilyPicture('andrew1'), new FamilyPicture('andrew2'),
        );

Funzioni predefinite

Per imparare a usare correttamente le funzioni relative agli array è conveniente costrursi degli esempi specifici. Ad esempio, con un codice come il seguente...

<pre>
<?php

    function line() {
        print(str_repeat("-=-", 10) . "\n");
    }

    $a1=array("Uno", "Due", "tre", "ch4"=>"quattro", "ch5"=>"cinque", "Sei");
    print "originale\n";
    print_r($a1);

    line();
    print "sort()\n";
    $a2=$a1;
    sort($a2);
    print_r($a2);

    line();
    print "ksort()\n";
    // risultati strani
    $a2=$a1;
    ksort($a2);
    print_r($a2);

    line();
    print "asort()\n";
    $a2=$a1;
    asort($a2);
    print_r($a2);

    function cmp($a, $b)
    {
        // si potrebbe usare strnatcasecmp al posto di questa
    if (strtoupper($a) == strtoupper($b)) {
        return 0;
        }
    return (strtoupper($a) < strtoupper($b)) ? -1 : 1;
    }
    line();
    print "usort(, \"cmp\")\n";
    $a2=$a1;
    usort($a2, "cmp");
    print_r($a2);

    line();
    print "uasort(, \"cmp\")\n";
    $a2=$a1;
    uasort($a2, "cmp");
    print_r($a2);

    line();
    line();
    line();

    $b=array(
        array('name'=>'mario', 'surname'=>'rossi', 'age'=>20),
        array('name'=>'giuseppe', 'surname'=>'verdi', 'age'=>40),
        array('name'=>'stefania', 'surname'=>'bianchi', 'age'=>30),
        array('name'=>'giorgia', 'surname'=>'neri', 'age'=>50),
  );
    
    function comparePersons($item1, $item2)
    {
    global $fieldname;
    return (strcmp($item2[$fieldname], $item2[$fieldname]));
    }
    $fieldname='surname';
    usort($b, 'comparePersons');
    print_r($b);

... otteniamo un output come questo:

originale
Array
(
[0] => Uno
[1] => Due
[2] => tre
[ch4] => quattro
[ch5] => cinque
[3] => Sei
)
-=--=--=--=--=--=--=--=--=--=-
sort()
Array
(
[0] => Due
[1] => Sei
[2] => Uno
[3] => cinque
[4] => quattro
[5] => tre
)
-=--=--=--=--=--=--=--=--=--=-
ksort()
Array
(
[ch4] => quattro
[0] => Uno
[ch5] => cinque
[1] => Due
[2] => tre
[3] => Sei
)
-=--=--=--=--=--=--=--=--=--=-
asort()
Array
(
[1] => Due
[3] => Sei
[0] => Uno
[ch5] => cinque
[ch4] => quattro
[2] => tre
)
-=--=--=--=--=--=--=--=--=--=-
usort(, "cmp")
Array
(
[0] => cinque
[1] => Due
[2] => quattro
[3] => Sei
[4] => tre
[5] => Uno
)
-=--=--=--=--=--=--=--=--=--=-
uasort(, "cmp")
Array
(
[ch5] => cinque
[1] => Due
[ch4] => quattro
[3] => Sei
[2] => tre
[0] => Uno
)
-=--=--=--=--=--=--=--=--=--=-
-=--=--=--=--=--=--=--=--=--=-
-=--=--=--=--=--=--=--=--=--=-
Array
(
[0] => Array
(
[name] => giorgia
[surname] => neri
[age] => 50
)

[1] => Array
(
[name] => stefania
[surname] => bianchi
[age] => 30
)

[2] => Array
(
[name] => giuseppe
[surname] => verdi
[age] => 40
)

[3] => Array
(
[name] => mario
[surname] => rossi
[age] => 20
)

)

Esercizio

A partire da un codice come il seguente

<html>
<head><title>sdf</title></head>
<body>
<pre>
<?php


$list=Array(
    'bob'=>1,
    'ada'=>2,
    'charlie'=>0,
);

sort($list);

print_r($list);

?>
</pre>
</body>
</html>

modificare il codice per ottenere:
a) un array ordinato per chiave anziché per valore;
b) un array ordinato per valore, dove però le chiavi sono preservate.

sabato, luglio 06, 2013

A quick introduction to Yii development


Some days ago, I have been invited to write a review for the book Yii 1.1. Application Development Starter, written by Jacob Mumm and Mark Safronov (Packt Publishing). The book is published in the "instant" book series, and is available only in its digital form. I received it as a complimentary copy from the publisher.


In the book you don't find anything that isn't available online amongst the official documentation on Yii's website or on Wikipedia. The example in the "Quick start" chapter is taken from (and links to) the official documentation, bringing your attention to the main aspects to consider and leaving out some less important details. The "Top 5 features you need to know about" chapter is a bit more interesting, because it gives you some hints about features that are a little hidden, with some usage examples. I found particularly interesting the sections about authentication and role-based access control. Again, everything is available on the web, so if you have time and want to achieve a deep knowledge of the subject this book is not for you: just go through the examples in the official documentation, access the forums, run your own experiments. But if you are short of time, and just want to know about the main benefits Yii has to offer, it might be worth buying and reading the book, because it offers a quick introduction to all the basic points you'd have to deal with when projecting and implementing a web application using Yii.

domenica, giugno 16, 2013

Appunti su HTTP, REST e API

Nota: questo post è stato aggiornato il 27 dicembre in seguito alla richiesta di chiarimenti sulla parte relativa a REST.

Leggendo qua e là documentazione sui servizi REST e sull'uso corretto dei metodi previsti da HTTP, mi sono accorto che spesso mancano alcune informazioni basilari, che permettono di comprendere a fondo i concetti esposti.

Innanzitutto, vi sono due tipi di servizi che possono essere offerti da un server web: quelli pensati per essere utilizzati da un utente umano, tramite il browser, e quelli pensati per essere utilizzati da applicazioni, che a loro volta saranno utilizzate da esseri umani.

Sempre più spesso, un server web offre entrambe le possibilità: nasce offrendo qualche servizio utilizzabili dai propri utenti tramite browser, e successivamente, quando gli utenti chiedono di automatizzare qualche processo, mette a disposizione dei metodi per l'uso degli stessi (o di altri) servizi da parte di applicazioni diverse.

(Scegliete un social network di vostro gradimento, e cercate nel relativo sito web la sezione dedicata agli sviluppatori per rendervene conto.)

Facciamo un esempio. L'ipotetico nuovo sito web kitchen.example.com consente ad una persona di controllare via web l'attrezzatura e le provviste della propria cucina, completamente automatizzata. Tutto questo via web, con un utente umano che usa un normale browser.



L'applicazione dovrà mettere a disposizione anche un'interfaccia per registrare i carichi di provviste in un database. Offrire all'utente una normale interfaccia web, da utilizzare con il proprio browser, per caricare questi dati potrebbe non essere un'idea ottimale (eppure, quante volte è capitato di vedere cose del genere!). Se avete provato a caricare manualmente dati di tipo analogo (insiemi di fotografie, di documenti vari, ecc.) in numeri maggiori a tre / quattro sapete di che cosa sto parlando. L'ideale è di fornire un modo per automatizzare il procedimento di caricamento delle provviste acquistate e di rendere pubbliche le specifiche tecniche relative a questo processo, in modo che, ad esempio, sia possibile sviluppare un'applicazione che carica le informazioni sul server a partire da qualche altra fonte di dati (ad esempio, il programma di controllo degli scontrini di acquisto...).

Se le specifiche rispettano alcune convenzioni, poi, tanto di guadagnato, perché un qualsiasi programmatore sarà in grado di adattare l'esperienza maturata in progetti precedenti al nuovo caso, senza dover inventare ogni volta la ruota.

Le specifiche tecniche sono documenti che spiegano in maniera dettagliata come un programma può invocare operazioni sul server, e vengono comunemente indicate come API (application program interface). Il server che risponde alle richieste di applicazioni sviluppate seguendo queste indicazioni viene a volte indicato come apiserver. L'applicazione che invia le richieste può essere di diversi tipi: uno script bash eseguito da riga di comando con all'interno richiami di cURL, un'applicazioncina web che sfrutta javascript per le richieste, un'applicazione desktop, un'applicazione per smartphone, un'applicazione web ordinaria, ecc.


Gli scenari naturalmente possono essere molto articolati. Potrebbe succedere che una persona interagisca tramite browser con un webserver che a sua volta si basa su un apiserver per elaborare la propria risposta.


Le app degli smartphone spesso sono programmi che sfruttano le API di un servizio, quindi senza rendervene conto avete sfruttato queste cose molte volte nella vostra esperienza di utenti.

Prima di analizzare le differenze che esistono fra applicazioni web ordinarie e applicazioni che definiscono delle API per l'interazione, e di introdurre il discorso di REST, facciamo un piccolo veloce riepilogo di come funziona HTTP. Il web abbonda di esempi al riguardo, per cui non sarò molto dettagliato.

Il client HTTP (user-agent, può essere un browser o qualsiasi altro programma in grado di fare richieste e ricevere risposte) invia al server HTTP (webserver o apiserver) richieste con un metodo, un percorso, la versione del protocollo usato e una serie di intestazioni. Ad esempio:

GET /kitchens/1/cookers/4.html HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

La risposta del server potrebbe essere una pagina web che dà informazioni sullo stato del fornello 4 della cucina 1.

In questo caso, il metodo è GET, la versione di HTTP è 1.1, il percorso è /kitchen/1/cooker/4.html. L'URI completa è http(s)://kitchen.example.com/kitchen/1/cooker/4.html, ma viene suddivisa in due parti per motivi storici, legati al fatto che originariamente un server web poteva ospitare un solo dominio, per cui era di fatto inutile specificare nella richiesta a quale host ci si voleva rivolgere.

(Non fatevi fuorviare dal fatto che ci sia quel .html alla fine del percorso; le applicazioni moderne usano un modulo chiamato URL-rewriting che consente di mappare tutte le richieste verso specifici moduli applicativi, che possono essere scritti in php, perl, python, ruby o ciò che volete, e l'estensione viene usata per determinare il formato con cui si vuole ottenere la rappresentazione -- se avete sviluppato un'applicazione in php e vi hanno detto che il path deve finire per .php, sappiate che non è effettivamente così: nella configurazione tipica, è il file che deve essere eseguito sul server a dover avere l'estensione .php, e si tratta di una cosa diversa.)

I metodi principali per le richieste sono GET (per ottenere dati), POST (per aggiungere dati), PUT (per aggiornare dati già esistenti, o per aggiungerni di nuovi in una posizione determinata dal client e non dal server) e DELETE (per eliminare dati). L'RFC 5789 ha introdotto anche il metodo PATCH (per aggiornare parte dei dati esistenti relativi a una risorsa).

Ciascuno metodo può possedere o meno le seguenti due caratteristiche:
  • sicurezza (l'esecuzione della richiesta non deve avere effetti collaterali, ossia non deve modificare le informazioni sostanziali sul server, escludendo operazioni di log, incremento di contatori, ecc.);
  • idempotenza (richieste ripetute identiche devono portare al medesimo stato dei dati sul server).
metodosicuroidempotente
GET
PUTno
DELETEno
PATCHnono
POSTnono

I metodi sicuri, come GET, devono poter essere eseguiti senza correre il rischio di causare effetti sul server. Ad esempio, un browser potrebbe precaricare la pagina successiva di una serie di pagine anche senza che l'utente umano faccia clic sul link corrispondente, al fine di velocizzare l'esperienza di navigazione. Oppure un programma di mirroring deve poter seguire tutti i link di una pagina per creare una copia locale del sito remoto senza causare nessun cambiamento di stato.

(Se non avete mai provato a fare il mirroring di un sito web, potete farlo con strumenti semplici come wget, che dispone dell'apposita opzione.)

In contrasto, i metodi non sicuri, come PUT, DELETE e POST, cambiano lo stato delle informazioni sul server. Ad esempio, un'ipotetica richiesta tipo

DELETE /kitchens/2/sauces/1234 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

dovrebbe portare alla cancellazione della salsa con codice 1234 dal database delle salse legato alla cucina 2. È bene quindi che tali metodi vengano eseguiti solo quando un utente umano effettivamente vuole usarli.

I metodi idempotenti sono quelli che, anche se eseguiti ripetutamente con la stessa richiesta, portano allo stesso risultato in termini di stato del server (anche se la risposta potrebbe essere diversa). Tornando all'esempio precedente, alla prima richiesta di cancellazione il server potrebbe rispondere che la cancellazione è avvenuta, e ad una seconda richiesta che la risorsa da cancellare non esiste. In entrambi i casi, comunque, lo stato finale sarà che la salsa con id 1234 non esisterà.

Similmente, l'accensione del fuoco del fornello 4 della cucina 2 potrebbe essere fatto con una richiesta del tipo

PUT /kitchens/2/cookers/4 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

status=on

mentre lo spegnimento potrebbe avvenire con

PUT /kitchens/2/cookers/4 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

status=off

Il metodo POST, che non è idempotente, serve a causare ogni volta l'aggiunta di informazioni sul server. Quindi, ad esempio, tre richieste ripetute di tipo

POST /kitchens/2/sauces HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

type=tomato&price=1.12&quantity=20&mu=l

dovrebbero portare alla registrazione del carico di 60 litri di salsa di pomodoro.

Notate che con il metodo POST non si dovrebbe indicare l'id della risorsa, perché ne stiamo chiedendo al server di aggiungerla, e sarà esso (o, più concretamente, il dbms) a determinare l'id.

Nella risposta ad una richiesta di tipo POST, il server dovrebbe fornire l'URI della risorsa creata, in modo da consentire eventuali modifiche successive, che potranno essere fatte tramite PUT, se si vogliono sostituire tutte le informazioni presenti, oppure tramite PATCH, se si vogliono modificare solo degli attributi.

Un esempio di richiesta di modifica dell'attributo prezzo potrebbe essere la seguente:

PATCH /kitchens/2/sauces/8 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

price=1.14

L'uso del browser pone dei limiti rispetto a quanto consentito, in generale, dall'HTTP. Il limite maggiore è che l'HTML (non l'HTTP) supporta solo i metodi GET e POST (per vari motivi, che non discuteremo in questa sede). Quando fate clic su un link, il browser fa una richiesta di tipo GET. Quando compilate una form e inviate i dati, il metodo di invio dipende dall'attributo "method" dell'elemento form. Ma non si possono avere form con attributo "method" impostato a DELETE o PUT. Di conseguenza, quando si sviluppa un'applicazione web pensata per essere eseguita da un utente umano tramite browser, il tipo di richiesta potrà essere solo GET o POST: il primo verrà usato quando si devono mostrare informazioni (lista dei prodotti, scheda del prodotto, form per la modifica dei dati di un prodotto, ecc.), il secondo quando dei dati devono essere effettivamente acquisiti (inserimento di un nuovo prodotto, cancellazione di un prodotto, modifica di un prodotto, ecc.).

Immaginando di dover presentare un link per la cancellazione di una risorsa, la soluzione migliore da adottare, in un'ottica di miglioramento progressivo, sarebbe di:
  1. implementare la soluzione con un link ordinario ad una pagina di richiesta della cancellazione in cui viene presentata una form con un pulsante per la cancellazione, che userà il metodo POST per l'invio dei dati;
  2. sul server, fare in modo che la richiesta fallisca se il metodo usato non è POST;
  3. utilizzare codice javascript discreto per sostituire il link ordinario con un link che faccia direttamente il POST dei dati, presentando una finestra di conferma di cancellazione;
  4. eventualmente, aggiungere codice AJAX da attivare su richiesta (javascript può fare richieste asincrone che usano i metodi PUT e DELETE).
Nella definizione di API per applicazioni, visto che non si devono considerare le limitazioni dell'HTML, che, come detto, non prevede la possibilità di specificare metodi diversi da GET e POST come attributo dell'elemento form, è possibile sfruttare al meglio le potenzialità di HTTP.

Per fare delle prove, è possibile usare cURL dalla riga di comando. Ad esempio:

curl -X PUT -d status=off http://kitchen.example.com/kitchens/2/cookers/4

consente di fare la richiesta di spegnimento del fuoco del fornello numero 4 della seconda cucina, come visto precedentemente.

Veniamo ora al concetto di API REST.  Innanzitutto, che cos'è REST?

Si tratta di un'architettura software per la gestione di risorse, basata su principi che delineano come le risorse devono essere definite e indirizzate. La sigla sta per "Representational State Transfer", ed è stata coniata da Roy Fielding. Informazioni dettagliate e ulteriori link si possono trovare nella pagina della Wikipedia. In linea teorica REST potrebbe essere usato anche senza HTTP, ma in pratica HTTP e REST viaggiano quasi sempre in coppia, per cui darò per scontato il fatto che si usi HTTP (spesso nella versione sicura, HTTPS). I concetti fondamentali sono questi:
  1. visto che HTTP prevede già l'uso di metodi specifici per le diverse operazioni, ci si concentra sulla risorsa piuttosto che sul cosa deve essere fatto, per la definizione degli URI, in cui si vedranno riferimenti alle cose, non alle azioni;
    tradotto: nel caso di API per un negozio online, nell'URI non si dovrebbero vedere "verbi" tipo "buy", "pay", ecc., ma solo riferimenti alle risorse effettive; per un pagamento pianificheremo un URI come http(s)://example.com/payment/transaction/1234/amount/150, da richiamare con il verbo POST per effettuarlo
  2. le risorse sono identificate da URI, e diversi URI possono puntare alla stessa risorsa;
    tradotto: la stessa notizia potrebbe essere identificata dall'URI http(s)://example.com/news/2013/10/22/italy/web sia dall'URI http(s)://example.com/news/italy/web/latest (in un dato momento)
  3. una risorsa è diversa dalla sua rappresentazione (posso rappresentare i dati della salsa 1234 in un file XML, in un file JSON, con un'immagine, in una pagina HTML) -- sarà lo user-agent a chiedere quale tipo di rappresentazione gli interessa;
    tradotto: la notizia del 22 ottobre identificata dall'URI http(s)://example.com/news/2013/10/22/italy/web potrebbe essere fornita dal server in formato HTML, JSON, XML, ecc., a seconda di ciò che richiede il browser -- su alcune opzioni disponibili ho scritto un altro post in questo blog
  4. il formato con cui il client specifica come vuole ricevere i dati, secondo HTTP, dovrebbe essere specificato nelle intestazioni, in un campo di "negoziazione del contenuto", ma a fini pratici spesso si usa semplicemente l'estensione;
    tradotto: quando uno user agent richiede una risorsa via HTTP, può specificare nella richiesta il formato con cui desidera ottenere le informazioni (es. Accept: text/plain), ma spesso nelle implementazioni si dà la possibilità di usare l'estensione (es. http(s)://example.com/news/2013/10/22/italy/web.txt)
  5. l'interfaccia è uniforme, ossia le cose si fanno sempre allo stesso modo indipendentemente dal tipo di risorsa (questo vale sia per il metodo HTTP da usare, sia per il tipo di risposta che si ottiene dal server);
    tradotto: per eliminare una risorsa si usa sempre il metodo DELETE, ecc.
  6. la risposta del server contiene un codice HTTP e, spesso ma non sempre, un payload (ossia le informazioni richieste);
    tradotto: il server risponde con un codice tipo 200 OK, oppure 404 File not found, ecc; per alcune risposte potrebbero non esserci altre informazioni da rappresentare (contenuti HTML, testi, file binari, ecc.)
  7. un'applicazione REST dovrebbe essere completamente senza gestione di stato da parte del server (quando l'utente interagisce con un server tramite browser, invece, la sessione viene gestita con una collaborazione tra client e server, che generalmente avviene con l'impostazione di cookies di sessione al momento dell'autenticazione; nelle applicazioni REST il server non dovrebbe mantenere informazioni sulla "sessione", e ogni richiesta dovrebbe essere valutata autonomamente rispetto a quelle precedenti e a quelle successive) -- ciò consente di gestire, ad esempio, il bilanciamento di carico tra più server;
    tradotto: ogni richiesta inviata al server dal client dovrebbe essere in qualche modo indipendente dalle precedenti o, meglio, non deve essere il server a farsi carico di tenere traccia di chi è autorizzato a fare qualche cosa, come nel caso delle sessioni che con un browser vengono avviate dall'utente con il login
  8. le rappresentazioni possono essere memorizzate temporaneamente in una cache, anche a più livelli (come quando tra client e server si frappongono dei proxy);
    tradotto: quando un client invia la richiesta ad un server, può avvalersi di server intermediari (proxy), i quali sono autorizzati a memorizzare la risposta del server per un determinato lasso di tempo, in modo da fornirla ad eventuali altri client che la dovessero richiedere
  9. le rappresentazioni fornite dal server dovrebbero contenere collegamenti ipertestuali (URI) che consentono di passare da uno stato all'altro (questo concetto viene indicato come HATEOAS, Hypermedia as the engine of application state, ma è uno dei principi più violati dell'architettura, come spiegato nel video HATEOAS 101);
  10. quando client e server devono comunicare tra loro, è bene che i dati vengano trasmessi utilizzando formati facilmente gestibili da sistemi automatizzati, come JSON e XML (per quanto riguarda JSON, esistono anche delle proposte di standard di fatto, come JSend, in cui si stabilisce come la risposta del server debba contenere dati relativi all'esito della richiesta) -- notate che gli esempi presentati qui sopra sono semplificati in quanto non usano JSON.
Per molti servizi è disponibile abbondante documentazione sulle API REST che si possono utilizzare, ed è sempre una buona idea dare un'occhiata per ottenere ispirazione sulle pratiche correnti. Inoltre, per approfondimenti, consiglio la lettura di due raccolte di buone pratiche: RESTful Best Practices e  Best Practices for Designing a Pragmatic RESTful API.

domenica, giugno 02, 2013

A good cookbook for Yii development

I just finished reading Alexander Makarov's Yii Application Development Cookbook that I found a really valuable resource to keep at hand if you are developing a web application using Yii.
In its thirteen chapters you are provided with lots of recipes that open your eyes on what the framework does for you under the hood, and on what you can do with minimal effort to improve your application.
The book can be really a time-saving resource: by reading the recipes provided, I ended up many times thinking "If I hadn't read this here, I would have reinvented the wheel, should I have faced the same problem described." For instance, I didn't know about decorators, custom input widgets, some methods automatically called as event
handlers, reusable controller actions, etc., and I found out that a very easy implementation of these concepts is actually just a few lines of code away.
Even for things that I already knew before reading the book, I found it very interesting to compare my solutions with what was described in the book, since I always try to write my code "in the right way",
following the best practices, and I am eager for improvements. Ideas and implementations of simple table inheritance, zii widget customization, role based access control, etc., fell for me in this
category.
Last but not least, the book provides very useful hints for the deployment, when one should fine-tune the application in order to improve the performances.
The only things that appeared to be wrong, from my point of view, is the use of "get", "put" and "post" HTTP methods, that in some recipes seem to be used in the wrong way. In a web application you should never allow somebody to delete something by clicking on an ordinary link, and that means that you should check whether the request is done via the "post" method (Yii provides a postOnly filter for that), and the cookbook should stress that. Another point is that in RESTful applications "post" is used to store new data, while "put" to update data already existing, not viceversa (this is already fixed in the errata page).