venerdì, agosto 30, 2013

PHP - SQLite


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

SQLite

SQLite è il database più diffuso al mondo, è nativamente a disposizione degli sviluppatori che usano PHP5 e non necessita di connessioni a database esterni. È ottimo per fare degli esperimenti e anche per applicazioni professionali che necessitano delle suemolte caratteristiche positive e non hanno bisogno di caratteristiche che invece gli mancano.

Un database SQLite è costituito da un unico file binario contenente tutto ciò che serve. I permessi per l'accesso al database corrispondono a quelli per l'accesso al file.

Il database è accessibile tramite delle API pubbliche che hanno consentito l'implementazione di librerie per diversi linguaggi di programmazione e anche per semplici programmi da usare sulla riga di comando, oppure integrati nel browser come SQLite Manager.

SQLite supporta transazioni e trigger, ma non i i vincoli di integrità referenziale, che però possono essere ottenuti predisponendo degli appositi trigger (esiste anche un apposito generatore).

Creazione del DB

Supponiamo di voler creare un database con due tabelle come quelle qui rappresentate:

Schema del database di esempio (ottenuto con wwwsqldesigner)

Nota: nella progettazione di basi di dati, spesso i nomi delle tabelle vengono impostati al plurale e i campi al singolare (avremmo quindi Pictures e Categories, ma Category_id). Visto che, come vedremo, ci sarà una corrispondenza abbastanza diretta tra tabelle e classi, qui useremo, per semplicità (l'approccio è pragmatico, no?), sempre i nomi al singolare.

Il codice sarà simile al seguente:

<?php

try
{
  $db = new SQLiteDatabase('pictures.sql.db', 0666);
  // il file dovrebbe essere in un punto inaccessibile agli utenti

  $query="
CREATE TABLE Picture (
id INTEGER AUTOINCREMENT NOT NULL,
Path TEXT NOT NULL ,
Description TEXT DEFAULT NULL,
Type TEXT DEFAULT NULL,
Width INTEGER DEFAULT NULL,
Height INTEGER DEFAULT NULL,
Category_id INTEGER NOT NULL ,
PRIMARY KEY (id)
);

CREATE TABLE Category (
id INTEGER AUTOINCREMENT NOT NULL,
Description TEXT NOT NULL DEFAULT 'NULL',
Rank INTEGER NOT NULL ,
PRIMARY KEY (id)
);
";

  $db->query($query);

  unset($db); 
}
catch (SQLiteException $e)
{
  echo 'Errore: ' . $e->getMessage() . "\n";
  die();
}
Nota: il codice per la query è stato ottenuto direttamente da wwwsqldesigner.

Inserimento di record

Per inserire record in una tabella sarà sufficiente effettuare delle query di tipo Insert:

<?php
try
{
  $db = new SQLiteDatabase('pictures.sql.db');

  $query="
INSERT INTO Category(Description, Rank) VALUES('Luoghi', 1);
INSERT INTO Category(Description, Rank) VALUES('Persone', 2);
INSERT INTO Category(Description, Rank) VALUES('Oggetti', 3);
";

  $db->query($query);

  unset($db);

}
catch (SQLiteException $e)
{
  echo 'Errore: ' . $e->getMessage() . "\n";
  die();
}

Dalla riga di comando possiamo vedere il risultato (shell bash sotto Linux):

$ echo "SELECT * FROM Category;" | sqlite pictures.sql.db 
1|Luoghi|1
2|Persone|2
3|Oggetti|3

Ottenimento di record

Per ottenere i record dobbiamo fare una query di selezione:

<?php

try
{
  $db = new SQLiteDatabase('pictures.sql.db');
  $query="SELECT * FROM Category;";

  $result = $db->arrayQuery($query, SQLITE_ASSOC);
  print_r($result);
  unset($db);
}
catch (SQLiteException $e)
{
  echo 'Errore: ' . $e->getMessage() . "\n";
  die();
}

Quello che otteniamo è:

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

    [1] => Array
        (
            [id] => 2
            [Description] => Persone
            [Rank] => 2
        )

    [2] => Array
        (
            [id] => 3
            [Description] => Oggetti
            [Rank] => 3
        )

)

Come si vede, abbiamo usato per la funzione arrayQuery la costante SQLITE_ASSOC, che fa sì che ci venga restitutito un array associativo per ogni record. Avremmo potuto optare anche per SQLITE_NUM o SQLITE_BOTH.

Tutti i risultati (tutte le tuple) sono stati posti automaticamente in un array. In alternativa, potremmo desiderare di ottenere solo un handle all'insieme dei risultati, da passare in rassegna uno per uno:

<?php
try
{
  $db = new SQLiteDatabase('pictures.sql.db');
  $query="SELECT * FROM Category;";

  $result = $db->query($query);

  while($record = $result->fetch(SQLITE_ASSOC))
  {
    print_r($record);
  }

  unset($db);
}
catch (SQLiteException $e)
{
  echo 'Errore: ' . $e->getMessage() . "\n";
  die();
}

