venerdì, agosto 30, 2013

PHP - MySQL


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

Creazione di un utente e di un database

Per poter accedere a un database MySQL è necessario godere di adeguati privilegi.

Se vogliamo creare un utente sym con privilegi completi per tutti i database il cui nome inizia con sym_, dovremo utilizzare (ad esempio con l'interfaccia web phpMyAdmin):

-- Codice SQL
CREATE USER 'sym'@'localhost' IDENTIFIED BY 'ALpBtawuKws6fSbG';


GRANT USAGE ON * . * TO 'sym'@'localhost' IDENTIFIED BY 'ALpBtawuKws6fSbG' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ;

GRANT ALL PRIVILEGES ON `sym\_%` . * TO 'sym'@'localhost';

Un nuovo database, ad esempio sym_mediamanager, potrà poi essere creato con:

CREATE DATABASE `sym_mediamanager` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

A questo punto abbiamo a disposizione le informazioni necessarie per l'accesso al database, che potremo scrivere in un file di configurazione. Ad esempio, potremmo preparare un file db.ini simile al seguente:

USER=sym
PASSWORD=ALpBtawuKws6fSbG
SERVER=localhost
DB=sym_mediamanager

Creazione delle tabelle

Possiamo creare le tabelle mediante phpMyAdmin o con altri strumenti. Immaginiamo di voler creare due tabelle come le seguenti:

Schema relazionale (realizzato con wwwsqldesigner)

Le istruzioni necessarie saranno simili alle seguenti:

-- Codice SQL-- ---
-- Globals
-- ---

-- SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';
-- SET FOREIGN_KEY_CHECKS=0;

-- ---
-- Table 'Category'
-- 
-- ---

DROP TABLE IF EXISTS `Category`;
        
CREATE TABLE `Category` (
  `id` INTEGER NOT NULL AUTO_INCREMENT,
  `Description` VARCHAR(255) NOT NULL,
  `Rank` INTEGER NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
);

-- ---
-- Table 'Picture'
-- 
-- ---

DROP TABLE IF EXISTS `Picture`;
        
CREATE TABLE `Picture` (
  `id` INTEGER AUTO_INCREMENT,
  `Path` VARCHAR(255) NOT NULL,
  `Description` VARCHAR(255) DEFAULT NULL,
  `Type` VARCHAR(10) DEFAULT NULL,
  `Width` INTEGER DEFAULT NULL,
  `Height` INTEGER DEFAULT NULL,
  `Category_id` INTEGER NOT NULL,
  PRIMARY KEY (`id`)
);

-- ---
-- Foreign Keys 
-- ---

ALTER TABLE `Picture` ADD FOREIGN KEY (Category_id) REFERENCES `Category` (`id`);

-- ---
-- Table Properties
-- ---

ALTER TABLE `Category` ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
ALTER TABLE `Picture` ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

Notare che le tabelle devono essere di tipo InnoDB per poter supportare l'integrità referenziale e le transazioni.

Inserimento di dati nelle tabelle

L'inserimento si ottiene con una query di tipo Insert:

<?php
$dbinfo=parse_ini_file('db.ini');
$conn = new mysqli($dbinfo['SERVER'], $dbinfo['USER'], $dbinfo['PASSWORD'], $dbinfo['DB']);

if ($mysqli_connect_error){
    die("Connessione al DB fallita: " . mysqli_connect_error());
    }

$query="INSERT INTO Category (Description, Rank) VALUES ('Paesaggi', 1)";

if ($conn->query($query))
{
    echo "Inserimento riuscito\n";
}
else
{
    echo "Inserimento fallito\n";
}

$conn->close();

Estrazione di dati

Quando si esegue una query di selezione si può decidere di ottenere i risultati in forma di array con indici numerici (fetch_row), di array associativo (fetch_assoc) o di oggetto (fetch_object):

<?php

$dbinfo=parse_ini_file('db.ini');

$conn = new mysqli($dbinfo['SERVER'], $dbinfo['USER'], $dbinfo['PASSWORD'], $dbinfo['DB']);

if ($mysqli_connect_error
{

    die("Connessione al DB fallita: " . mysqli_connect_error());
}

echo "Connesso al DB " . $conn->host_info . "\n";

$result = $conn->query("SELECT * FROM Category;");

while ($row = $result->fetch_row())
{
    print_r($row);
}

$result->free();
$conn->close();

Risultato:

Array
(
    [0] => 2
    [1] => Paesaggi
    [2] => 1
)
Array
(
    [0] => 3
    [1] => Persone
    [2] => 2
)

Usando $row = $result->fetch_assoc() otteniamo array associativi:

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


Usando invece $row = $result->fetch_object() otteniamo:

stdClass Object
(
    [id] => 2
    [Description] => Paesaggi
    [Rank] => 1
)
stdClass Object
(
    [id] => 3
    [Description] => Persone
    [Rank] => 2
)

È naturalmente possibile specificare quale classe usare per istanziare l'oggetto ottenuto.

Query multiple

È possibile comporre una query multipla ed eseguirla per ottimare le prestazioni. Ad esempio:

$query="
SELECT Description FROM Category;
SELECT Rank FROM Category;
";

if ($conn->multi_query($query))
{
  do
  {
    if ($result=$conn->store_result())
    {
      while ($row = $result->fetch_assoc())
      {   
        print_r($row);
      }
      $result->close();
    }
  } while ($conn->next_result());
  $conn->close();
}

Risultato:

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

Prepared statements

È possibile effettuare query di inserimento e di selezione usando delle istruzioni a cui vengono agganciate automaticamente alcune variabili (binding). Sebbene sia possibile farlo direttamente con MySQL, affronteremo l'argomento nella lezione in cui parleremo di PDO.

Implementazione tramite classe specifica

Una soluzione per gestire i dati di una tabella potrebbe essere di definire una classe specifica. Si veda questo esempio:

<?php

$dbinfo=parse_ini_file('db.ini');

class InvalidDBConnectionException extends Exception{};

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

    function __toString()
    {
        return sprintf('[%d] %s (rank: %d)', $this->id, $this->Description, $this->Rank);
    }

    function __construct(Mysqli $conn)
    {
        $this->conn=$conn;
    }

    function setData($info)
    {

        /* use an array as input */
       $this->id = $info['id'];
       $this->Description = $info['Description'];
       $this->Rank = $info['Rank'];
    }

    function retrieveData($id)
    {

        $query="SELECT * FROM Category WHERE id=$id";
        $result=$this->conn->query($query);
        if ($result->num_rows==1)
        {
           $row=$result->fetch_assoc();
           $this->setData($row);
           return true;
        }
        else
        {
           return false;
        }
        $result->free_result();
    }
}

$db = new mysqli($dbinfo['SERVER'], $dbinfo['USER'], $dbinfo['PASSWORD'], $dbinfo['DB']);

$testvalues=array(1, 2, 3, 4, 5);

foreach($testvalues as $id)
{
  $category=new Category($db);
  if ($category->retrieveData($id))
  {
    echo $category . "\n";
  }
  else
  {
    echo "--> La categoria con id $id non è presente nel DB\n";
  }
}
$db->close();

Risultato:

--> La categoria con id 1 non è presente nel DB
[2] Paesaggi (rank: 1)
[3] Persone (rank: 2)
--> La categoria con id 4 non è presente nel DB
--> La categoria con id 5 non è presente nel DB

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).

