Nell'articolo di oggi parleremo di come è possibile far comunicare i client JavaScript con i nostri script server scritti in PHP. Per la comunicazione tra client e server in abito Web è pratica ormai consolidata l'utilizzo dell'oggetto XMLHttpRequest (implementato originariamente dalla Microsoft per Internet Explorer ed ormai esteso a qualunque browser conosciuto) e di protocolli specifici per permettere ad entrambi i poli di comprendere i messaggi inviati.
I protocolli utilizzati possono essere di qualunque tipo e natura, ma quello che sta lentamente guadagnandosi spazio tra la moltitudine di soluzioni utilizzabili è senza dubbio JSON. Il protocollo JSON non fa altro che specificare come formato di scambio dei messaggi la normale notazione con cui descriveremo un oggetto in JavaScript. In questo modo i messaggi sono abbastanza compatti e JavaScript può permettersi di interpretarli con grande velocità utilizzando semplicemente la funzione eval()
.
Per PHP sono presenti varie implementazioni per la codifica e la decodifica di oggetti in formato JSON, ed oggi ho deciso di soffermarmi sull'implementazione nativa fornita inizialmente attraverso il repository PECL e, con il rilascio della versione 5.2, come estensione abilitata di default. La scelta è caduta su questa estensione sia perchè la promozione ad estensione di default ne aumenterà esponenzialmente la popolarità sia perchè è un'implementazione estremamente veloce e semplice.
Un'introduzione al protocollo JSON
Il protocollo JSON (di cui sono state anche scritte delle semplici ma complete specifiche) è un formato di scambio dati molto semplice da leggere e generare da qualunque linguaggio di programmazione. Oltre a questo vantaggio, il protocollo risulta abbastanza compatto e di facile lettura anche ad occhio umano, facilitando il debug delle informazioni scambiate tra client e server senza la necessità di strumenti specifici per la decodifica.
JSON permette di codificare tipi di dato semplici come stringhe, interi, numeri a virgola mobile e due strutture complesse: la prima, che in PHP potremmo definire come array associativo o oggetto, è composta da delle coppie chiave-valore dove la chiave è sempre una stringa; la seconda invece è una lista di valori semplici o strutture complesse che in PHP potremmo identificare come un array. Su questa semplice base e prendendo qualche accorgimento da entrambi i poli di comunicazione è possibile scambiare la maggior parte dei dati necessari a JavaScript ed i linguaggi lato server per comunicare.
Vediamo ora un semplice esempio di messaggio JSON; prendiamo in esame una situazione in cui il client deve inviare al server una lista di oggetti selezionati da un utente all'interno di una tabella ed i rispettivi costi calcolati dal codice JavaScript prima dell'invio dei dati:
Listato 1: un messaggio JSON
{
"oggetti" : [
{
"nome" : "Penna stilografica",
"quantita" : 5
},
{
"nome" : "Quaderno a righe",
"quantita" : 3
}
],
"costi" : [ 1.50, 2.50 ]
}
Il messaggio così codificato identifica l'acquisto di cinque penne stilografiche con costo unitario 1.50 e tre quaderni a righe di costo unitario 2.50; possiamo notare come il formato del messaggio non definisca esplicitamente la relazione tra i diversi componenti, e per questo motivo è necessario (come del resto lo è anche quando si utilizzano formati alternativi di scambio) che client e server conoscano la struttura di dato che riceveranno in input.
In JavaScript la struttura dei dati in questione sarebbe stata definita praticamente utilizzando la stessa notazione a parte per gli apici doppi che racchiudono il nome delle chiavi; in PHP invece la struttura sarebbe stata rappresentata in modo leggermente diverso basandosi solamente su gli array. In entrambi i casi il formato è comunque molto semplice e facilmente interpretabile e riproducibile anche manualmente per testare il corretto funzionamento dell'applicazione.
La libreria JSON di PHP
Come già accennato all'inizio dell'articolo, PHP 5.2 viene già distribuito con il supporto nativo abilitato per la libreria JSON; chi non avesse già installato questa nuova versione può comunque testare ed utilizzare la libreria stessa che attualmente è disponibile come package PECL.
Qualunque sia la situazione in cui vi trovate, vi accorgerete subito della semplicità dei prototipi esposti dalla libreria; difatti JSON è una tecnologia che non richiede affatto complessi sistemi per la gestione della comunicazione, e quindi lo sviluppatore della libreria si è limitato ad implementare due sole funzioni:
json_encode( mixed value )
che si occupa di trasformare un valore PHP nella stringa JSON che lo rappresenta;json_decode( string json [, bool assoc] )
che invece effettua l'operazione contraria trasformando una stringa JSON in un valore PHP valido;
Entrambe le funzioni si aspettano che le stringhe in input siano codificate in UTF8, dato che questo set di caratteri è quello standard utilizzato da JavaScript per l'invio e l'interpretazione delle stringhe; per questo motivo è sempre buona norma applicare alle stringhe in input la funzione utf8_encode
affinchè la rappresentazione ottenuta non sia una stringa vuota o comunque una stringa non corretta.
Vediamo ora una semplice applicazione che sfrutti queste due funzioni per comunicare con il browser attraverso XMLHttpRequest. Per codificare gli oggetti JavaScript in stringhe JSON utilizzerò la libreria (chiamata json.js
) reperibile sul sito ufficiale del protocollo JSON; la libreria in questione estende il prototipo dell'oggetto String
e dell'oggetto Object
aggiungendo al primo il metodo parseJSON
per trasformare una stringa in un oggetto, ed al secondo il metodo toJSONString
per svolgere l'operazione inversa.
Nel codice d'esempio genereremo una lista di valori che potranno essere selezionati dall'utente; una volta selezionati verranno automaticamente aggiunti ad un carrello virtuale e verrà aggiornato il totale dei costi. Per comodità, il codice JavaScript sarà compatibile solo con Firefox ed il carrello verrà salvato su un file di testo; non effettuerò controlli su errori per non dilungarmi troppo con codice non strettamente utile per comprendere il sistema.
Per prima cosa creiamo un semplice file (products.php
) in cui includere i prodotti:
Listato 2: il file products.php
<?php
return array(
array("Penna Stilografica", 1.50),
array("Quaderno a righe", 2.50)
);
?>
Successivamente passiamo all'implementazione del codice JavaScript necessario per il funzionamento del sistema:
Listato 3: il file cart.js
function req(method, data, callback)
{
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function()
{
if(xmlhttp.readyState == 4)
callback(xmlhttp.responseText.parseJSON());
}
xmlhttp.open("POST", "server.php?action=" + method, true);
xmlhttp.send((data == null ? "" : data.toJSONString()));
}
function fillCart(cart)
{
var item,
html = cart.length == 0 ? "Non ci sono prodotti" : "";
for(var i = 0; i < cart.length; ++i)
{
item = cart[i];
html += item.quantity + "x <b>" + item.product + "</b> - " + (item.price * item.quantity) + "€<br />";
}
document.getElementById('card').innerHTML = html;
}
function loadCart()
{
req("loadCart", null, function(cart)
{
fillCart(cart);
});
}
function addProduct(id)
{
req("addProduct", { product_id : id }, function(cart)
{
fillCart(cart);
});
}
window.onload = function()
{
loadCart();
}
Il codice dovrà essere salvato all'interno del file cart.js e
si basa sul file json.js
, la libreria JSON citata in precedenza e scaricabile sul sito ufficiale.
Ora passiamo all'implementazione del client (client.php
) che semplicemente visualizzerà una tabella contenente una serie di prodotti e permetterà agli utenti di visualizzare ed aggiornare il carrello:
Listato 4: il file client.php
<html>
<head>
<title>JSON test</title>
<script language="javascript" type="text/javascript" src="json.js"></script>
<script language="javascript" type="text/javascript" src="cart.js"></script>
</head>
<body>
<table style="width: 300px; border: 1px solid black;" border="1">
<tr>
<th>Prodotto</th>
<th>Costo Unitario</th>
<th> </th>
</tr>
<?php
$products = require("products.php");
foreach($products as $id => $product)
{
echo sprintf("
<tr>
<td>%s</td>
<td>%s €</td>
<td>
<input type="button" value="aggiungi" onclick="addProduct(%d)" />
</td>
</tr>
", str_replace(" ", " ", $product[0]), $product[1], $id);
}
?>
</table>
<div id="card">
Non ci sono prodotti nel carrello
</div>
</body>
</html>
La lista è creata utilizzando PHP mentre per il carrello ci appoggiamo direttamente alla funzione loadCart
che verrà chiamata al caricamento della pagina.
Infine passiamo all'implementazione del server (server.php
). Il file in questione esporrà due semplici metodi: addProduct
che aggiungerà un prodotto al carrello e loadCart
che si occuperà di restituire la lista dei prodotti salvati. Per comodità anche il metodo addProduct
restituirà il carrello.
Listato 5: il file server.php
<?php
if(!isset($_GET['action']))
exit();
class Cart
{
private $path;
private $list;
public function __construct($path)
{
$this->path = $path;
$list = array();
if(file_exists($path))
foreach(file($path) as $line)
{
$line = trim($line);
if(strlen($line) == 0)
continue;
list($id, $quantity) = explode("|", $line);
$list[strval($id)] = intval($quantity);
}
$this->list = $list;
}
public function add($id)
{
$id = strval($id);
if(!isset($this->list[$id]))
$this->list[$id] = 0;
$this->list[$id]++;
}
public function save()
{
$fp = fopen($this->path, "w+");
foreach($this->list as $id => $q)
{
fwrite($fp, $id."|".$q."n");
}
fclose($fp);
}
public function getList()
{
$list = array();
$products = require("products.php");
foreach($this->list as $id => $q)
$list[] = array(
"product" => $products[intval($id)][0],
"quantity" => $q,
"price" => $products[intval($id)][1]
);
return $list;
}
}
switch($_GET['action'])
{
case 'addProduct':
$data = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : file_get_contents("php://input");
if(strlen($data) == 0)
exit();
$json = json_decode($data);
$cart = new Cart("cart.txt");
$cart->add($json->product_id);
$cart->save();
echo json_encode($cart->getList());
break;
case "loadCart":
$cart = new Cart("cart.txt");
echo json_encode($cart->getList());
break;
}
?>
Come possiamo notare anche il codice del server è estremamente semplice; unica cosa da sottolineare è il fatto che avremmo potuto utilizzare la funzione json_decode
passando come secondo parametro true al fine di ottenere come oggetto di root un array associativo invece di un'istanza di stdClass
.