venerdì, agosto 30, 2013

PHP - Modelli (template)


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

Livello "vista" tramite modelli (template)

Lo sviluppo che segue la modalità nota come MVC prevede una netta separazione tra modello (accesso ai dati), controllore(autenticazione, gestione del chi può fare cosa, ecc.) e vista (forma di presentazione dei dati).
In linea di massima, il modello e il controllo devono limitarsi a estrarre i dati e a fornirli alla vista in forma di semplici variabili (array e oggetti), già pronti per essere rappresentati.

Nella vista, il fatto che i dati derivino da interrogazione di database, da analisi di file XML, o da qualsiasi altra fonte, è assolutamente ininfluente.

Analogamente, il modello non ha idea dello scopo finale per cui vengono richiesti ed estratti dei dati, e li fornirà in maniera il più possibile grezza.

Il controllore ha lo scopo di capire quale interrogazione deve essere fatta e quale forma di visualizzazione adottare. Ad esempio, vengono chiesti i dati sugli articoli che possono essere visualizzati in forma di:
  • pagina web tabellare
  • pagina web con elenco puntato
  • file CSV
  • documento XML
  • ...

La vista si occupa di formattare i dati nella maniera opportuna. In genere, si predispongono dei template (modelli) e il controllore deciderà quale template utilizzare.

Template in PHP (nativi)

Il modo pìù semplice per prediporre dei template è di usare direttamente PHP.

Ad esempio, il file principale (controller) effettuerà le operazioni di estrazione dei dati e richiamerà un template per la visualizzazione.

<?php
$name='George Smith';
$address= '45th & Harris';
include('template.php');

Il file template.php conterrà codice HTML e codice PHP con sintassi alternativa:

<html>
<head>
<title>User Info</title>
</head>
<body>
<p>User Information:<br />
Name: <?php echo $name ?><br />
Address: <?php echo $address ?>
</p>

</body>
</html>

Il risultato sarà, come ci aspettiamo:

User Information: Name: George Smith
Address: 45th & Harris

Vantaggi:
  • non serve imparare un nuovo "linguaggio"
  • non serve installare nessuna libreria di funzioni
  • nel template si possono usare tutte le funzioni di PHP per la formattazione dei dati, non ci sono limiti
Svantaggi:
  • chi prepara il template deve conoscere (almeno un minimo di) PHP
  • i template non sono portabili tra ambienti di sviluppo diversi
  • i template risultano prolissi e poco comprensibili
  • chi prepara il template può comunque eseguire codice PHP "normale" (con eventuali problemi di sicurezza)


Template con codice specifico

In alternativa, si possono usare strumenti per preparare template indipendenti da PHP. Il codice di questi template in genere viene analizzato e tradotto in codice PHP nativo, con la gestione di una cache per evitare di dover fare questa operazione più volte.
Il linguaggio per template più noto è probabilmente quello predisposto per Smarty. Per avere un'idea del suo funzionamento, si può dare un'occhiata al suo crash course. Ci sono però oltre cinquanta progetti per i template di pagine web, non c'è che l'imbarazzo della scelta. È presente anche una tabella di comparazione nella Wikipedia.

Un esempio di codice è il seguente (basato sul crash course, con l'aggiunta dell'indicazione dei percorsi per le directory e la libreria):

<?php
require_once('../../../lib/Smarty/libs/Smarty.class.php');
// ovviamente il percorso varia a seconda dell'installazione...

$smarty = new Smarty;

$smarty->template_dir = './smarty_dirs/templates';
$smarty->compile_dir = './smarty_dirs/templates_c';
$smarty->cache_dir = './smarty_dirs/cache';
$smarty->config_dir = './smarty_dirs/configs';

$smarty->assign('name', 'george smith');
$smarty->assign('address', '45th & Harris');

$smarty->display('index.tpl');

Il template (file index.tpl) corrisponderà al seguente:

<html>
<head>
<title>User Info</title>
</head>
<body>
<p>User Information:<br />
Name: {$name}<br />
Address: {$address}
</p>

</body>
</html>

Il risultato corrisponde a quello precedente.