PHP - Basi di dati


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

Basi di dati

Un'applicazione con un minimo di complessità avrà bisogno sicuramente di memorizzare dei dati e di sfruttare quindi delle basi di dati.
Un classico delle applicazioni web sviluppate con PHP si appoggiano a MySQL (e deriva da questo il fatto che spesso si parli di LAMP, Linux-Apache-MySQL-PHP), ma in realtà sono disponibili moltissimi altri tipi di database.

Accesso ai dati

Per accedere ai dati in una base di dati si può far uso:
  • degli strumenti nativi offerti da PHP per il particolare tipo di basi di dati (funzioni native per MySQL, per SQLite, ecc.);
  • di strumenti che offrono un livello di astrazione per accedere ad un qualsiasi tipo di base di dati tra quelli supportati, permettendo le operazioni basilari (ad esempio, PDO);
  • di strumenti che consentano la generazione di codice PHP per la gestione degli oggetti associati ai record nel database (ORM, Object-Relational Mapper, come Propel o Doctrine);
  • di framework che, appoggiandosi a un ORM, permettono di gestire secondo il pattern MVC i dati.

Gli esempi di queste lezioni

In questo corso vedremo degli esempi di base con il codice nativo per SQLite, per MySQL e tramite PDO. L'uso degli ORM e dei framework di sviluppo è fortemente consigliato, ma non ne parleremo perché la documentazione in proposito è ottima e abbondante e non vale la pena di riprenderla qui.
Per le operazioni che possono essere effettuate sia con un'interfaccia procedurale sia con una interfaccia orientata agli oggetti, privilegeremo quest'ultima.

