martedì 22 gennaio 2008

Estendere il model con nuovi metodi

ovvero
visualizzare al meglio le colonne del proprio DB

Il mio problema di oggi è il seguente:
Ho una tabella che contiene una colonna che si chiama isChecked di tipo INT che può contenere 0 oppure 1.
Il risultato che voglio ottenere è quello di chiamare un metodo del tipo

$table->status()

che restituisca "verificato" oppure "da verificare" a seconda del valore contenuto nella colonna.
Naturalmente il tutto deve essere fatto in modo naturale ovvero col minimo sforzo possibile.

Vediamo allora di capire dove andare a mettere il metodo magico che ci trasforma il risultato.
Quando si estraggono i dati da db utilizzando Zend_DB_table, quello che si ottiene indietro è un Rowset, ovvero una collezione di Rows.
Con Zend Framework c'è la possibilità di definire una classe personalizzata di Righe (Rows) che estende quella base in modo da aggiungere i metodi che si desiderano.


Passo 1 - estendere la riga


Per aggiungere nuovi metodi alla riga si crea, quindi, una classe che estende Zend_Db_Table_Row_Abstract

nel mio caso uso la seguente:

<?php

class Naturalads_WebsiteRow extends Zend_Db_Table_Row_Abstract
{
/**
* Find the Row in the current Rowset with the
* greatest value in its 'updated_at' column.
*/
public function status()
{
$output = ($this->isChecked == 0)? "da verificare":"verificato";
return $output;
}
}

la classe la salvo naturalmente in library/Naturalads/WebsiteRow.php dove l'autoloader la può trovare comodamente

Passo 2 - aggiungere la classe al modello


Occorre aggiungere al model la classe di riga creata nel passo 1. Questo è molto più semplice del previsto, basta infatti aggiungere al proprio modello una variabile protetta:

protected $_rowClass = 'Naturalads_WebsiteRow';

in pratica io uso il seguente modello che basta e avanza per i miei scopi:

<?php
class Website extends Zend_Db_Table {
protected $_name = 'Websites';
protected $_rowClass = 'Naturalads_WebsiteRow';
}

devo dire che il modello lo salvo come al solito in models/Website.php

Passo 3 - Usare il modello


Adesso il modello è pronto per l'uso e contiene la mia estensione di classe e il mio metodo. L'unica cosa che mi manca è solo richiamarlo. Vediamo come è possibile istanziare il tutto nel controller e poi visualizzarlo in una view:

Nel mio controller posso utilizzare qualcosa del genere


...
$website = new Website();
$this->view->assign('websites', $website->fetchAll()); // fetchAll's signature is unmodified
...


quindi nella view uso qualcosa del genere per richiamare il mio metodo personale:


<?php foreach($this->websites as $website) : ?>
<tr>
<td><?php echo $this->escape($website->url);?></td>
<td><?php echo $this->escape($website->status());?></td>
</tr>
<?php endforeach; ?>


e adesso...


Forse non è velocissimo, ma in questo modo posso 'giocare' con tutte le colonne direttamente nel modello di dati, aggiungere colonne virtuali e richiamare il tutto sempre come metodi dalla view senza dover toccare il controller in alcun modo.
Un ottima cosa per la manutenzione del progetto. Se un domani dovrò gestire il nuovo stato "verificato senza successo", basterà inserire il valore -1 nel campo isChecked e modificare il metodo status(). Tutta l'applicazione continuerà a funzionare e a mostrare il nuovo stato senza problemi.

1 commento:

Giorgio ha detto...

Io preferirei una relazione di tipo has-a invece che is-a fra i modelli e le istanze di *_Row. Questo rende le righe riutilizzabili anche per altri scopi senza che debbano corrispondere a un entità ben definita.
Ad esempio taggare un post di un form sotto una categoria corrisponde a una riga di una certa tabella. Può essere un modello sotto un certo punto di vista, in quanto il tag può avere alcune operazioni da svolgere (immagina una tag cloud per esempio), ma anche solamente una riga che viene letta dalla classe che gestisce la categoria o il singolo post. Inoltre preferisco questo approccio perché astrae il modello dal database e si può dargli l'interfaccia più appropriata (che non sempre è save(), eccetera..).