Smarty: un esempio con un array

Se al template Smarty passiamo un array, possiamo sfruttare il codice per la gestione dell'iterazione.

Il controller sarà simile al seguente:

<?php
require_once('../../../lib/Smarty/libs/Smarty.class.php');

$smarty = new Smarty;

$smarty->template_dir = './smarty_dirs/templates';
$smarty->compile_dir = './smarty_dirs/templates_c';
$smarty->cache_dir = './smarty_dirs/cache';
$smarty->config_dir = './smarty_dirs/configs';

$pictures=array(
    'mountain.jpg',
    'desert.jpg',
    'sea.jpg',
    'lake.jpg',
    'countryside.jpg',
    );

$smarty->assign('title', 'My pictures');
$smarty->assign('pictures', $pictures);

$smarty->display('smarty2_basic.tpl');

Il template sarà invece fatto così:

<html>
<head>
<title>{$title}</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<h1>{$title}</h1>

<h2>Elenco puntato</h2>

<ul>
{foreach from=$pictures item=picture}
<li>{$picture}</li>
{/foreach}
</ul>

<h2>Testo preformattato</h2>

<pre>
{foreach from=$pictures item=picture}
{$picture}
{/foreach}
</pre>

<h2>Tabella</h2>

<table class="sample">
<tr><th>name</th></tr>
{foreach from=$pictures item=picture}
<tr>
<td>{$picture}</td>
</tr>
{/foreach}
</table>

<h2>Tabella con numero di iterazione</h2>

<table class="sample">
<tr><th>number</th><th>name</th></tr>
{foreach name=picturestable from=$pictures item=picture}
<tr>
<td>{$smarty.foreach.picturestable.iteration}</td>
<td>{$picture}</td>
</tr>
{/foreach}
</table>

<h2>Tabella con colori di sfondo diversi per le righe pari e dispari</h2>

<table class="sample">
<tr><th>name</th></tr>
{foreach name=picturestable from=$pictures item=picture}
<tr>
<td style="background-color: {if $smarty.foreach.picturestable.iteration is even}yellow{else}orange{/if}">{$picture}</td>
</tr>
{/foreach}
</table>

</body>
</html>




Header/Footer o Layout?

Nella predisposizione dei template, si possono usare due strategie: quella di predisporre intestazioni e piè di pagina standard (e di occuparsi dei contenuti) oppure quella di impostare un layout complessivo, in cui è presente un segnaposto per il contenuto specifico.

Header/Footer

Templating basato su Header/Footer (immagine tratta da Practical Symfony)

L'idea di base è che nel template devo incorporare l'intestazione e il piè di pagina. Nella logica di Smarty dovrò scrivere qualcosa tipo:

{include file="header.tpl" title="Main Menu"}
{* qui va il corpo del template *}
{include file="footer.tpl" logo="http://my.example.com/logo.gif"}

Layout

Templating basato su Layout (immagine tratta da Practical Symfony)

L'idea qui è che ci sia un file di layout generico che contiene tutto il codice di base, ad eccezione di quanto comparirà di specifico a seconda dell'azione invocata. 
Nella logica di Smarty avrò un file layout.tpl che contiene il layout generico:

<html>
<head>
<title>{$title}</title>
</head>
<body>
<h1>{$title}</h1>
<div id="content">
{include file="$action.tpl"}
</div>
</body>
</html>

Il file di layout includerà un file specifico a seconda dell'azione indicata dal controller, che avrà la seguente forma:

<?php
require_once('../../../lib/Smarty/libs/Smarty.class.php');
$smarty = new Smarty;

$smarty->template_dir = './smarty_dirs/templates';
$smarty->compile_dir = './smarty_dirs/templates_c';
$smarty->cache_dir = './smarty_dirs/cache';
$smarty->config_dir = './smarty_dirs/configs';

$pictures=array(
    'mountain.jpg',
    'desert.jpg',
    'sea.jpg',
    'lake.jpg',
    'countryside.jpg',
    );

$smarty->assign('title', 'My pictures');
$smarty->assign('pictures', $pictures);
$smarty->assign('action', 'list');