PHP - Analisi di un file XML


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

Feed Atom come esempio di file XML

Prefiggiamoci come scopo quello di elaborare, per uso strettamente personale, un feed Atom come quello dell'ANSA relativo al Friuli-Venezia Giulia.

La struttura del file è la seguente:

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link rel="self" type="application/rss+xml" href="http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml"></atom:link>
<title>News di 01. Friuli Venezia Giulia - ANSA.it</title>
<link>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia.shtml</link><description>Updated every day - FOR PERSONAL USE ONLY</description>
<language>it</language>
<copyright>Copyright: (C) ANSA, http://www.ansa.it/web/static/disclaimer.html</copyright>
<item>
  <title><![CDATA[Mostre:Enzensberger...]]></title>
<description]]></title>

  <description><![CDATA[Per avvicinare...]]></description>
  <link>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/14/visualizza_new.html_1733110800.html</link>
  <pubDate>14 Mar 2010 18:44:00 +0100</pubDate>
  <guid>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/14/visualizza_new.html_1733110800.html</guid>
</item>
<item>
  <title><![CDATA[Regioni: sanita', in...]]></title>
  <description><![CDATA[Nel 2008 Molise...]]></description>
  <link>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732935087.html</link>
  <pubDate>12 Mar 2010 18:47:00 +0100</pubDate>
  <guid>http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732935087.html</guid>
</item>
</channel>
</rss>

Come si vede, esiste una parte introduttiva, con il nome della testata, le informazioni sul copyright, ecc., seguita da una serie di articoli (item), per i quali viene riportato il titolo, una descrizione, un link, la data di pubblicazione e un identificativo univoco della risorsa (guid).

Prima versione: lettura semplice con simpleXml

In una prima versione, per capire se tutto funziona, possiamo scrivere codice di questo genere:

<pre>
<?php
$feed="http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml";
$xml=simplexml_load_file($feed);
echo "Title: " . chop($xml->channel->title) . "\n"; 
foreach ($xml->channel->item as $item)
{
  echo "title: " . $item->title . "\n";
  echo "  description: " . $item->description . "\n";
  echo "  link: " . $item->link . "\n";
}

che ci consente di ottenere:
Title: News di 01. Friuli Venezia Giulia - ANSA.it
title: Mostre:Enzensberger...
  description: Per avvicinare...
  link: http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/14/visualizza_new.html_1733110800.html
title: Regioni: sanita', in...
  description: Nel 2008 Molise...
  link: http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732935087.html
title: Roma: spara...
  description: Arrestato...
  link: http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/2010/03/12/visualizza_new.html_1732933202.html

Seconda versione: MVC

Secondo la buona pratica della separazione tra modello (cicciotto), controller snello) e view (come serve), si potrebbe ripensare il codice in questo modo:

<?php
/* model */
class Article
{
  private
    $_title,
    $_description, 
    $_link,
    $_pubDate;

  public function __construct($title, $description='', $link='', $pubDate='')
  {
     $this->_title=$title;
     $this->_description=$description;
     $this->_link=$link;
     $this->_pubDate=$pubDate;
  }
  public function getTitle()
  {
    return $this->_title;
  }
  public function getLink()
  {
    return $this->_link;
  }
  public function getDescription()
  {
    return $this->_description;
  }
  public function getPubDate()
  {
    return $this->_pubDate;
  }
}

class NewsReader
{
  private
    $_feed,
    $_xml,
    $_articles;

  public function __construct($feed)
  {
    $this->_feed=$feed;
    $this->_xml=simplexml_load_file($this->_feed);
    $this->findArticles();
  }
  private function getXML()
  {
    return $this->_xml;
  }
  private function findArticles()
  {
    $this->_articles=Array();

    foreach ($this->getXML()->channel->item as $item)
    {
      $this->_articles[]=new Article(
        $item->title,
        $item->description,
        $item->link,
        $item->pubDate
      );
    }
  }

  public function getTitle()
  {
    return $this->getXML()->channel->title;
  }

  public function getArticles()
  {
    return $this->_articles;
  }
}


/* controller */

$feed="http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml";
$newsreader = new NewsReader($feed);

/* view */
?>
<html><head>...</head>
<body>
<h1><?php echo $newsreader->getTitle() ?></h1>

<ul>
<?php foreach($newsreader->getArticles() as $article): ?>
   <li><a href="<?php $article->getLink() ?>"><?php echo $article->getTitle() ?></a><br />
   <em><?php echo $article->getDescription() ?></em>
   <span class='pubdate'><?php echo $article->getPubDate() ?></span>
   </li>
<?php endforeach ?>
</ul>
</body>
</html>

Altre cose su XML

Esistono altri due modi per analizzare codice XML con PHP: SAX (che funziona ad eventi) e DOM (che mappa gli elementi in un albero in memoria, senza trasformarli in proprietà come fa SimpleXML).

Con SimpleXML ci possono essere alcuni problemi che riguardano la presenza di namespaces. Si veda l'articolo Using SimpleXML To Parse RSS Feeds di Stuart Hebert (o altri analoghi) per informazioni al riguardo.

Se un elemento contiene valori rappresentati con CDATA, bisogna caricare il file specificando di non interpretarne il codice (FIXME).

Esempio:

<pre>
<?php
$feed='http://www.ansa.it/web/notizie/regioni/friuliveneziagiulia/friuliveneziagiulia_rss.xml';
$xml=simplexml_load_file($feed, 'SimpleXMLElement', LIBXML_NOCDATA);
$items=$xml->xpath('channel/item');
foreach($items as $item)
{
    echo $item->title . "\n";
}

Se in un file XML ci sono più elementi fratelli (sibling), la proprietà con il nome dell'elemento rappresenta il primo dei fratelli, ma si possono specificare gli altri aggiungendo l'indice (FIXME):

echo $xml->channel->item  // primo elemento
echo $xml->channel->item[0]  // primo elemento
echo $xml->channel->item[1]  // secondo elemento

Esercizi

  1. Fare delle prove con altri tipi di feed
  2. Fare delle prove con altri tipi di file XML
  3. Fare delle prove con xpath

PHP - File di configurazione


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

Dove mettere i file di configurazione

I parametri necessari per la configurazione/personalizzazione dell'applicazione andrebbero posti in uno o più file accessibili al server web, ma - se possibile - non al pubblico.
Per esempio, i file dell'applicazione potrebbero essere in /var/www/myapp, mentre i file di configurazione in /etc/myapp.
Ovvii motivi di sicurezza dicono che i file - soprattutto se non in formato php - non vanno messi in una directory accessibile.
Vale anche la pena di notare che ci potrebbero essere problemi in ambienti di shared hosting (sulla stessa macchina, il server web può leggere i file di configurazione di un altro dominio).

Tipi di file di configurazione

I tipi più comuni di file di configurazione sono:
  • puro PHP;
  • XML;
  • INI;
  • YAML.

File in puro PHP

Un file come questo config.inc.php

<?php
$CONFIG['directory_name'] = 'stuff';
$CONFIG['default_type'] = 'png';

potrebbe essere incluso in questo modo:

require_once('config.inc.php');
print_r($CONFIG);
echo "directory name: " . $CONFIG['directory_name'] . "\n";

File in formato XML

Il file è come questo:

<?xml version='1.0'?>
<config>
<directory_name>stuff</directory_name>
<default_type>png</default_type>
</config>

Per leggerlo scriveremo:

$conf=simplexml_load_file('config.xml');
print_r($conf);
echo "directory name: " . $conf->directory_name . "\n";

File in formato INI

Il file è come questo:

;CONFIGURATION FOR MY APPLICATION
directory_name = stuff
default_type = png

e per leggerlo scriveremo: 

$conf=parse_ini_file('config.ini');
print_r($conf);
echo "directory name: " . $conf['directory_name'] . "\n";

File in formato YAML


I file in formato YAML non sono (ancora) supportati nativamente da PHP (bisogna installare un estensione PECL o librerie apposite).

PHP - Intestazioni HTTP


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

Introduzione

Spesso capita di dover indicare esplicitamente quali intestazioni HTTP devono essere inviate prima di servire un determinato contenuto.
La funzione header permette di farlo, ma è bene incapsularla in qualche classe che gestisca la questione in maniera strutturata.
Come esempio di partenza, consideriamo il caso in cui si voglia servire un file contenente il logo della nostra organizzazione:

<?php
header('Content-Type: image/png');
header('Content-Length: 5727');
readfile('logo.png');

Un errore comune è di avere qualche carattere prima dell'inizio del codice PHP, che porta al seguente esito:

Warning: Cannot modify header information - headers already sent by (output started at ...)

File not found

Se si vuole gestire un errore di tipo 404 (file not found), si può inviare un'intestazione specifica:

<?php
header('HTTP/1.0 404 Not Found');
header('Content-Type: text/html; charset=utf-8');
?>
<h1>Error 404: File not found</h1>

Esercizio

Predisporre uno script image.php che mostri una determinata immagine presente nel filesystem del server web.
In una prima versione, lo script verrà invocato con un URL simile al seguente:

http://../image.php?file=logo

Lo script dovrà verificare se il file logo.png esiste o meno in una determinata directory e servirlo se lo trova ed è leggibile (con la corretta impostazione della dimensione del file); altrimenti, dovrà restituire una pagina 404.

Predisporre una pagina web picture.php, richiamabile con un URL simile al seguente,

http://../picture.php?file=logo

che produca codice HTML valido (usare validator.w3.org per controllare) per mostrare l'immagine, sfruttando lo script precedente:

...
<html>
<head><title>...</title></head>
<body>
<img src="image.php?file=logo" alt="..." width="..." height="..." />
</body>
</html>

Il testo da riportare nell'attributo alt corrisponderà al nome del file (senza l'estensione), mentre larghezza e altezza dipenderanno dalle dimensioni effettive dell'immagine.