Risultati come oggetti

Si potrebbe desiderare anche che i risultati della query servano ad istanziare degli oggetti di una determinata classe:

<?php

class Category
{
  private
    $id,
    $Description,
    $Rank;
}

try
{
  $db = new SQLiteDatabase('pictures.sql.db');
  $query="SELECT * FROM Category;";
  $result = $db->query($query);

  while($category = $result->fetchObject('Category', null))
  {
    print_r($category);
  }

  unset($db);
}
catch (SQLiteException $e)
{
  echo 'Errore: ' . $e->getMessage() . "\n";
  die();
}

Risultato:

Category Object
(
    [id:private] => 1
    [Description:private] => Luoghi
    [Rank:private] => 1
)
Category Object
(
    [id:private] => 2
    [Description:private] => Persone
    [Rank:private] => 2
)
Category Object
(
    [id:private] => 3
    [Description:private] => Oggetti
    [Rank:private] => 3
)

La funzione fetchObject() permette di impostare anche una serie di parametri da passare al costruttore della classe. Potremmo sfruttare questa caratteristica per passare un riferimento al database di provenienza dell'oggetto (che ci tornerà utile):

<?php

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


try
{
  $db = new SQLiteDatabase('pictures.sql.db');
  
  $query="SELECT * FROM Category;";

  $result = $db->query($query);

  while($record = $result->fetchObject('Category', array($db)))
  {
    print_r($record);
  }

  unset($db);

}
catch (SQLiteException $e)
{
  echo 'Errore: ' . $e->getMessage() . "\n";
  die();
}

Setters e Getters impliciti

Già che ci siamo, introduciamo il concetto di setter e getter implicito, ottenuto con l'overloading delle funzioni standard __set() e__get()

<?php

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

  function __get($property)
  {
    return $this->$property;
  }
  
  function __set($property, $value)
  {
    if ($property=='Rank')
    {
      if ($value<0)
      {
        throw new InvalidArgumentException('Not a valid value: ' . $value);
      }
    }

    $this->$property = $value;
  }
}

try
{
  $db = new SQLiteDatabase('pictures.sql.db');
  $query="SELECT * FROM Category WHERE Description = 'Luoghi';";

  $catPlaces = $db->query($query)->fetchObject('Category', array($db));

  $catPlaces->Rank = 7; // viene richiamata la funzione __set()

  print_r($catPlaces);

  unset($db);
}
catch (SQLiteException $e)
{
  echo 'Errore: ' . $e->getMessage() . "\n";
  die();
}

Funzione save()

Un oggetto della classe Category ha a disposizione tutto ciò che serve per salvare se stesso in caso di modifiche ai suoi dati. Aggiungendo la funzione membro save() alla classe:

class Category
{
  // ..
  public function save()
  {
    $query=sprintf(
      'UPDATE Category SET Description="%s", Rank=%d WHERE id = %d',
      $this->Description, $this->Rank, $this->id);
      
    $this->db->query($query);
  }
}

possiamo scrivere delle istruzioni come le seguenti:

  $query="SELECT * FROM Category WHERE Description = 'Luoghi';";
  $catPlaces = $db->query($query)->fetchObject('Category', array($db));
  $catPlaces->Rank = 9;
  $catPlaces->save();

e ottenere l'aggiornamento del record nella tabella:

$ echo "SELECT * FROM Category;" | sqlite pictures.sql.db 
1|Luoghi|9
2|Persone|2
3|Oggetti|3

Setters e Getters espliciti

L'impostazione di setters e getters espliciti consente, fra le altre cose, l'uso di un'interfaccia fluente:

class Category
{
  // ...

  public function setRank($value)
  {
    $this->Rank = $value;
    return $this;
  }

  public function setDescription($value)
  {
    $this->Description = $value;
    return $this;
  }
}

Nel codice della funzione chiamante potremo quindi scrivere:

// ...
$catPlaces = $db->query($query)->fetchObject('Category', array($db));

echo "Prima della cura\n";
print_r($cat_places);

$catPlaces
->setRank(9)
->save();

Esercizi

  1. Fare in modo che la funzione save() effettui un operazione di Insert quando l'oggetto è stato creato direttamente e di Updatequando invece proviene dal DB.
  2. Scrivere un'applicazione che legge una directory contenente delle immagini di vario tipo (jpeg, png, gif), recupera le informazioni su di esse, attribuisce una categoria casuale (o determinata in base a qualche criterio) e scrive le informazioni nel DB.
  3. Scrivere un'applicazione, secondo il pattern MVC, che mostra in una pagina web tutte le immagini di una determinata categoria.
  4. Aggiungere all'applicazione la possibilità di cambiare le informazioni associate ad un'immagine (descrizione e categoria di appartenenza), usando il metodo POST per la conferma (con redirezione alla pagina di dettaglio dopo che l'aggiornamento è stato effettuato).

Nessun commento:

Posta un commento