$smarty->display('layout.tpl');
 
Il file list.tpl sarà quello che caratterizza l'azione specifica:

<h2>The list</h2>
<ul>
{foreach from=$pictures item=picture}
<li>{$picture}</li>
{/foreach}
</ul>

Approfondimenti

Nella predisposizione di modelli (template), è bene tenere presente alcuni concetti che possono tornare utili. Qui userò la terminologia usata con Symfony (altri framework potrebbero usare termini diversi per gli stessi oggetti).

Slot

È una parte del layout il cui valore viene deciso nel momento in cui viene predisposto il contenuto del template relativo all'azione specifica. L'esempio comune è quello del testo del titolo della pagina.

Partial

È una sorta di mini-template che serve per rappresentare una parte dei contenuti e che non ha vita autonoma (non viene richiamato direttamente, almeno di solito, ma da un altro template). È utile per poter sfruttare lo stesso codice in diversi template (un po' come si farebbe con una funzione).

Component

È come un template, con la differenza che usa un'azione specifica e coinvolge quindi il livello del controller.

Flash

È una parte di pagina web che contiene un testo che viene visualizzato una sola volta (al successivo GET scompare), grazie all'uso di cookies. Tipicamente viene utilizzato per dare feedback all'utente sulla riuscita o meno di operazioni che richiedevano l'uso di un POST (visto che il browser viene ridiretto ad un GET).

Helper

Funzioni specifiche che agevolano la preparazione di template (ne esistono per rappresentare date, paesi, misure, messaggi internazionalizzati, ecc.).

PHP - Moduli HTML


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

Moduli HTML: come non reinventare la ruota

Una delle cose più comuni nello sviluppo di applicazioni web è di dover predisporre dei moduli per l'inserimento di dati da parte dell'utente.
Più di qualcuno si è cimentato nell'operazione di scrivere il codice HTML necessario, come ad esempio:

<form action="..." method="...">
<input type="text" name="id" value="12" />
</form>

Si tratta naturalmente di una pratica noiosa, passibile di errori, poco pratica rispetto alla manutenibilità del codice, ecc. e quindi, salvo casi particolari, da evitare.

Per non reinventare la ruota, esistono delle alternative:
  • usare librerie di classi già pronte che permettono di generare i moduli
  • usare framework che permettono generazione e gestione di moduli


Classi per la generazione di moduli

Tra le classi più popolari per la generazione di moduli, possiamo citare PEAR::HTML_QuickForm2 e UltimateForm.


HTML_QuickForm2

La documentazione di queste classi non è ancora presente, ma ci si può basare sul codice sorgente, sulla documentazione di HTML_QuickForm e sulla bozza di API.

L'esempio fornito con il pacchetto di installazione porta alla generazione di un modulo in cui compaiono gli elementi qui raffigurati:

Elementi generati con PEAR::HTML_QuickForm2

Fondamentalmente, per generare un modulo è necessario istanziare un oggetto di tipo HTML_QuickForm2, a cui dovremo agganciare i vari elementi, eventualmente raggruppati in fieldset.

require_once 'HTML/QuickForm2.php'; 
 
$form = new HTML_QuickForm2('picture'); 
 
$form->addDataSource(new HTML_QuickForm2_DataSource_Array(array(
    'hiddenId'        => $picture['id'], 
    'textDescription' => $picture['Description'], 
    'selCategory'     => $picture['Category_id'], 
    'textPath'        => $picture['Path'], 
    'textType'        => $picture['Type'], 
    'textWidth'       => $picture['Width'], 
    'textHeight'      => $picture['Height'], 
))); 

$form->addElement('hidden', 'hiddenId');
 
$fsImage = $form->addElement('fieldset')->setLabel('Immagine'); 
$fsImage->addElement( 
    'text', 'textDescription', array('style' => 'width: 300px;'), array('label' => 'Descrizione:') 
); 
$fsImage->addElement( 
    'select', 'selCategory', null, array('options' => $categories, 'label' => 'Categoria:') 
); 

$fsFile = $form->addElement('fieldset')->setLabel('File'); 
$fsFile->addElement( 
    'text', 'textPath', array('style' => 'width: 200px;'), array('label' => 'Percorso:') 
); 
$fsFile->addElement( 
    'text', 'textType', array('style' => 'width: 200px;'), array('label' => 'Tipo:') 
); 

$fsPhoto = $form->addElement('fieldset')->setLabel('Foto'); 
$fsPhoto->addElement( 
    'text', 'textWidth', array('style' => 'width: 200px; text-align: right'), array('label' => 'Larghezza:') 
); 
$fsPhoto->addElement( 
    'text', 'textHeight', array('style' => 'width: 200px; text-align: right'), array('label' => 'Altezza:') 
); 

$fsActions = $form->addElement('fieldset')->setLabel('Azioni'); 
$btnReset = $fsActions->addElement( 
    'reset', 'btnReset', array('value' => 'Reimposta valori') 
); 
$btnSubmit = $fsActions->addElement( 
    'submit', 'btnSubmit', array('value' => 'Salva') 
);

I dati da utilizzare per valorizzare i campi verranno probabilmente estratti da una base di dati, e dovremo decidere cosa fare alla ricezione del POST dell'utente. Il codice diventerà qualcosa di analogo al seguente:

<?php

/* model */

try
{
    /*** connect to SQLite database ***/
    $dbh = new PDO("sqlite2:pictures.sql.db");
   
    $sql = "SELECT id, Description FROM Category ORDER BY RANK;";
    
    $categories[' ']='--seleziona una categoria--';
       
    foreach ($dbh->query($sql, PDO::FETCH_ASSOC) as $row)
    {
      $categories[$row['id']] = $row['Description'];
    }

    $stmt = $dbh->prepare("SELECT * FROM Picture where id = :id");

    $stmt->bindParam(':id', $id);
    
    $id = $_GET['id'];

    if ($stmt->execute())
    {
      $picture = $stmt->fetch(PDO::FETCH_ASSOC);
    }
    
    
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}


/* controller */
// vedi codice precedente

// decisione su cosa fare (se abbiamo avuto un post congeliamo il modulo)
if ('POST' == $_SERVER['REQUEST_METHOD'])
{
  $submittedData=$form->getValue(); 
  $fsActions->removeChild($btnReset); 
  $form->toggleFrozen(true); 
}

/* view */
?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html> 
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <style type="text/css"> 
/* Set up custom font and form width */ 
body { 
    margin-left: 10px; 
    font-family: Arial,sans-serif; 
    font-size: small; 
} 
 
.quickform { 
    min-width: 500px; 
    max-width: 600px; 
    width: 560px; 
} 
 
/* Use default styles included with the package */ 
 
<?php readfile('data/quickform.css') ?> 
    </style> 
    <title>HTML_QuickForm2 example</title> 
  </head> 
  <body> 
<?php 
 
echo $form; 
?>

<?php if ($form->toggleFrozen()): ?>
  <p><a href="picture.php?id=<?php echo $id ?>">Modifica</a></p>
<?php endif ?>

<pre>
<?php print_r($picture) ?>
</pre>
</body> 
</html>

Si dovrebbe ottenere un modulo simile a questo:



Esercizi

  1. Integrare l'uso di HTML_QuickForm2 nell'applicazione di gestione delle immagini, con la possibilità di modificare descrizione e categoria di appartenenza delle fotografie memorizzate nella base di dati.
  2. Gestire in maniera corretta il POST (con un redirect a una richiesta effettuata con metodo GET).
  3. Integrare HTML_QuickForm2 nella propria gerarchia di classi, in modo che per l'oggetto Picture sia possibile avere un metodo generateForm() o equivalente.
  4. Provare a usare le funzioni di validazione lato client e lato server.
  5. Provare altre classi per la generazione di moduli, come UltimateForm.
  6. Dare un'occhiata alla documentazione relativa alla gestione dei moduli in symfony, in particolare l'appendice riguardante iwidget disponibili.

PHP - PDO


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

Cos'è PDO

PDO (Php Data Objects) è un'estensione che fornisce  un'interfaccia coerente per l'accesso a diversi tipi di basi di dati. Si usa specificando quale driver usare per l'accesso alla particolare base di dati che si intende utilizzare.

Driver disponibili

L'elenco dei driver disponibili si può ottenere con:

<?php

print_r(PDO::getAvailableDrivers());

Sul mio calcolatore ottengo:

Array
(
    [0] => mysql
    [1] => odbc
    [2] => pgsql
    [3] => sqlite
    [4] => sqlite2
)

Gli esempi che seguono fanno uso, per semplicità, del driver sqlite2, ma a parte la specificazione iniziale della sorgente dati dovrebbero essere applicabili con qualsiasi tipo di base di dati supportata.

Inserimento di righe

Per inserire righe in una tabella dovremo fare una connessione e poi eseguire una query di tipo insert:

<?php
try
{
    /*** connect to SQLite database and get a handler ***/
    $dbh = new PDO("sqlite2:pictures.sql.db");
    // $dbh = new PDO("mysql:host=localhost; dbname=mydb", $user, $password); // for MySql
    
    $count = $dbh->exec("INSERT INTO Category(Description, Rank) VALUES ('Libri', 10)");
    echo 'Affected rows: ' . $count . "\n";
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

Risultato:

Affected rows: 1

Selezione


<?php
try
{
    /*** connect to SQLite database ***/
    $dbh = new PDO("sqlite2:pictures.sql.db");
    
    $sql = "SELECT * FROM Category";
        
    foreach ($dbh->query($sql, PDO::FETCH_ASSOC) as $row)
    {
      print_r($row);
    }

}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

Risultato:

Array
(
    [id] => 1
    [Description] => Luoghi
    [Rank] => 9
)
Array
(
    [id] => 2
    [Description] => Persone
    [Rank] => 2
)
Array
(
    [id] => 3
    [Description] => Oggetti
    [Rank] => 3
)
Array
(
    [id] => 4
    [Description] => Libri
    [Rank] => 10
)

Inserimenti con Prepared Statement

Supponiamo di dover fare diverse operazioni di inserimento in un'unica tabella. A tale scopo è possibile usare i cosiddetti Prepared Statement, con i seguenti vantaggi:
  1. protezione da SQL injection
  2. maggiore velocità di esecuzione
  3. maggiore comodità di esecuzione (non dobbiamo preoccuparci di fare l'escape degli apici, ad esempio)

<?php
try
{
  /*** connect to SQLite database ***/
  $dbh = new PDO("sqlite2:pictures.sql.db");
  
  /*** we prepare the generic query, using placeholders ***/
  $stmt = $dbh->prepare(
    "INSERT INTO Category (Description, Rank) VALUES (:description, :rank)"
    );
  $stmt->bindParam(':description', $description);
  $stmt->bindParam(':rank', $rank);

  // insert one row
  $description = 'Feste';
  $rank = 13;
  $stmt->execute();

  // insert a second row
  $description = 'Sport';
  $rank = 14;
  $stmt->execute();
  
  // insert a third row
  $description = '€urozona';
  $rank = 15;
  $stmt->execute();

  // insert a fourth row
  $description = "Prove d'orchestra";
  $rank = 16;
  $stmt->execute();

}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

Dopo l'esecuzione, possiamo verificare la presenza in tabella delle righe inserite:

$ echo "select * from Category;" | sqlite pictures.sql.db 
1|Luoghi|9
2|Persone|2
3|Oggetti|3
4|Libri|10
5|Feste|13
6|Sport|14
7|€urozona|15
8|Prove d'orchestra|16

Analogamente, avremmo potuto fare un collegamento basato sulla posizione anziché sul nome del segnaposto (pratica sconsigliabile):

<?php
try
{
  /*** connect to SQLite database ***/
  $dbh = new PDO("sqlite2:pictures.sql.db");
  
  $stmt = $dbh->prepare(
    "INSERT INTO Category (Description, Rank) VALUES (?, ?)"
    );
  $stmt->bindParam(1, $description);
  $stmt->bindParam(2, $rank);

  // insert one row
  $description = "Fuochi d'artificio";
  $rank = 17;
  $stmt->execute();

  // insert another row
  $description = "Monumenti";
  $rank = 18;
  $stmt->execute();
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

Controlliamo l'esito:

$ echo "select * from Category where id>8;" | sqlite pictures.sql.db 
9|Fuochi d'artificio|17
10|Monumenti|18

Selezione tramite Prepared Statement

Anche per le operazioni di selezione possiamo tranquillamente usare delle istruzioni preparate:

<?php
try
{
  /*** connect to SQLite database ***/
  $dbh = new PDO("sqlite2:pictures.sql.db");
  
  $stmt = $dbh->prepare("SELECT * FROM Category where Rank <= ?");

  $stmt->bindParam(1, $max_rank);
  $max_rank = 10;

  if ($stmt->execute())
  {
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
    {
      print_r($row);
    }
  }
  
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

Risultato:

Array
(
    [id] => 1
    [Description] => Luoghi
    [Rank] => 9
)
Array
(
    [id] => 2
    [Description] => Persone
    [Rank] => 2
)
Array
(
    [id] => 3
    [Description] => Oggetti
    [Rank] => 3
)
Array
(
    [id] => 4
    [Description] => Libri
    [Rank] => 10
)

E anche per la selezione vale ovviamente la possibilità di usare dei segnaposto con nomi sensati:

<?php
try
{
  /*** connect to SQLite database ***/
  $dbh = new PDO("sqlite2:pictures.sql.db");
  
  $stmt = $dbh->prepare(
    "SELECT * FROM Category WHERE Description LIKE :description AND Rank <= :max_rank"
    );
    
  $stmt->bindParam(':description', $description);
  $stmt->bindParam(':max_rank', $max_rank);

  $max_rank = 9;
  $description = 'L%';

  if ($stmt->execute())
  {
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
    {
      print_r($row);
    }
  }
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

Risultato:

Array
(
    [id] => 1
    [Description] => Luoghi
    [Rank] => 9
)

Aggiornamenti e cancellazioni

Istruzioni di tipo update e di tipo delete possono essere preparate in maniera analoga.

  $stmt = $dbh->prepare(
    "UPDATE Picture SET Path=:path WHERE id=:id;"
    );

oppure

  $stmt = $dbh->prepare(
    "DELETE FROM Picture WHERE id=:id;"
    );

Gestione degli errori

Nel caso di esecuzione di inserimenti, possiamo controllare l'esito dell'operazione e verificare cosa è andato storto. Si consideri il seguente esempio, in cui tentiamo di inserire una riga in cui il valore per una chiave esterna non è definito:

<?php
try
{
  /*** connect to SQLite database ***/
  $dbh = new PDO("sqlite2:pictures.sql.db");

  $count = $dbh->exec(
    "INSERT INTO Picture(Path, Description, Type) VALUES
      ('pictures/first.jpg', 'A tree', 'jpeg');"
    );
    
  if ($count)
  {
    echo "Elemento inserito\n";
  }
  else
  {
    print_r($dbh->errorInfo());
  }
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

L'esecuzione non effettua nessun inserimento, ma possiamo avere notizie sul motivo:

Array
(
    [0] => 23000
    [1] => 19
    [2] => Picture.Category_id may not be NULL
)

Transazioni

Nel caso si debbano eseguire più query dipendenti l'una dall'altra, è consigliabile racchiuderle in una transazione, in modo che vengano eseguite tutte o nessuna. Si consideri questo esempio, in cui prima viene inserita una categoria e successivamente una immagine con un id duplicato e per questo non valido. Ciò che si vuole ottenere è che se il secondo inserimento fallisce, anche il primo deve essere annullato.

<?php
try
{
  /*** connect to SQLite database ***/
  $dbh = new PDO("sqlite2:pictures.sql.db");

  $dbh->beginTransaction();
  
  // we insert a new category
  $dbh->exec(
    "INSERT INTO Category(Description, Rank) VALUES
      ('Alberi', 18);"
    );
  
  // we get the id, since it comes from an auto-increment field
  $category_id = $dbh->lastInsertId();  
  echo 'Categoria inserita, id=' . $category_id . "\n";
  
  // we insert the picture, using the category_id we got
  $count = $dbh->exec(
    "INSERT INTO Picture(id, Path, Description, Type, Category_id) VALUES
      (1, 'pictures/first.jpg', 'A tree', 'jpeg', $category_id);"
    );
    
  if ($count)
  {
    echo "Elemento inserito\n";
    $dbh->commit();
  }
  else
  {
    print_r($dbh->errorInfo());
    // since we did not insert the picture, we don't want the category either
    $dbh->rollBack();
  }

}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

L'esecuzione ci dice che la categoria è stata inserita, ma poi in effetti non ne troviamo traccia nel database (visto che è stato eseguito il RollBack):

Categoria inserita, id=11
Array
(
    [0] => 23000
    [1] => 19
    [2] => PRIMARY KEY must be unique
)

Controlliamo il database:

$ echo "select * from Category;" | sqlite pictures.sql.db 
...
10|Monumenti|18

Oggetti come risultato

Con fetchObject si possono ottenere i dati direttamente come istanza di una classe:

<?php

class Picture
{
  // settings, variables, functions...
}

try
{
  /*** connect to SQLite database ***/
  $dbh = new PDO("sqlite2:pictures.sql.db");

  $query = "SELECT * FROM Picture";
    
  $result = $dbh->query($query);
  
  while ($picture = $result->fetchObject('Picture'))
    {
      print_r($picture);
    }
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
}

Risultato:

Picture Object
(
    [id] => 1
    [Path] => pictures/first.jpg
    [Description] => A tree
    [Type] => jpeg
    [Width] => 
    [Height] => 
    [Category_id] => 12
)
Picture Object
(
    [id] => 2
    [Path] => pictures/first.jpg
    [Description] => A tree
    [Type] => jpeg
    [Width] => 
    [Height] => 
    [Category_id] => 13
)

Questo approccio è però sconsigliato quando la classe contiene un costruttore che imposta dei valori per alcune proprietà dell'oggetto. Il costruttore dovrebbe essere il primo metodo eseguito dopo la creazione dell'istanza, ma si può facilmente verificare che esso viene invece eseguito dopo che le proprietà lette con la query nel database sono state importate.

Se si usano i prepared statement, si può ovviare forzando il caricamento delle proprietà dopo l'esecuzione del costruttore, come nel seguente esempio:

class Category
{
  private $id;
  private $Description;
  private $Rank;
  private $_status;
  public function __construct($status)
  {
    $this->_status = $status;
  }
}

try
{
    /*** connect to SQLite database and get a handler ***/
    $dbh = new PDO("sqlite2:pictures.sql.db");
}
catch(PDOException $e)
{
    echo 'Error: ' . $e->getMessage() . "\n";
    $dbh = false;
}

if($dbh)
{
  $stmt = $dbh->prepare("SELECT * FROM Category");
  $stmt->setFetchMode(
     PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 
     'Category', 
     array(1)
     );

  if ($stmt->execute())
  {
    while ($row = $stmt->fetch())
    {
      print_r($row);
    }
  }
}

Risultato:

Category Object
(
    [id:Category:private] => 1
    [Description:Category:private] => Luoghi
    [Rank:Category:private] => 9
    [_status:Category:private] => 1
)
Category Object
(
    [id:Category:private] => 2
    [Description:Category:private] => Persone
    [Rank:Category:private] => 2
    [_status:Category:private] => 1
)
Category Object
(
    [id:Category:private] => 3
    [Description:Category:private] => Oggetti
    [Rank:Category:private] => 3
    [_status:Category:private] => 1
)
Category Object
(
    [id:Category:private] => 4
    [Description:Category:private] => Libri
    [Rank:Category:private] => 10
    [_status:Category:private] => 1
)

Nel codice, si può notare che viene impostata la modalità di recupero dei dati (metodo setFetchMode) indicando di usare sia la modalità FETCH_CLASS sia quella FETCH_PROPS_LATE. È anche possibile impostare un quarto parametro, dopo il nome della classe da istanziare, in forma di array di valori da passare al costruttore.