Nel caso di file non presente, deve essere restituita una pagina con l'indicazione che il file non esiste.

Definire una classe per l'immagine (quali saranno le funzioni membro?), una per la WebResponse, con funzioni tipo setHttpHeader(), una per la WebRequest, con funzioni tipo getParameter().

Varianti ed integrazioni

A partire dal codice precedente:
  1. fare in modo che image.php restituisca una pagina 404 quando il referer non è picture.php;
  2. gestire un URL più SEO, come ad esempio http://.../picture.php/file/logo.png;
  3. costruire i casi di test per la classe che gestisce l'immagine;
  4. far sì che, in qualche caso particolare (ad esempio, se la richiesta avviene nei secondi dispari) l'immagine venga restituita leggermente alterata (ad esempio, con un bordo rosso di due pixel)

PHP - Gestione degli errori


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

Introduzione

La gestione degli errori ha a che fare con cosa si vuole che succeda nel caso si verifichino errori durante l'esecuzione. Esistono diversi livelli di errore (notice, warning, parse, error) che possono essere gestiti.

Funzioni utili

Tra le molte funzioni utili, segnalo error_log, che fa scrivere ciò che si desidera nel file di log del server web.

$name='Mario';
error_log(
  sprintf('name is currently "%s" (line %d)',
    $name,
    __LINE__
    )
  );

fa sì che venga scritto nel file di log:

[...date...] [error] [client 127.0.0.1] name is currently "Mario" (line 8)