Share |

domenica 31 gennaio 2010

Javascript: Filtrare il DOM con query XPath

In questo articolo mostrerò come poter agilmente ed efficacemente eseguire query XPath in Javascript per selezionare nodeset, o gruppi di nodi, dal DOM di un documento XML caricato attraverso una chiamata AJAX.

Il file XML oggetto degli esempi è serra.xml, mentre per quanto concerne gli script Javascript di esempio saranno validi solo per il parser di IE poichè gli
oggetti Node espongono il metodo selectNodes, differentemente da FireFox che invece non ne possiede uno così immediato e renderebbe più difficile la comprensione dell'argomento.

Il metodo JavaScript selectNodes(xpath) appartiene alla classe Node del DOM e ha queste caratteristiche:

  • accetta come parametro un'espressione XPath;
  • restituisce una NodeList contenente i nodi rispondenti ai criteri di selezione utilizzati;

Nel seguente esempio faccio uso della funzione Javascript loadXml(url) descritta in un precedente articolo:

<script type="text/JavaScript" language="JavaScript">
   var doc = loadXml("serra.xml");

   //piante contiene tutti gli elementi 
   //<pianta> del documento XML
   var piante = doc.selectNodes("/pianta");
<script>

Ora proporrò due esempi nei quali estrarrò dal DOM del file XML serra.xml solo i nodi XML che descrivono le piante per esterno, ovvero i nodi <pianta> che hanno il nodo figlio <esterno> settato a 'true'.

Nel primo esempio, per raggiungere lo scopo utilizzerò solo il DOM con uno script JavaScript:

<script type="text/JavaScript" language="JavaScript">
   var doc = loadXml("serra.xml");
   
   //piante contiene tutti gli elementi 
   //<pianta> del documento XML
   var piante = doc.getElementsByTagName("pianta");
 
   for(var i=0;i<piante.length;i++){
      //nodo <pianta>
      var pianta = piante[i]; 
      
      //verifico il contenuto del 
      //nodo figlio <esterno>
      if(pianta.getElementsByTagName("esterno")[0].text 
          == 'true'){
       .....
      } 
   }   
<script>

Nel secondo esempio invece estrarrò tutte le piante da esterno utilizzando una query XPath come parametro di ingresso al metodo selectNodes(qry):

<script type="text/JavaScript" language="JavaScript">
   var doc = loadXml("serra.xml");
   var piante_da_esterno = 
      doc.selectNodes("//piante/pianta[esterno = 'true']");   
<script>

Et Voilat: non c'è paragone!

L'espressione XPath "//piante/pianta[esterno = 'true']" contiene infatti tutta la logica che avremmo dovuto esprimere navigando il DOM nodo per nodo.

E' innegabile che la conoscenza di XPath e' un asso nella manica da potersi giocare, insomma non e' da sottovalutare.

Alla prossima,
MA.

XPath: Filtrare e Selezionare nodi XML

In questo articolo affronteremo la sintassi di XPath e capiremo come assemblare le sue regole per raffinare gli esiti delle query di estrazione dati da documenti XML generici.

Le regole della sintassi di XPath non sono molto complicate anzi a mio avviso rappresentano un ottimo metodo per esprimere un percorso (in inglese, path) da seguire all'interno di un documento XML.

Tuttavia, inizialmente potrebbe risultare difficile abbinare logicamente i vari simboli per formare le espressioni necessarie ad eseguire selezioni complesse di nodi XML.

Al fine di rendere più semplice la comprensione delle regole sintattiche cercherò di fornire un esempio per ogni regola che tratterò.

Come file XML da esplorare negli esempi che seguiranno utilizzo serra.xml.

Selezionare i nodi di un documento XML

Passiamo a trattare le path-expressions più utilizzate:

XPath Expressions"
EspressioneDescrizionePath Exspression Risultato
nomeSeleziona tutti gli elementi figli del nodo che corrisponde a quello indicato
/Seleziona a partire dall'elemento radice/piantaTutti gli elementi <pianta> a partire dalla radice <serra>
//Seleziona i nodi a partire dal nodo indicato indipendentemente dalla posizione in cui ci troviamo//coloreSeleziona tutti gli elementi <colore> a partire da dove si trovano
.Seleziona il nodo corrente
..Seleziona il nodo Parent del nodo corrente
@nomeSeleziona i nodi che contengono l'attributo indicato

Ricerche più precise con i Predicati

I Predicati sono utilizzati per la ricerca di nodi e attributi specifici che contengono un determinato valore.

I predicati devono essere contenuti in parentesi quadre: [].

I Predicati XPath
EspressioneRisultato
/serra/pianta[1]Seleziona il primo nodo <pianta> figlio di <serra>
/serra/pianta[last()]Seleziona l'ultimo nodo <pianta> figlio di <serra>
//serra/attrezzo[@id='1']Seleziona i nodi <attrezzo> il cui attributo id contiene un valore uguale a quello indicato

Selezionare i Nodi non conosciuti

Selezione dei nodi XLM sconosciuti
EspressioneDescrizionePath ExspressionRisultato
* Seleziona qualsiasi elemento figlio del nodo indicato/serra/*Seleziona tutti gli elementi figli di <serra> indipendentemente dal nome
@* Estrae qualsiasi attributo appartenente ai figli del nodo indicato/serra/pianta/@*Estrae tutti gli attributi degli elementi <pianta> figli di <serra> indipendentemente dal nome dell'attributo
node()Seleziona qualsiasi nodo di qualsiasi tipo

Selezioni Multiple

Grazie all'utilizzo dell'operatore '|' possiamo eseguire selezioni in più percorsi.

Selezioni Multiple
EspressioneRisultato
//serra/pianta | //serra/vasoSeleziona tutti i nodi <pianta> e <vaso> figli di <serra>

Come si può notare le regole non sono tanto difficili ed in più offrono una vasta gamma di possibili operazioni da effettuare sui dati XML.

Alla prossima,
MA.

XML:Usare Entità Predefinite e Customizzate

Capita di riceve documenti XML well-formed ma che purtroppo non passano la fase di parsing perchè al loro interno sono stati utilizzati caratteri vietati, anziché le rispettive entità XML. Un esempio di ciò che intendo potrebbe essere la situazione seguente:

<nodo> 5 < 10 </nodo>

Dal punto di vista formale il nodo è corretto, detto appunto well-formed, ma al suo interno è presente il carattere '<' che essendo vietato dalla grammatica XML genera errori in fase di parsing.

Il suddetto carattere (<) deve essere sostituito dalla sequenza '&lt;' che e' una delle 5 entità predefinite che rimpiazzano altrettanti caratteri significativi e riservati per il linguaggio XML.

Cosa sono le Entità?

Le entita' sono delle scorciatoie da utilizzare al posto dei riferimenti numerici (ASCII) con i quali sarebbe possibile esprimere ogni carattere digitabile: tornando al nostro carattere non utilizzabile (<), questo potrebbe essere anche sostituito dalla sequenza '&#62;', cioè dal valore decimale con cui quel carattere viene rappresentato in codice macchina.

Ovviamente, l'utilizzo delle entità facilita non poco le cose rispetto a quello che sarebbe altrimenti lavorare con i riferimenti numerici.

Entita' Predefinite

Ho accennato precedentemente all'esistenza di 5 entita' XML predefinite, ma quali sono?

Entità Predefinite
Entita'Riferimento nel TestoSignificato
lt&lt;minore di
gt&gt;maggiore di
amp&amp;e commerciale (&)
apos&apos;apostrofo (')
quot&quot;doppie virgolette (")

E' necessario specificare che differentemente dai caratteri '<', '>' e '&', che sono sempre vietati all'interno di tutto il documento XML, i caratteri ' e " devono essere obbligatoriamente sostituiti dalle rispettive entita' solo se presenti nel valore di un attributo delimitato dallo stesso carattere, come nel seguente esempio:

<nodo attributo='l'apostrofo'/>

Entità Customizzate

Siccome 5 entità predefinite potrebbero non essere sufficienti, XML ci consente di crearne altre personalizzate, o customizzate.

Possiamo infatti creare infinite scorciatoie per sostituire i caratteri nei nostri documenti, per esempio mi viene in mente il carattere della moneta EURO '€': se ogni volta per utilizzarlo all'interno di un documento XML dovessimo ricordarci il suo riferimento numerico (#8364;) saremmo quanto meno a rischio neuro. :)

Come possiamo definire una nuova entità con cui sostituire facilmente ogni volta il carattere dell'EURO?

Ci viene in aiuto la sintassi DTD (Document Type Definition): questa ci permette di dichiarare, all'interno del documento o esternamente, una definizione DOCTYPE a cui fare riferimento per rendere agevole la sostituzione di con &#8364; nella fase di creazione del XML.

<!DOCTYPE portafoglio[
   <!ENTITY eur "&amp;#8364;">
]>   
<portafoglio>
   <lunedi>33 &eur; </lunedi>
   <martedi>13 &eur; </martedi>
   .......
</portafoglio>

E' chiaro che è possibile inventare entità per esprimere qualunque tipo di testo, carattere o stringa che sia.

Per esempio, abbiamo la lista delle opere di un autore il cui nome e' lunghissimo e deve essere ripetuto per ogni opera?

In questo caso, potrebbe tornare utile la creazione di una entità di questo tipo:

<!DOCTYPE opere[
   <!ENTITY aut "Mirko Agrati">
]>  

E ogni volta che devo scrivere l'autore faccio riferimento a '&aut;'. Semplice e comodo, no?

Alla prossima, MA.

sabato 30 gennaio 2010

PHP: Realizzare una Cache Singleton (OOP)

In questo articolo affronteremo la creazione di una cache rispettosa del design pattern singleton scritta in PHP. Saranno analizzate chiaramente le motivazioni che mi hanno portato a scegliere come soluzione questo tipo di componente software durante la realizzazione di un progetto web.

Requisiti

Per poter proseguire agilmente nella lettura di questo articolo è necessaria la conoscenza dei seguenti concetti:

Contesto applicativo

Dunque, dopo aver rimesso a fuoco i concetti fondamentali alla base di questo articolo (pattern singleton, cache ed OOP), passo a descrivervi brevemente ciò che ho implementato e le motivazioni che mi hanno spinto alla scelta del patter Singleton.

Partiamo dal contesto: stiamo parlando del sito che ho creato per la presentazione della mia figura professionale, di come l'ho pensato e di come l'ho progettato.

Tutto il contenuto presente sul sito è conservato in documenti XML validati attraverso l'utilizzo di una DTD specifica. Questi documenti vengono wrappati da apposite classi PHP in oggetti che vengono utilizzati da altre Factory Classes per completare il codice XHTML del template PHP, che viene inviato al browser utente, con i contenuti richiesti.

Questo in breve è il modo in cui ho architettato il sito perchè mi consente di avere completamente separati i contenuti, cioè i dati, le logiche applicative e la parte di view(pattern MVC: Model View Controller).

Le sessioni utente

Analizziamo ora cosa succede quando un utente, e poi un'altro, avviano una nuova sessione sul sito.

Arriva il primo visitatore che (per semplicità) ha richiesto la home page:

  1. viene caricato il file homepage.xml in una istanza della classe DOMDocument;
  2. vengono caricati allo stesso modo il menu principale ed il footer;
  3. tutti questi dati XML vengono wrappati in classi e sotto-classi del tipo MAPage;
  4. la classe HTMLFactory espone i dati in formato XHTML completando un template PHP;
  5. la pagina richiesta viene visualizzata dal browser.

La seconda sessione si avvia richiedendo la pagina articoli.php:

  1. viene caricato il file articoli.xml in una istanza della classe DOMDocument;
  2. vengono caricati allo stesso modo il menu principale ed il footer;
  3. viene attaraversato ricorsivamente il filesystem per creare il doc menu_laterale_articoli.xml
  4. tutti questi dati XML vengono wrappati in classi e sotto-classi del tipo MAPage;
  5. la classe HTMLFactory espone i dati in formato XHTML completando un template PHP;
  6. la pagina richiesta viene visualizzata dal browser.

Riflessioni e Considerazioni

partiamo dalle considerazioni più semplici: perchè ogni volta devo ricaricare il menù principale ed il footer?
Anche pensando di stoccarli nell'array $_SESSION[] dovrei quanto meno caricare questi documenti una volta per ogni visitatore del mio sito.

Ora, più seriamente, quante volte mi sarà capitato di modificare un documento dopo averlo uploadato? Pochissime volte: penso meno di 5. Quindi, a ben vedere, è possibile e ragionevole ipotizzare che siccome un documento non viene mai modificato, dopo essere stato caricato per la prima volta da una sessione applicativa potrebbe essere messo a disposizione di tutte le successive visite che faranno i miei utenti.

Quindi effettivamente se ogni volta che venisse richiesta una pagina i contenuti fossero già wrappati in oggetti MAPage, e quindi già a disposizione, eviterei tutte le fasi necessarie al caricamento in memoria dei dati e rimarrebbe da eseguire solo la fase di creazione del codice XHTML ed il completamento del template PHP.

In quest'ottica ridurrei al minimo tutte le attività che richiedono più risorse ed impiegano necessariamente più tempo ad eseguire il task e potrei nello stesso tempo offrire ai miei utenti delle performance migliori riducendo il carico di lavoro del web-server.

Ovviamente è possibile considerare di ibernare definitivamente alcuni stati applicativi e come nel mio caso è meglio che alcune situazioni siano stoccate solo per un ragionevole arco di tempo. Per esempio, se consideriamo il menù laterale della pagina degli articoli non sarebbe utile cacharlo all'infinito perchè altrimenti un nuovo articolo(cioè un nuovo documento XML) non apparirebbe mai nell'elenco; se invece pensiamo ad un singolo articolo, siccome possiamo ragionevolmente affermare che una volta pubblicato non lo modificheremo mai, è naturale prendere in considerazione l'idea di caricarlo in cache una volta per sempre.

La soluzione: una Cache Singleton

Eh si, è evidente che ciò che sto cercando è proprio questo: una cache singleton parametrizzabile in termini di tempo di vita concesso agli elementi in essa caricati.

C'è un piccolo problema però: per questioni di sicurezza non posso utilizzare l'array globale $GLOBALS[] per poter salvare e richiamare i dati della cache e neanche utilizzando delle proprietà statiche posso semplicemente tenere vivo lo stato applicativo dei miei oggetti tra una pagina ed un'altra: PHP permette di farlo solo all'interno della stessa pagina.

Considerando anche altri orizzonti, tipo la possibilità di accedere ai dati in cache anche da altri applicativi in remoto (sto progettando una estensione per FireFox che cambia notevolmente il modo di fruire il mio sito), ho optato per risolvere elegantemente il problema tramite i processi di serializzazione e de-serializzazione della cache.

MASingletonCache

Questo è il nome della classe che ho creato e che ormai lavora magnificamente da diverso tempo.

Riassumendo io avrei bisogno per il caching dei dati di un componente con queste caratteristiche:

  • Deve essere dinamico e scalabile;
  • Deve ricaricarsi ogni tot minuti/ore/giorni;
  • deve essere ATOMICO: contiene tutti i metodi di factory necessari;
  • Deve gestire dati applicativi;
  • Deve essere performante;
  • Deve essere decisamente Singletone;
  • Deve gestire accessi concorrenziali;

Tutto questo è riassunto in modo migliore nel seguente caso d'uso UML:Specifiche del componente software singleton

Per quanto riguarda l'architettura del componente, la classe implementa alcune interfacce ed estende altre classi che già utilizzo: la situazione è perfettamente descritta nel seguente diagramma di classe UML:architettura del componente software singleton.

Come si può notare, il cuore del sistema di caching è rappresentato dall'interfaccia IMAList e dalla classe MAList che la implementa.

La classe MASingletonCache fondamentalmente incapsula la lista che funge da cache e garantisce che il suo utilizzo sia aderente alle specifiche del pattern Singleton. Poi ci sono l'interfaccia IMACachable e la classe MACachable che la realizza.

La classe MASingletonCache si occupa di verificare che ogni oggetto cachato implementa l'interfaccia IMACachable in modo tale che possa essere sempre verificato da quanto tempo un dato risiede in cache.

Rinfrescati i concetti basilari e chiarito lo scenario in cui mi sono trovato, siamo pronti per affrontare il codice sorgente dei singoli componenti che fanno parte del mio sistema di caching.

Iniziamo ad analizzare il codice sorgente delle classi di Model, che rappresentano i contenuti da visualizzare e conservare.

L'Interfaccia IMAList

interface IMAList{
  public function set($key, $obj);
  public function get($key);
  public function remove($key);
  public function rebuild();
  public function size();
  public function getItems();
  public function setItems($array);
  public function contains($key);
}

Non c'è molto da dire, le particolarità di questa interfaccia sono racchiuse nell'utilizzo di oggetti abbinati ad una chiave specifica (fossimo in ambiente Java potrebbe essere una HashTable od una HashMap) e nel metodo pubblico rebuild(): effettivamente il nome scelto per la firma del metodo poteva essere migliore in quanto (lo vedremo in seguito) non si occupa di ricostruire la lista, ma è stato pensato come un punto di ingresso per lavorare in maniera differente i dati stoccati. Vedremo analizzando la classe MAList che in questo metodo viene attraversata la cache in cerca di elementi nulli o de-referenziati da rimuovere, in modo da alleggerirla il più possibile e ridurre al minimo i tempi di ricerca dei dati al suo interno.

La classe MAList

class MAList implements IMAList {
  private $items = array();
 
  /**
   * Riceve un valore identificativo univoco e 
   * l'oggetto da conservare.
   * @param String $key Identificativo Univoco;
   * @param mixed  $obj Oggetto da conservare;
   */
  public function set($key, $obj){
    if($this->isValidVar($key) 
        && $this->isValidVar($obj))
      $this->items[$key] = $obj;
  }

  /**
   * Riceve un valore identificativo e restituisce 
   * l'oggetto immagazzinato identificato da $key.
   * @param  String $key  Identificativo Univoco;
   * @return mixed        Oggetto immagazzinato;
   */
  public function get($key){
    if($this->isValidVar($key) && 
        array_key_exists($key, $this->items))
      return $this->items[$key];
  
      return null;
  }
  /**
   * Rimuove l'elemento identificato 
   * con $key dalla lista.
   * @param String $key Valore univoco 
   *                    dell'elemento da rimuovere.
   */
  public function remove($key){
    if($this->isValidVar($key) && 
        array_key_exists($key, $this->items)) 
      unset($this->items[$key]); 
  }
  /** Rimuove tutti i dati nulli dallo stack. */
  public function rebuild(){
    foreach($this->items as $k=>$v){
      if(!$this->isValidVar($v))
        unset($this->items[$k]);
    }
  }
  /**
   * Restituisce la dimensione dello stack.
   * @return  Integer  Il numero di oggetti 
   *                   presenti in lista;
   */
  public function size(){
    $c = count($this->items);
    return (!$c) ? -1 : $c;
  }
  /**
   * Verifica l'esistenza di un determinato dato.  
   * @param  String  $key  La chiave abbinata al dato di 
   *                       cui verificarne l'esistenza;
   * @return  Boolean      true se il dato esiste, 
   *                       altrimenti false;
   */
  public function contains($key){
    return array_key_exists($key, $this->items);
  }
  /**
   * Restituisce l'intero stack. 
   * Prima di resituirlo esegue una chiamata
   * al metodo rebuild(). 
   * @return  Array  Tutta la lista;
   */
  public function getItems(){
    $this->rebuild();
    return $this->items;
  }
  /**
   * Sostituisce l'intero stack.  
   * @param  Array  Il nuovo stack con cui 
   *                sostituire il vecchio;
   */
  public function setItems($arr){ 
    $this->items = $arr;
  } 
  /**
   * Verifica se il parametro ricevuto 
   * e' utilizzabile.
   * @return Boolean  true se e' utilizzabile, 
   *                  altrimenti false;
   */
  private function isValidVar($var){
    return (isset($var) && !empty($var))
       ? true : false;
  }
}

La classe incapsula un array nella proprietà privata items ed espone i metodi per il salvataggio/modifica/lettura/ricerca/elimininazione dei dati presenti in tale proprietà.

Qui puoi vedere cosa realmente accade quando viene chiamato il metodo MAList::rebuild(): il metodo viene richiamato solo quando si accede al membro pubblico MAList::getItems() per eseguito un controllo su ogni singolo item presente prima di restituire tutta la lista di oggetti.

IMAList e MAList rappresentano il garante e il gestore dei nostri dati.

Passiamo ora ad analizzare ciò che per me vuol dire essere un oggetto cachabile.

Con il termine cachabile intendo un determinata tipologia di oggetti sui quali la mia cache può eseguire determinati controlli ed eventualmente agire sugli stessi di conseguenza.

Se ridiamo un'occhiata alle features descitte nel caso d'uso UML, sopra esposto, si può facilmente capire quali sono i benefici che l'interfaccia IMACachable dona al progetto.

Avere una cache che è in grado di distruggere e ricaricare i propri items in base ad un arco di tempo prestabilito è sicuramente una ottima caratteristica che rende il sistema molto elastico e facilmente riadattabile in altri progetti.

L'interfaccia IMACachable

L'interfaccia è molto semplice quanto utile. Essa si compone di un solo metodo pubblico che deve essere implementato dalla classe che la realizzerà.

interface IMACachable{
  /**Restituisce il valore timestamp di quando 
   * è stata realizzata istanza di classe. */
  public function getCreationUTS();
}

La classe MACachable

Questa classe fondamentalmente è il classico JavaBean ma oltre ad implementare l'interfaccia IMACachable racchiude due proprietà molto importanti:

  • key, la chiave con cui viene stoccato nella cache;
  • data, il vero oggetto sa cachare;

Ritengo strategico, in un ottica nella quale ogni oggetto possa auto-verificarsi a favore della cache, che l'oggetto conservi in se stesso la stessa chiave che la classe MASingletonCache utilizza per riferircisi.

class MACachable implements IMACachable{
  private $creationUTS;
  private $key;
  private $data;
 
  public function __construct($key=null,$data=null){
    $this->creationUTS = time();
    $this->key = $key;
    $this->data = $data;
  } 
 
  public function getCreationUTS(){
    return $this->creationUTS;}
  public function getKey(){return $this->key;}
  public function setKey($key){$this->key = $key;}
  public function getData(){return $this->data;}
  public function setData($data){$this->data = $data;} 
}

Ora che conosciamo come sono strutturati i capisaldi del sistema, possiamo addentrarci nel codice sorgente della classe MASingletonCache per capire come le sue logiche interne agiscono sui dati cachati e come è possibile garantire sempre l'utilizzo di una istanza unica di classe.

La classe MASingletonCache

class MASingletonCache 
      extends MAList 
      implements IMACachable {
/**
 * Arco di tempo massimo in cui trattenere 
 * i dati in cache.
 * Espresso in secondi (max. 5 min)
 */
const DEFAULT_ITEMS_TIME_OF_LIFE = 300;

/**
 * Se utilizzato, gli oggetti cachati 
 * non scadranno mai.
 */
const INFINITY_ITEMS_TIME_OF_LIFE = -999;

/**
 * Se true, inibisce il funzionamento della cache.
 * Utilizzato in fase di test e debug.
 */
private static $suspended = false;

/**
 * Unica istanza della classe. 
 * Garantisce l'utilizzo del 
 * design pattern Singleton.
 */
private static $instance = null;

/**
 * Unix Timestamp del momento di 
 * creazione della cache.
 */
private $creationUTS;

/**
 * Indirizzo del file su cui eseguire 
 * le operazioni di serializzazione della cache.
 */
private $storePath;

/**
 * Secondi di vita concessi agli oggetti cachati
 */
private $itemsTimeOfLife;

private function __construct($path,$itemsTOL){
  $this->creationUTS = time();
  $this->storePath = $path;
  $this->itemsTimeOfLife = $itemsTOL;
} 

/**
 * Unico punto di accesso per la creazione 
 * di una istanza di classe.
 * L'istanza di classe creata sarà unica 
 * in tutta l'applicazione (Singleton).
 * 
 * @param $path  String  Path per la creazione del file 
 *                       nel quale salvare lo stato 
 *                       dell'istannza di classe;
 * @param $itemsTOL Integer  Secondi di vita concessi ad 
 *                           un oggetto cachato prima di 
 *                           essere rimosso dalla cashe. 
 *                           300secondi è il valore di default   
 * @return  MASingletonCache Istanza di classe Singleton.
 */
public static function getInstance($path,$itemsTOL=300){
  if(!isset($path) || empty($path))
    throw new UnAcceptedValueException
      ('Impossibile recuperare la cache Singleton',6485986);
 
  if(!self::$suspended)
    self::$instance = unserialize(file_get_contents($path));

  echo "<!-- Tentativo recupero cache da IO: " . 
    isset(self::$instance) . "; Cache Sospesa: " . 
    ((self::$suspended) ? '1' : '0' ) . " -->\n";
 
  if(!self::$instance){
    self::$instance = new MASingletonCache($path,$itemsTOL);
    echo "<!-- nuova instanza di MASingletonCache -->\n";
  }
  return self::$instance;
}

/**
 * Prima di eseguire la serializzazione della cache 
 * esegue una compattazione degli oggetti presenti in cache. 
 * @see self::compact()
 */
public function store(){
  echo '<!-- Inizio Fase di Storage -->'."\n";
  $this->compact();
  $s = serialize(self::$instance);
  $fp = fopen($this->storePath, "w");
  fwrite($fp, $s);
  fclose($fp);
  echo '<!-- Fine Fase di Storage -->'."\n";
}

/**
 * Verifica la scadenza di ogni oggetto in cache.
 * Se un oggetto è scaduto o se non è una istanza 
 * della classe MACachable viene rimosso. 
 * @see MACachable
 */
private function compact(){
  $arr = parent::getItems();
  foreach($arr as $k=>$v){
    echo "\t" . '<!-- Oggetto in Analisi: "' 
      . $k . '" tipo: "' . get_class($v) . '" -->';
  
    if(!isset($v) || get_class($v) != 'MACachable'){
      parent::remove($k); 
      echo '<!-- Oggetto RIMOSSO perchè di tipo'
        . ' diverso da \'MACachable\'. -->'."\n";
      continue;
    }

    $dataCached = $v->getData();  
    if(!isset($dataCached) || empty($dataCached))
    unset($arr[$k]);
  
    $seconds = time() - $v->getCreationUTS(); 
    echo '<!-- Oggetto conservato in cache da ' 
      . $seconds . ' sec.(Limite=' . $this->itemsTimeOfLife 
      . 'sec.) -->';

    if($this->itemsTimeOfLife 
        != self::INFINITY_ITEMS_TIME_OF_LIFE){ 
      if( $seconds >= $this->itemsTimeOfLife){
        parent::remove($k); 
        echo '<!-- Oggetto RIMOSSO. -->';
      }
    }
    echo "\n";
  }
}

/** ---------- SATISFY IMACachable --------------- **/
/**
 * Restituisce il valore unixtimestamp 
 * del momento della creazione della cache.
 */
public function getCreationUTS(){
  return  $this->creationUTS;
}
}

Dettagliatamente

Dando uno sguardo alla dichiarazione della classe possiamo notare che:

  • la classe eredita tutte le proprietà ed i metodi di MAList;
  • è anche un oggetto cachabile;
  • MASingletonCache::getInstance(String, int) è l'unico entry point disponibile;

Avere un costruttore privato è la base del paradigma Singleton infatti è solo garantendo il passaggio obbligato per il metodo statico e pubblico 'getInstance(String, int)' che possiamo assicurare che esisterà una e solo una istanza della classe, ed in particolare sarà rappresentata dalla proprietà privata e statica 'instance'.

Il meccanismo alla base del pattern Singleton è molto semplice: ad ogni chiamata di MASingletonCache::getInstance() viene verificato che $instance sia valorizzata: in caso affermativo viene restituita immediatamente al chiamante altrimenti viene settata con una nuova istanza della classe creata al volo e poi restituita.

In questo caso però c'è molto di più: il costruttore riceve 2 parametri che, in ordine, rappresentano l'indirizzo del file su cui eseguire la serializzazione dell'oggetto $instance e il numero di secondi di vita concessi ad ogni oggetto conservato. Al termine del 'count-down' l'oggetto viene rimosso dalla cache.

La fase di Serializzazione

Questa attività comprende le fasi di consolidamento, di lettura dei dati e creazione degli oggetti che quei dati rappresentano.
Nel mio componente il consolidamento dei dati avviene quando si accede al membro pubblico MASingletonCache::store() ed invece i dati vengono caricati in memoria quando si chiama il metodo MASingletonCache::getInstance(String, int).

Di fatto serializzare un oggetto significa riportare il suo stato interno in un formato tale che lo stesso oggetto possa essere ricreato rileggendo lo stesso formato di dati.

In questo caso non disponendo di un Server DB serializzo l'istanza di classe su un file di testo che arbitrariamente ho scelto e di cui ho passato l'indirizzo alla classe, come primo parametro del metodo MASingletonCache::getInstance(String, int).

Vale la pena soffermarsi a riflettere su come l'introduzione della serializzazione ha cambiato questa classe.
Effettivamente la classe è scritta rispettando tutti i vincoli che il design pattern Singleton richiede:

  • avere un unico costruttore privato;
  • fornire un metodo getter statico che ritorna sempre la stessa istanza della classe;
  • memorizzare il riferimento all'unica istanza in un attributo privato anch'esso statico;

Però, non è vero in assoluto che l'istanza restituita rappresenti sempre lo stesso insieme di dati.

Nuovi orizzonti

E' vero che l'istanza generata è unica ed è sempre la stessa ma, semplicemente parametrizzando il file su cui serializzare, ho dato alla classe la caratteristica di poter rappresentare dati diversi con la stessa istanza di classe. Questo potrà sembrare banale e sbagliato ma innanzi tutto in realtà è frutto dell'impossibilità (gentile concessione del servizio di hosting che utilizzo) di poter utilizzare la variabile globale $GLOBALS[] per salvare dati a livello globale e l'incapacità di PHP (almeno fino ad oggi) di conservare stati applicativi tra una pagina ed un'altra.

Soprattutto, però, mi offre la possibilità di organizzare le mie cache, per esempio, in base al tempo di sopravvivenza dei dati in esse conservati.

Così facendo deciderò di serializzare sul file './permanent.cache' tutti quei dati che non devono mai scadere:

$cache = MASingletonCache('./permanent.cache',
           MASingletonCache::INFINITY_ITEMS_TIME_OF_LIFE);

Mentre utilizzerò il file './temporary.cache' per i dati che ogni 10 minuti verranno rimossi dalla cache:

$tempCache = MASingletonCache('./temporary.cache', 600);

Il membro pubblico MASingletonCache::store() non esegue solo il consolidamento dei dati, ma in esso avviene l'eliminazione degli oggetti nulli o deferenziati e la verifica che nessuna istanza di MACachable si scaduta. Nel caso contrario, ossia quando un oggetto cachato risulta essere scaduto, questo stesso viene rimosso dalla cache. Il risultato è che la fase di scrittura, e successivamente quella di ri-lettura, terrà conto solo dei dati considerati ancora validi risultando essere più snella e veloce possibile.

Esempio di utilizzo di MASingletonCache

include_once 'IMAList.php';
include_once 'MAList.php';
include_once 'IMACachable.php';
include_once 'MACachable.php';
include_once 'MASingletonCache.php';

/** Carico la cache permanente */
$permanentCache = MASingletonCache('./permanent.cache',
    MASingletonCache::INFINITY_ITEMS_TIME_OF_LIFE);

/** Pagina di presentazione che deve essere sempre disponibile */
$splashpage = (isset($permanentCache->get('page.splashpage'))) 
  ? $permanentCache->get('page.splashpage') 
  : new MACachable('page.splashpage', 
        new SplashPage('Ciccio_Pasticcio'));
 
echo $splashPage->html();
$permanentCache->store();

/** Carico la cache 'volatile' */
$tempCache = MASingletonCache('./temporary.cache', 600);

/** Ovviamente un menu dinamico non potrà  
 * essere un dato cachato in modo permanente */
$menuDownload = ( isset($tempCache->get('menu.download')) ) 
  ? $tempCache->get('menu.download') 
  : new MACachable('menu.download', new Menu('Downloads'));
 
echo $menuDownload->html();
$tempCache->store();

Conclusioni

Il sistema di caching presentato a mio avviso centra tutte le specifiche descritte nel caso d'uso UML riportato precedentemente, in più risulta essere sicuramente di facile riutiizzo in altri progetti.

La fase di serializzazione avviene tramite le funzioni standard offerte dalle librerie PHP ed in modo trasparente.

In questo articolo la cache viene serializzata in 'chiaro' ma è possibile trasformare i dati da serializzare utilizzando algoritmi di cifratura ed anche di compressione.

Spero che l'articolo ed i concetti espressi ti siano piaciuti e che tu abbia potuto apprezzare la qualità del codice PHP qui offerto.

Alla prossima,
MA.

SEO: Le 10 Regole base da conoscere

SEO (Search Engine Optimization) è uno degli ingredienti più importanti per aumentare notevolmente il traffico HTTP diretto ad un qualunque sito web che presenta però contenuti di elevata qualità. Infatti, una volta che i gli articoli pubblicati sono vecchi di poche settimane, la maggioranza del traffico HTTP generato verso quegli articoli proverrà dai motori di ricerca. Ecco perchè è importante figurare tra i primi risultati delle ricerche effettuate interrogando i motori di ricerca.

In quest'articolo elencherò quelle che ritengo essere le 10 regole SEO fondamentali che bisognerebbe sempre tenere a mente quando si scrive un nuovo articoli, o si sviluppa un nuovo sito web, per assicurarsi un posto tra gli alti ranghi negli esiti delle query dei motori di ricerca.

Collocare le parole chiave nel tag TITLE

Il tag <TITLE>, attraverso l'uso di parole chiave particolari, è uno dei mezzi più importanti per posizionare in alto una pagina. Ciò è dovuto al fatto che i titoli delle pagine HTML sono i link meglio visibili tra i risultati delle ricerche e, contenendo e mostrando le stesse parole ricercate, catturano l'attenzione dell'utente.

E' importante sapere che Google mostra solo i primi 70 caratteri (circa 8 parole) del titolo di una pagina e non indicizza più parole chiave, presenti nel titolo della pagina, dopo la 12a parola. Yahoo! e MSN non indicizzano alcun chè se il titolo della pagina è troppo lungo.

Per approfondire puoi leggere un articolo in inglese con informazioni aggiuntive sull'utilizzo del tag TITLE.

Ottimizzare il file Robot.txt

Avere un file robot.txt ottimizzato è importante perché esso è utilizzato dagli spiders dei motore di ricerca per muoversi all'interno di qualunque sito web.

Per approfondimenti fare riferimento al sito ufficiale.

Uso appropriato dell'attributo ALT

L'attributo ALT dovrebbe essere usato per descrivere un'immagine e non per spammare parole chiave che nulla hanno a che vedere con l'immagine visualizzata dall'utente.
Questo perché gli spiders, sebbene non siano in grado di leggere il contenuto delle immagini, possono comunque leggere il contenuto dell'attributo ALT e incrociandolo con i dati di navigazione raccolti analizzando il comportamento degli utenti, i motori di ricerca potrebbero penalizzare il sito facendogli perdere posizioni nel rank.

Utilizzo accurato del Anchor Text contenuto nei link

L'Anchor Text è il testo contenuto all'interno del tag <a>, ovvero l'etichetta che collega il nostro sito ad altre pagine.

E' importante che il testo in questione sia descrittivo della risorsa linkata sia per gli utenti che per i motori di ricerca perché permette di far conoscere loro altre risorse ed i legami con esse.

Sono quindi da evitare come Anchor Text le parole come clicca qui.

Altra cosa da tenere a mente: non utilizzare più di 55 caratteri per le descrizioni degli Anchor Text.

Qualità dei links in entrata ed uscita

Mentre la maggior parte degli sviluppatori sa che avendo molti siti web collegati al proprio può incrementare il suo rank, molti di loro non sanno che la qualità dei siti web collegati gioca un ruolo vitale nel calcolo del rank di una pagina del loro sito web.

Essere collegati ai siti web infettati da malware, e a loro volta untori, può causare realmente la rimozione del sito dai risultati delle ricerche.

Una cosa da tenere a mente è che gli spiders non seguono più di cento link presenti su una sola pagina.

Utilizzo appropriato degli Header Tags

I tags <H1>, <H2> ecc. dovrebbero essere usati con accuratezza sintattica.
Per esempio non si dovrebbe usare <H2> per titolare un paragrafo o usare <div class="title"></div> per mostrare il titolo di una pagina.

Di seguito poche cose da tenere a mente quando si utilizzano i tags header:

  • usare un solo tag <H1> per pagina;
  • nella stessa pagina è possibile utilizzare molteplici volte i tags compresi nel range <H2> - <H6>;
  • i crawlers dei motori di ricerca leggono solo il codice HTML contenuto nei tags headers.

L'importanza del primo paragrafo

Il primo paragrafo è il paragrafo più importante dell'intera pagina HTML. Questo perché i motori di ricerca lo considerano, o presumono che sia, il sommario della pagina, o dell'articolo.

E' quindi essenziale posizionare le parole chiave più importanti nella prima frase del primo paragrafo.

Assicurarsi che il sito sia accessibile

Un sito dovrebbe garantire che il suo contenuto sia accessibile e raggiungibile con successo il più spesso possibile.

Le funzionalità contenute all'interno delle pagine, l'uptime del server e la validità degli elementi HTML fanno parte di ciò che viene espresso con il termine accessibilità.

Se questi punti sono ignorati o imperfetti, sia i visitatori che i motori di ricerca selezioneranno e visiteranno altri siti.

Per assicurarti che il tuo sito sia raggiungibile e che non contenga broken links, in italiano link a risorse non più raggiungibili, puoi utilizzare questi strumenti:

Utilizzare URLs SEO friendly

L'URL di una pagina dovrebbe essere abbastanza descrittiva da poter indicare sia ai motori di ricerca sia agli utenti che risorsa si sta per selezionare.

Fare questo significa utilizzare URL amichevoli dal punto di vista SEO, dal momento in cui nell'indirizzo della risorsa saranno presenti le parole chiave della pagina.

URL così fatte solitamente vengono classificate dai motori di ricerca con un rank più alto di altre come example.com/p=981489894.

Nel caso si utilizzassero URLs dinamiche bisognerebbe riuscire a non utilizzare più di due parametri dinamici altrimenti aumentano le probabilità che gli spiders dei motore di ricerca non percepiscano cose importanti.

Due interessanti risorse per approfondire le differenze tra l'utilizzo di URLs dinamiche e statiche sono:

Usabilità del sito

Se il tuo sito web è stipato di annunci e ha un layout terribile, un'accessibilità, una navigazione e contenuti di bassa qualità le probabilità che i motori di ricerca spingano in alto altri siti con usabilità migliore aumenta notevolmente, causando così lo sprofondare delle tue pagine nel più bassi ranghi della classifica.


Altre importanti risorse SEO per approfondire gli argomenti trattati

Alla prossima, MA.

mercoledì 27 gennaio 2010

Javascript: Browsers e parsers XML (FF, IE)

Il DOM (Document Object Model) è un'interfaccia astratta che rappresenta la struttura di un documento XML.

I parsers che leggono un documento XML, rispettando le specifiche espresse nell'interfaccia DOM, sono in grado di analizzare formalmente tale documento per verificarne la conformità alle regole del Document Object Model.

Sia Internet Explorer che FireFox (e la famiglia di prodotti Mozilla) supportano il DOM, ma in maniera differente ognuno dall'altro.

Il parser di Internet Explorer

IE utilizza la libreria MSXML richiamandola come oggetto ActiveX e scegliendo un identificatore diverso per ogni sua versione:

var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");

Siccome l'identificativo varia a seconda della versione, anzichè Microsoft.XMLDOM potrebbe essere necessario richiamare MSXML.DOMDocument oppure MSXML2.DOMDocument o anche altre versioni più recenti.

Per caricare un documento XML con il browser di casa Microsoft si eseguiranno delle istruzioni simili a queste:

var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.load("documento.xml");

Il parser di FireFox

FireFox, come gli altri browsers che utilizzano il motore di Mozilla, invece attiva il parser tramite un
oggetto nativo.

var xmlDoc = document.implementation.createDocument("ns","root",null);

I 3 parametri che riceve il metodo createDocument() rappresentano:

  • lo spazio dei nomi (namespace) usato nel documento XML;
  • il nome dell'elemento radice del file XML;
  • il III° parametro e' lasciato 'null' perchè riservato a utilizzi futuri;

Per caricare un documento XML con Mozilla FireFox il codice sorgente necessario sarà simile a questo:

var xmlDoc = document.implementation.createDocument("","",null);
xmlDoc.async = false;
xmlDoc.load("documento.xml");

Uno script cross-browsers

Viste le differenze tra i browsers nella creazione di una istanza del parser, viene comodo creare uno script che, rilevato il browser utente utilizzato, sia in grado di istanziare l'oggetto corretto ed eseguire il parsing del documento XML caricato.

L'esempio che sto per esporre, anche se funzionante, rappresenta lo scheletro di una chiamata AJAX e quindi dovrebbe essere implementato e raffinato per divenire più robusto.

<script type="text/JavaScript">

function loadXml(url){
   var xmlDoc;
   var is_IE = (tipeof window.ActiveXObject != 'undefined');
   var is_MOZ = ! is_IE;
   
   xmlDoc = (is_MOZ) 
      ? document.implementation.createDocument("","",null)
      : new ActiveXObject("Microsoft.XMLDOM"); 
      /*
      E' possibile eseguire ulteriori test per trovare
      la versione corretta dell'identificatore di casa Microsoft
      */
      
   xmlDoc.async = false;
   xmlDoc.load(url);
   
   return xmlDoc;
}

</script>

In questo modo si ha la possibilità di caricare correttamente il documento XML e di eseguirne il parsing semplicemente con il browser che preferiamo.

Per approfondire ulteriormente l'argomento puoi leggere un mio articolo che tratta proprio la navigazione tra i DOM Nodes.

Alla prossima,
MA.

martedì 26 gennaio 2010

Javascript: Recuperare i parametri dalle URLs

Capita spesso di aver bisogno di leggere il valore di un parametro presente in querystring, e spesso per un solo dannato parametro si sceglie di intervenire lato server con script PHP o servlet Java(o Action Struts) e JSP.

Molto spesso la cosa è scocciante perchè magari non ritengo che sia così importante da dedicargli uno script lato server.


Quindi, grazie all'oggetto JavaScript Location, che rappresenta la barra degli indirizzi di ogni browser, ho potuto risolvere il mio problema: una funzione che analizza la querystring e mi restituisce il valore del parametro cercato, simulando una GET HTTP lato sever.

La funzione è molto semplice e molto funzionale.

Immaginiamo di dover estrapolare i valori dei parametri di una URL così fatta:

http://applicazione.intranet?id=222&ctx=corp&next=fwd

Procedendo per step:

  • il primo passo è quello di separare l'intera URL per poter avere l'indirizzo della risorsa richiesta e l'elenco dei parametri;
  • Il secondo passo è quello di creare l'elenco dei parametri presenti in querystring, rappresentati ogniuno da una coppia chiave/valore separata da un uguale. ES: ctx=corp;
  • ultimo passo è quello di separare il valore del parametro dal suo nome;

In pochi passaggi, grazie alle funzioni per il trattamento delle stringhe che JavaScript espone, saremo in grado di avere il nome di tutti i parametri passati in querystring con i rispettivi valori comodamente stockati in un array associativo.

Passo ora ad illustrare il codice della funzione JavaScript getParameter() per l'analisi della URL e la raccolta dei parametri contenuti in querystring.

Come al solito, il codice è abbondantemente commentato in tutti i suoi passaggi più caldi.

<script type="javascript">
<--
function getParameter(){
//Array Associativo che conterrà i parametri presenti 
//in querystring
var allParams = new Array();

//Recupero la URL visualizzata
var url = unescape( String(this.location) );

//Restituisco NULL se non ci sono i parametri
if(url.indexOf('?') < 0)
return null;

//Recupero la lista dei parametri/valori
var paramList = url.split("?")[1];

//Recupero ogni coppia chiave/valore
var params = paramList.split("&");

//Scorro tutte le coppie chiave/valore
//e le separo
for(var i=0; i<params.length; i++){
var temp = params[i].split("=");

//Carico l'array con tutti i parametri trovati
allParams[temp[0]] = temp[1];
}

//Restituisco l'array dei parametri con i rispettivi valori
return allParams;
}

// ****** ESEMPIO DI UTILIZZO ***********/

//Carico i parametri
var params = getParameter();

//Leggo il parametro ctx
var paramCtx = params['ctx'];
-->
</section>

Come precedentemente annunciato, la funzione non è assolutamente complessa ed è illustrato anche il modo di utilizzo tramite un esempio.

Poichè nella funzione getParameter() sono stati utilizzati esclusivamente oggetti Javascript standard, essa è assolutamente utilizzabile su qualunque browser esistente.

Alla prossima,
MA

domenica 24 gennaio 2010

Javascript: Traversing the D.O.M.

Molto spesso, soprattutto in fase di debug, mi e' comodo poter visualizzare in un documento HTML il risultato di una chiamata AJAX ad una risorsa XML e quindi mi sono attrezzato creando una funzione JavaScript che ricevuto un DOM-Object, tramite il metodo responseXML della classe JavaScript XMLHttpRequest, lo naviga ricorsivamente nodo per nodo, passando per ogni nodo figlio.

La classe XMLHttpRequest e' il cuore del framework AJAX, che sta per Asyncronus JavaScript And XML, ovvero il padre della nuova frontiera dei servizi Web: il famoso Web2.0.

Il compito della funzione javascript che porto come esempio e' di attraversare un D.O.M. object partendo da qualsiasi nodo del documento, che sia la radice (root) o un nodo figlio (childNode).
Ad ogni childNode che incontra la funzione verifica se questo nodo possiede a sua volta altri figli, e quindi rappresenta un ramo dell'albero XML, oppure se e' solo una foglia.
Nel primo caso, la funzione si richiama da se per scorrere ricorsivamente il nuovo ramo fino ad incontrare una foglia, altrimenti, terminata la lista di childNode, continua nell'analisi del nodo successivo.

Il funzionamento dello script javascript e' stato testato con esito positivo sui due browser piu' famosi: Mozilla FireFox ed Internet Explorer 6 e 7.

Se qualcuno di voi avesse testato lo script con altri prodotti potrebbe gentilmente postare un commento all'articolo.

Passiamo quindi ad analizzare il prototipo della funzione:

function walkXmlTree(xmlelement, htmlelement, cssFatherTagName
                      , cssChildTagName, cssTagData);

Dunque la funzione javascript walkXmlTree riceve 5 parametri:

  • xmlelement: l'elemento XML radice da cui iniziare a scorrere il DOM XML
  • htmlelement: un oggetto HTML nel quale appendere il contenuto del DOM XML (solitamente un DIV)
  • cssFatherTagName: classe CSS per decorare i nodi Padre XML (ParentNode)
  • cssChildTagName: classe CSS per decorare i nodi Figlio XML (childNode)
  • cssTagData: classe CSS per decorare i nodi testo (foglie) XML (textNode)

E' ora il momento di passare all'analisi del codice sorgente dell'esempio: come al solito lo spiattello lì, e come sempre sono ben commentati ogni passaggio critico ed ogni punto chiave del codice sorgente.

Buona Visione.

function walkXmlTree(xmlelement, htmlelement 
    ,cssFatherTagName, cssChildTagName, cssTagData){

  var obj = xmlelement;
  var elem = htmlelement;

  //Se cssFatherTagName e' null lo inizializzo con '' 
  var cssFather = (cssFatherTagName != null) ? cssFatherTagName : '';
  var cssChild = (cssChildTagName != null) ? cssChildTagName : '';
  var cssData = (cssTagData != null) ? cssTagData : '';

  //Scorro tutti i childNodes del nodo in analisi 
  for(var i=0;i<obj.childNodes.length;i++){
    var child = obj.childNodes[i]; 

    /**
     * Se è un nodo di Tipo ELEMENT_NODE ed ha Figli 
     * ne prendo il valore, ricordo che il valore di un nodo 
     * è trattato come figlio e precisamente è un TEXT_NODE
     */
    if(child.nodeType == 1 && child.childNodes != null 
        && child.childNodes.length > 0){
      if(child.childNodes.length == 1){
        /**
         * In assenza di attributi (ATTRIBUTE_NODE) 
         * il valore testo di un nodo e' sempre il primo figlio
         * e lo si preleva accedendo alla sua proprietà 'data' 
         */ 
         if(child.firstChild.data != null 
            && child.firstChild.data != undefined){
          /**
           * La proprietà innerHTML mi garantisce la massima 
           * compatibilità con tutti i tipi di browser
           */
          elem.innerHTML += '<span class="' + cssChild + '">'
              + child.tagName + '</span>: '
              + '<span class="' + cssData + '">'
              + child.firstChild.data + '</span><br>';
         }
      }  
      /**
       * Nel caso il nodo in esame abbia piu' di un figlio
       * richiamo ricorsivamente walkXmlTree passandogli il
       * suddetto nodo Padre.
       */
      else{
        elem.innerHTML += '<br><span class="' + cssFather 
            + '">' + child.tagName + '</span>:<br>';
        walkXmlTree(child,elem,cssFather,cssChild,cssData);
      }
    }
  } 
}

Il tutto e' molto trasparente, non c'è un granchè da aggiungere.
Se avete scoperto di avere delle lacune sul DOM Object potete dare un occhio alla più autorevole fonte di standard per il WEB: il consorzio W3C.

Alla prossima,
MA.

Javascript: Gestire i Preferiti (FF, IE)

Dunque, sappiammo tutti quanto può essere importante per un webmaster offrire ai propri visitatori un modo semplice ed efficace per non scordarsi l'indirizzo del sito e quindi per tornare a farci visita.

Oltre ovviamente la qualità dei contenuti, è opportuno fornirsi di un meccanismo con cui l'utente possa salvare l'indirizzo della home page con un click ed in modo a lui trasparente: la soluzione, standard ed efficiente, è il solito link Bookmark this Site (in italiano, Aggiungi ai Preferiti).

Grazie a Javascript ed agli oggetti esposti dai browsers più conosciuti bastano proprio poche righe di codice per poter aprire la cartella dei "Preferiti" dei nostri visitatori e fargli salvare il link al vostro sito.

La soluzione che io propongo funziona con tutte le recenti versioni di IE ed Mozilla Firefox, forse anche con Opera e Safari ma questi non li ho potuti testare.

Lo script è composto da una semplice funzione, inserita nell'intestazione (HEAD) della pagina e all'interno del tag "<script>", che è in grado di riconoscere il browser grazie ad un oggetto che lo caratterizza ("window.sidebar" è valido per Moz. FireFox mentre "window.external" identifica IE) e di sfruttare questa peculiarità.

Dichiarata la funzione si crea un normale link HTML che la attiva tramite il protocollo javascript:. Di seguito lo script contenente la funzione:

<script type="text/javascript">
  //Da cambiare a proprio piacimento  
  var urlAddress = "http://mirkoagrati.blogspot.com";
  var pageName = "Script e Articoli Informatici";

  function addToFavorites() {
    if (window.sidebar) {
      // Mozilla Firefox Bookmark
      window.sidebar.addPanel(pageName, urlAddress,"");
    }
    else if (window.external) {
      window.external.AddFavorite(urlAddress,pageName);
    }
    else {
      alert("Attenzione, il tuo browser non supporta questa funzione."
          + "\nAggiungi manualmente il sito ai tuoi Preferiti");
    }
  } 
</script>

Di seguito il link per attivare la funzione addToFavorites():


<a href="javascript:addToFavorites();"> Bookmark this Site </a>

Il meccanismo è molto semplice e prevede anche un paracadute nel caso in cui il browser utente fornisca un interfaccia non contemplata.

Alla prossima,
MA.

sabato 23 gennaio 2010

Le mie notizie su ilbloggatore.com

La mia richiesta di poter far figurare i miei articoli tecnici tra i feed de ilbloggatore.com è stata soddisfatta: lo staff mi ha dato l'OK.

Per chi non conoscesse ancora il servizio in questione, lo si potrebbe riassumere con queste parole:
si tratta di un aggregatore di feed on line, orientato a tutti quei blog che trattano argomenti legati al mondo dell’informatica!

Io lo seguo da parecchio tempo, ricevo tutte le mattine i feed RSS e devo dire che ho sempre trovato più di una notizia interessante.

Quindi, se siete abbonati ai feed RSS de il Bloggatoe.com, da oggi potreste imbattervi in un mio articolo.

Alla prossima,
MA

venerdì 22 gennaio 2010

Java: Formattare Numeri con NumberFormat

Può succedere a volte di trovare brutte sorprese nella rappresentazione di dati numerici formattati senza tenere conto dell'internazionalizzazione.

Internazionalizzare un'applicazione significa offrire all'utente un'interfaccia grafica familiare, non solo dal punto di vista della lingua utilizzata nei menù e nelle etichette ma anche l'utilizzo della giusta punteggiatura e formattazione nell'esposizione dei dati.

In Italia siamo soliti utilizzare il punto come separatore delle migliaia e la virgola per separare la parte decimale di un numero, aritmetico o finanziario che sia.

Non è così dappertutto.

Nei paesi anglosassoni solitamente è l'inverso, ossia il punto è utilizzato per indicare la parte decimale e la virgola per le migliaia.

Esempio:
Un milione di Euro in Italia è scritto € 1.000.000,000 .
Un milione di Sterline in Inghilterra si scrive £ 1,000,000.000


Nel preparare parte di un'applicazione riempivo delle combo box con numeri decimali formattandoli semplicemente con l'istruzione:

java.text.DecimalFormat df = new java.text.DecimalFormat("0.000");
System.out.println(df.format(222.344));

Ottenendo come risultato ciò che volevo, ovvero un numero decimale formattato con la virgola come separatore dei decimali, quindi 222,344.

Quando ho fatto il deploy dell'applicazione sul server di produzione ho purtroppo scoperto che la cosa non funzionava più come avrebbe dovuto.

Mi chiesi:
"Ma che differenza c'è tra il l'ambiente di test e quello di produzione?"

La differenza c'era .... e come!!!

L'ambiente di test risiedeva su macchine con sistema operativo in lingua Italiana mentre quello di produzione era configurato con la lingua Americana.

Ciò che vedevo nelle mie JSP erano numeri formattati utilizzando la virgola come separatore delle migliaia ed il punto per i decimali, in questa maniera 1,234.445 .

Come ho risolto il problema?

Ho utilizzato la classe java.util.Locale: questa assicura una formattazione rispettosa degli standard linguistici che si vogliono usare.

Nel mio caso è stato veramente semplice risolvere il problema, tra l'altro questa classe mi assicura che se in futuro utilizzeremo server in lingua tedesca o cinese, i miei risultati numerici saranno sempre e comunque visualizzati secondo gli standard della mia nazione: la virgola separa i decimali ed il punto separa le migliaia. Eccome come ho trasformato il codice per risolvere il problema:

java.text.NumberFormat nf =
    java.text.DecimalFormat.getInstance(java.util.Locale.ITALIAN);
System.out.println(nf.format(21622.344));

Finalmente ciò che ottenevo era 21.622,344 indipendentemente dall'ambiente nel quale era stata deployata l'applicazione.

Alla prossima,
MA.

domenica 17 gennaio 2010

Java: Codifica Unicode di buffer UTF-8

In questo articolo parlerò di come poter codificare e decodificare un buffer di dati UTF-8 proveniente dal WEB in modo da poterlo salvare temporaneamente, attraverso transazioni CICS, su tabelle DB2 di dominio per essere travasato, in fine, sulla piattaforma ORACLE di marketing.

Per fare questo si è deciso di utilizzare come set di caratteri finale il charset Unicode.


Premesse:

  • Le transazioni CICS utilizzano buffer con charset EBCDIC che è un set molto molto ristretto di caratteri.
  • La piattaforma di marketing supporta nativamente (anche se con un formato proprietario) il set di caratteri Unicode;
  • Gli strati software che i dati devo attarversare A/R sono differenti tra loro anche nella codifica dei dati utilizzata da ognuno: la parte WEB utilizza la codifica UTF-8, CICS utilizza EBCDIC e ORACLE è configurato in Unicode;

Indipendentemente dalla scelta finale del charset da utilizzare, il passaggio attraverso il CICS è obbligatorio per scelte architetturali e funzionali del cliente finale e quindi è comunque prevista una fase di conversione dei dati.

Ovviamente l'implementazione del componente non può prescindere dal fatto che i dati già presenti dovranno essere normalizzati e siccome i sistemi informativi del cliente lavorano la notte quanto il giorno questa operazione dovrà essere svolta durante le normali sessioni di lavoro:
una cosa morbida, mentre l'utente lavora e nel momento che salverà i dati, questi saranno codificati con il nuovo set di caratteri Unicode, e durante la fase di lettura decodificati in UTF-8.

Il componente che ho creato per svolgere questi compiti e che presento in questo articolo è la classe BufferEncoder.

La classe, ricevuto uno stream di caratteri lo parsa carattere per carattere, eseguendo dei controlli anche sui successivi, per poter valutare se ha a che fare con una sequenza di caratteri Unicode oppure UTF-8.

Gestisce situazioni particolari, come la presenza di un'entità HTML di cui ne rileva la posizione, lunghezza ed valore all'interno dello stesso stream.

Per motivi di performance non viene mai creata una istanza della classe, quindi è utlizzata in modo statico. Non conserva alcuno stato: è un puro,semplice ed efficace buffer burner.

Per risolvere il problema si devono trattare diversamente le fasi di invio (codifica) e di ricezione (decodifica) dei dati provenienti dalle transazioni CICS.


Browser to CICS: da UTF-8 a Unicode attraversando EBCIDC


Inviando i dati dei forms HTML a CICS, i caratteri estesi digitati, anche da paesi dell'est europeo, dovranno essere codificati in formato Unicode per ORACLE.
Deve essere tenuta in considerazione la possibilità che alcuni browsers non configurati in UTF-8 potrebbero inviare l'entità HTML rappresentante il carattere digitato anzichè il carattere stesso e che parti di applicazione utilizzano le librerie JAXB per l'invio di informazioni in formato XML.

Di questa fase è incaricato il metodo statico:
static final String encodeToUnicode(String utf8, boolean isxml).

Il metodo riceve in input una stringa con charset UTF-8 e un flag che indica se è XML o semplice testo; potrebbe, per esempio, essere:
ioeis Ósss § sssÓ sieosi
.

Il CICS, che è configurato per utilizzare il set di caratteri EBCDIC, non può comprendere i caratteri estesi 'Ó' e '§': bisognerà fare quindi in modo che tutte le rappresentazioni intere (conversione ad int di un char) dei caratteri non inclusi da EBCDIC vengano codificati in Unicode.

La stringa di esempio sarà codificata e alla fine del processo apparirà così:
ioeis \00D3sss \00A7 sss\00C3 sieosi.

In fase di codifica, la prima cosa di cui il componente si occupa è evitare che i dati da preparare per ORACLE non siano interpretabili per via di sequenze di caratteri sfuggite. Per pianare i dati vengono chiamati in successione i metodi statici:


  1. static final String normalizeHtmlEntity(utf8);
  2. static final String replaceHtmlEntityWithUnicode(utf8);

Il primo metodo normalizza tutte le entità HTML presenti nella stringa da codificare, che possono arrivare dal parser JAXB o da qualche browser con settaggi particolari. Per esempio:
o diventerebbe o .

La ricerca e la sostituzione delle entità è delegato al secondo metodo: questo lavora a basso livello sulla stringa e la sua logica è fondata su alcune convenzioni che internamente al CRM ci siamo dati. Per esempio, abbiamo stabilito che l'entità:

  1. Deve avere lunghezza 6 caratteri;
  2. La sua forma deve essere &#nnn;

L'unica eccezione ammessa e considerata è l'entità &, che è lunga 5 caratteri e ha una formattazione completamente differente.

Per riconoscere quindi un'entità così fatta, il metodo verifica che esistano i token '&#' e ';' e che questi non siano distanti più di 5 caratteri.

Il passo finale della fase di codifica del buffer è trasformare tutti quei caratteri esclusi dal charset EBCDIC (è stato considerato il range: 32 <= (int)char <= 125) in una sequenza utile a rappresentare caratteri Unicode e restituirla a chi si occupa di dialogare con CICS.


CICS to browsers: da Unicode a UTF-8


L'opposto invece per quel che riguarda l'invio dei dati da CICS verso il WEB: siccome i caratteri estesi, salvati in fase di codifica, saranno in formato Unicode per ORACLE, dovranno essere decodificati in entità HTML per essere interpretati correttamente dai browsers.

Il protagonista della fase di decodifica del buffer inviato dal CICS è il metodo:
final static String decodeUnicodeToUTF8(String unicode);

Poichè i dati sono stati sottoposti a rigidi controlli prima di essere salvati, la fase di decodifica non esegue nessuna verifica, bensì si occupa esclusivamente di trasformare le sequenze Unicode in HTML entities.

Anche in questo caso sono state auto-imposte delle regole, tipo il formato \hhhh che deve avere una sequenza unicode per essere considerata e trasformata.

La classe è testata e svolge correttamente il suo 'sporco' lavoro.

Di seguito il codice completo della classe BufferEncoder.

/**
* Classe che gestisce l'encoding ed il decoding dei caratteri
* nelle transazioni da e verso CICS e DB2.
*
* @author Agrati Mirko
* @version 26-ago-09 v2.2
*/
public class BufferEncoder {

public final static String CODE_PAGE_EBCDIC_ITA = "IBM01144";

/** Parametro necessario per la codifica in esadecimale.*/
private final static int HEX_BASE_NUMBER = 16;

/** Lunghezza prestabilita delle sequenze esadecimali.*/
private final static int HEX_SEQ_LENGHT = 4;

/** range consentito per accettare i caratteri UTF-8
* senza trasformarli in sequenze esadecimali */
private static final int EBCDIC_LOWER_INT_CHAR_VALUE = 32;
private static final int EBCDIC_UPPER_INT_CHAR_VALUE = 125;

/** Caratteri di controllo.*/
private final static int CHAR_NOT_FOUND = -1;

/**
* Trasforma tutti i caratteri esclusi dal range
* prestabilito all'interno del set di caratteri ASCII
* nel relativo carattere esteso con codifica Unicode
* in stile ORACLE (\00af).
* Per convenzione si è deciso di utilizzare una codifica
* di 4 byte esadecimali.
*
* @param utf8         stringa UTF-8 da codificare in EBCDIC
* @param isXmlContent indica se la stringa codificata deve essere
*                     contenuta in un tag XML.
* @return             se <code>utf8 == null</code> restituisce blank,
*                     altrimenti la stringa codificata.
*/
public final static String encodeToUnicode(String utf8
, boolean isXmlContent){
if(utf8 == null || utf8.trim().equals("")) return "";

utf8 = normalizeHtmlEntity(utf8);
utf8 = replaceHtmlEntityWithUnicode(utf8);  

StringBuffer unicode = new StringBuffer();
char[] c = utf8.toCharArray();
int v = 0;

for(int i=0; i<c.length; i++){
v = (int)c[i];
if(EBCDIC_LOWER_INT_CHAR_VALUE <= v
&& v <= EBCDIC_UPPER_INT_CHAR_VALUE){
if(isXmlContent){
switch(c[i]){
case '<':
unicode.append("<");
break;
case '>':
unicode.append("&gt");
break;
default:
unicode.append(c[i]);
break;
}
}
else{ unicode.append(c[i]);}
}
else{ unicode.append("\\" + toHex(c[i]));}
}
return unicode.toString();
}

/**
* Trasforma tutti i caratteri Unicode nei relativi
* caratteri UTF-8.
* Per convenzione interna al CRM vengono codificati
* i 4 byte esadecimali successivi ad ogni '\' incontrato.
*
* @param unicode stringa Unicode da decodificare
* @return        se unicode == null restituisce blank,
*                altrimenti la stringa decodificata.
*/
public final static String decodeUnicodeToUTF8(String unicode){
if(unicode == null || unicode.trim().equals("")) return "";
StringBuffer utf8 = new StringBuffer();
char[] stream = unicode.toCharArray();

for(int i=0; i<stream.length; i++){
if(stream[i] != '\\'){ utf8.append(stream[i]);}
else{
StringBuffer hex = new StringBuffer();
i++;
hex.append(stream[i]);
i++;
hex.append(stream[i]);
i++;
hex.append(stream[i]);
i++;
hex.append(stream[i]);
int conv = toInt(hex.toString());
if(conv != 0)
utf8.append("&#" + toInt(hex.toString()) + ";");
}
}
return utf8.toString();
}

/**
* Trasforma un carattere in una stringa di base esadecimale
* a lunghezza fissa di 4 byte.
*
* @param  c  Il carattere da trasformare;
* @return    La stringa relativa in base esadecimale.
*/
public final static String toHex(char c){
String s = Integer.toHexString((int)c);

while(s.length()<HEX_SEQ_LENGHT){
s = "0" + s;
}
return s.toUpperCase();
}

/**
* Trasforma una stringa di base esadecimale a
* lunghezza fissa di 4 byte
* nel relativo carattere.
*
* @param  hex  La stringa da trasformare;
* @return      Il relativo carattere.
*/
public final static char toChar(String hex){
if(hex == null) return ' ';
int res = toInt(hex);
return (res == 0) ? ' ':(char)res;
}

/**
* Trasforma una stringa di base esadecimale a
* lunghezza fissa di 4b. nel relativo valore intero.
*
* @param  hex  La stringa da trasformare;
* @return      Il relativo valore intero.
*/ 
public final static int toInt(String hex){
try{
return Integer.parseInt(hex, HEX_BASE_NUMBER);
}
catch(Exception e){ return 0;}
}

/**
* Sostituisce tutte le entità HTML contenute in
* una stringa con i relativi codici unicode.
*
* @param  utf8  Stringa con possibili entità HTML
*               da convertire in Unicode.
* @return       Stringa senza entità HTML.
*/
private final static String
replaceHtmlEntityWithUnicode(String utf8){
char[] temp =
String.copyValueOf(utf8.toCharArray()).toCharArray();
StringBuffer exit = new StringBuffer();

for(int i=0; i<temp.length; i++){
//Se può essere una entità
if(temp[i] == '&' && (i+1)<temp.length
&& temp[i+1] == '#'){
int tappo = CHAR_NOT_FOUND;
//Cerco il tappo: per specifica interna non può
//essere oltre di 6 caratteri dal token '&#'
for(int j=2; j<7; j++){
if((i+j)<temp.length && temp[i+j] == ';'){
tappo = i+j;
break;
}
}
//Se trovo il tappo allora è una Entità HTML
if(tappo != CHAR_NOT_FOUND){
String entityValue = utf8.substring(i+2,tappo);
StringBuffer buildHex = new StringBuffer();
buildHex.append("\\" + toHex((char)Integer.parseInt(entityValue)));
exit.append(buildHex);
i=tappo;
}
}   
else{ exit.append(temp[i]); }
}
return exit.toString();
}

/**
* Normalizza tutte le entità HTML.
* Dal formato o al formato o.
*
* @param  utf8  stringa in cui sostituire le entità HTML
* @return       la stringa in formato UTF8 correttamente
*               considerato da BufferEncoder.
*/
private final static String normalizeHtmlEntity(String utf8){  
StringBuffer res = new StringBuffer(); 
char[] streamJaxb = utf8.toCharArray();

for(int i=0; i<streamJaxb.length; i++){
char c = streamJaxb[i];

/* Verifico che la ricerca dei caratteri non vada
* oltre la lunghezza reale dell'array */
if(streamJaxb.length > i+5){
char c1 = streamJaxb[i+1];
char c2 = streamJaxb[i+2];
char c3 = streamJaxb[i+3];
char c4 = streamJaxb[i+4];
char c5 = streamJaxb[i+5];

if(c=='&' && c1=='a' && c2=='m'
&& c3=='p' && c4==';' && c5=='#'){
i +=4;
}
}
res.append(c);
}
return res.toString();
}

public static void main(String[] args){
String t = "<òòçç@@##àà°°ùù§§";
String uni = BufferEncoder.encodeToUnicode(t,true);
System.out.println("CODIFICA UTF8->UNICODE"
+ "(Per contenuti XML):\n\t\t" + uni);
System.out.println("DECODIFICA UNICODE->UTF8:\n\t\t"
+ BufferEncoder.decodeUnicodeToUTF8(uni));
uni = BufferEncoder.encodeToUnicode(t,false);
System.out.println("CODIFICA UTF8->UNICODE:\n\t\t" + uni);
System.out.println("DECODIFICA UNICODE->UTF8:\n\t\t"
+ BufferEncoder.decodeUnicodeToUTF8(uni));
}
}

Il caso esposto in questo articolo è molto particolare ma la classe costruita può ritornare molto utile in caso capitasse di dover operare su applicativi tecnologicamente non all'avanguardia: il procedimento di sostituzione infatti rimane sempre valido basta solo descrivere cosa cercare di nuovo.

Alla prossima,
MA.

sabato 16 gennaio 2010

HTML5: Panoramica della nuova Tecnologia

Proprio questa notte ho pubblicato un nuovo articolo su HTML5 nel Wiki di giorgiotave.it .

Non si tratta di un articolo prettamente tecnico ma piuttosto è una panoramica abbastanza completa e profonda di ciò che la nuova tecnologia già ci permette di sfruttare e di ciò che sarà possibile sviluppare a breve tramite componenti open source e standardizzati.

L'articolo non è ancora passato al vaglio del Direttore di redazione e, probabilmente, necessiterà di qualche ritocco formale; comunque gli argomenti affrontati sono molti e variegati.

Si parte con accenni storici riguardanti l'ultima release di HTML 4 e con la costituzione del gruppo di lavoro Web Hypertext Application Technology Working Group (WhatWG) da parte dei produttori di browsers Mozilla Foundation, Opera e Apple per procede poi con l'introduzione alla nuova tecnologia: lo stato attuale di draft e le potenzialità del linguaggio:

HTML5 permetterà facilmente la creazione di pagine multimediali mediante componenti open source(differentemente dalla situazione attuale che è dominata da plug-in esterni ai browsers perchè standards proprietari), offrirà ampie possibilità di caching locale dei dati per un utilizzo off-line delle applicazioni e, non da meno, darà la possibilità di attribuire un significato semantico alle varie aree delle pagine e dei rispettivi contenuti.

L'articolo fornisce un'ampia carrellata degli elementi già definiti ed interpretabili dai browsers, quindi utilizzabili già oggi nelle proprie applicazioni, siti e blogs.

Si conclude con alcune riflessioni sull'utilizzo che i motori di ricerca potrebbero fare delle pagine e di come le regole per l'indicizzazione SEO potrebbero cambiare.

Per leggere l'articolo redatto nel wikiGT basta seguire questo link.

Nel caso l'articolo fosse troppo lungo, è stato creato un indice ben particolareggiato in testa alla pagina, quindi sarà immediato trovare l'argomento di proprio interesse.


Ovviamente commenti, suggerimenti e domande sono ben voluti.

Alla prossima,
MA.

mercoledì 13 gennaio 2010

XPath: a cosa serve XPath

XPath (ovvero percorso XML) è un linguaggio interpretato utilizzato per estrapolare informazioni dai nodi di un documento XML.

XPath è utilizzato per navigare attraverso gli elementi e gli attributi di un documento XML.

Le relazioni tra i nodi, all'interno del documento XML, sono descritte metaforicamente alla stessa maniera di una famiglia:

  • Parent;
  • Children;
  • Siblings;
  • Anchestor;
  • Descendants;

Parent

Tutti gli elementi (esclusa la radice), attributi o nodi-testo, hanno un genitore. Per esempio:

<pianta>
  <rami>
    <ramo></ramo>
  </rami>
</pianta>

L'elemento Parent del nodo '<ramo>' è '<rami>' che a sua volta ha come nodo parent '<pianta>'.

Children

I nodi di tipo Element possono avere uno o più figli. Considerando l'esempio precedente, l'elemento '<ramo>' è Children di '<rami>' che a sua volta è children di '<pianta>'.

Siblings

I nodi di tipo Element hanno una relazione detta Sibling solo se esistono altri nodi di pari livello con lo stesso parent. Nell'esempio seguente sono tra loro sibling gli elementi '<ramo>' e '<ramoscello>':

<pianta>
  <rami>
    <ramo>
      <ramoscello />
    </ramo>
  </rami>
</pianta>

Anchestors

Sono Anchestors (o ascendenti) di un nodo tutti gli elementi che lo precedono, radice compresa. Nel seguente esempio:

<pianta>
  <rami>
    <ramo />
  </rami>
</pianta>

Sono anchestors di '<ramo>' gli elementi '<rami>' e '<pianta>'.

Descendants

Sono Descendants (o discendenti) di un nodo tutti gli elementi che esso racchiude. Nel seguente esempio:

<pianta>
  <rami>
    <ramo>
      <ramoscello />
    </ramo>
  </rami>
</pianta>

Sono descendants di '<pianta>' gli elementi '<rami>', '<ramo>' e '<ramoscello>'.

Conclusioni

Anche se apparentemente ciò che abbiamo visto sembra molto banale, imparare e comprendere le tipologie di relazioni possibili tra gli elementi di un documento XML é fondamentale per poter sfruttare la potenza e la modalità di funzionamento di XPath.

Alla prossima,
MA.

lunedì 11 gennaio 2010

PHP: Deploy di una Applicazione

Esistono differenti modi per gestire i parametri necessari per configurare un'applicazione PHP.

Spesso infatti ci si trova a dover scrivere in qualche file php il nome, la password e l'indirizzo del DB Server a cui bisogna connettersi, oppure, se si devono eseguire accessi al file system bisogna conoscere e segnare in qualche altro script il path con cui accedere alla nostra cartella.

E tutte queste informazioni devono essere preparate due volte:

  1. per configurare l'ambiente di test;
  2. per la configurazione dell'ambiente di 'produzione'.

Le soluzioni in questi casi si 'sprecano': possono essere preparati due script con variabili globali per ogni dato necessario, può essere una valida scelta definire un array associativo nel quale il nome della caratteristica rappresenta la chiave per accedere al rispettivo valore.

Ciò che sto per proporre è la soluzione definitiva che da tempo adotto durante lo sviluppo dei progetti PHP e, siccome con il rilascio della release 5.x anche PHP offre supporto alla OOP, incapsulerò la logica in una classe.


La mia soluzione: l'indirizzo IP di Loopback

Come è possibile utilizzare la stessa classe per configurare contemporaneamente gli ambienti di Test e di Produzione?
Il meccanismo alla base di tutto sta nello sfruttare l'indirizzo IP di localhost, che viene risolto in 127.0.0.1, detto anche indirizzo IP di loopback.

Quindi, verificando che l'indirizzo IP dell'host su cui risiede l'applicazione richiesta sia 127.0.0.1 sarò in grado di capire se sto lavorando in ambiente di test o viceversa in produzione.

Questa logica è incapsulata in una classe che sarà utilizzata per fornire all'applicazione i parametri di configurazione dei diversi servizi (DB, email, I/O) in maniera dinamica e consapevole, perchè la classe riconosce il contesto in cui gira l'applicazione.


La classe AppConfigurator.

AppConfigurator è la classe che ho scelto come esempio per mostrare come poter gestire dinamicamente la configurazione dell'ambiente di Test e contemporaneamente anche quello di Produzione.

In questo esempio riporterò solo alcuni dei parametri che solitamente sono considerati necessari durante lo sviluppo di un'applicazione PHP.


/**
 * Capisce il contesto in cui si trova l'applicativo
 * e restituisce i parametri di configurazione sia in 
 * ambiente di test che di produzione.
 */
class AppConfigurator{
  const TEST_SERVER_IP = '127.0.0.1';

  /** DB **/
  const TEST_DB_SERVER_NAME = "localhost";
  const DB_SERVER_NAME = "xxx.xxx.xxx.xxx";
  const TEST_DB_NAME = "este_new";
  const DB_NAME = "xxxxxx263588_1";
  const TEST_DB_USER_NAME = "este?????ser";
  const TEST_DB_USER_PWD = "_______";
  const DB_USER_NAME = "nnnnnnnnnn";
  const DB_USER_PWD = "-/_/_/_/_/_";

  public static function getDbServerName(){
    return (AppConfigurator::isTestEnvironment())
        ? AppConfigurator::TEST_DB_SERVER_NAME
        : AppConfigurator::DB_SERVER_NAME;
  } 

  public static function getDbName(){
    return (AppConfigurator::isTestEnvironment())
        ? AppConfigurator::TEST_DB_NAME 
        : AppConfigurator::DB_NAME;
  }

  public static function getDbUserName(){
    return (AppConfigurator::isTestEnvironment())
        ? AppConfigurator::TEST_DB_USER_NAME
        : AppConfigurator::DB_USER_NAME;
  }

  public static function getDbUserPwd(){
    return (AppConfigurator::isTestEnvironment())
        ? AppConfigurator::TEST_DB_USER_PWD
        : AppConfigurator::DB_USER_PWD;
  }

  /**
   * Questo metodo incapsula la logica per capire 
   * il contesto applicativo.
   *
   * @return boolean true se l'applicazione gira in 
   *                 ambiente di test.
   */
  private static function isTestEnvironment(){
    return
      (trim($_SERVER['SERVER_ADDR']) == AppConfigurator::TEST_SERVER_IP);
  }
}

Conclusioni

La classe AppConfigurator è eccezionale nella sua semplicità e nel modo in cui offre la possibilità di sviluppare applicazioni senza doverci ricordare al momento del deploy di dover sostituire uno script o i valori di alcune variabili globali.

Tecnicamente la classe oltre ad esporre direttamente le costanti applicative, in modo tale che possano essere utilizzate per altri scopi, espone un solo metodo statico per ogni parametro applicativo (rappresentato da 2 proprietà della classe tra cui dover scegliere). Ogni metodo pubblico incapsula una chiamata al cuore della classe, ovvero il metodo statico privato AppConfigurator::isTestEnvironment().

E' infatti in questo metodo che vengono confrontati l'indirizzo IP del server, su cui gira l'applicazione richiesta, e l'indirizzo di loopback per conoscere il contesto applicativo ed i vincoli da soddisfare.

Alla prossima,
MA.

Cos'è una cache e come funziona


La cache è un insieme di dati che viene memorizzato in una posizione temporanea, dalla quale possa essere recuperato velocemente su richiesta.

Le parole chiave sono temporanea e velocemente: in pratica, questo significa che non c'è nessuna certezza che i dati si trovino nella cache, ma che convenga comunque fare un tentativo per verificarne l'eventuale esistenza.

Quando è necessario l'accesso ad un dato, questo dato viene prima cercato nella cache.
Se è presente e valido, viene utilizzata la copia presente.
Viceversa, viene recuperato dalla memoria principale, e memorizzato nella cache, nel caso possa servire successivamente.

Una cache riduce il carico di richieste che deve essere smaltito dalla memoria principale, e dal collegamento tra questa e l'utilizzatore dei dati. Anche questo può contribuire a migliorare le prestazioni del sistema.

Si pensi per esempio ad un server proxy utilizzato da molti utenti: quando un utente richiede una pagina che era già stata richiesta da un altro, il proxy potrà rispondere senza doversi collegare al sito originale, ed eviterà così di caricare sia il sito originale che la rete, migliorando così le prestazioni del sistema anche per le richieste che devono essere inoltrate ai siti originali.

Oppure, una situazione simile potrebbe essere il modo di lavorare di uno dei tanti servizi di Feed Reader (esempio: Google Reader) presenti in internet: lo stesso feed, per esempio le news fornite da Repubblica.it, siccome potrebbe essere richiesto da più utenti, dopo essere stato scaricato la prima volta, verrà stockato in cache per essere estratto successivamente senza dover rileggere il feed originale.

Alla prossima,
MA

domenica 10 gennaio 2010

UML: Design Pattern Singleton


Il Singleton è un design pattern creazionale che ha lo scopo di garantire che di una determinata classe venga creata una e una sola istanza, e di fornire un unico punto di accesso globale a tale istanza.

Nella programmazione ad oggetti (OOP), il Singleton è considerato uno dei pattern fondamentali.

L'implementazione più semplice di questo pattern prevede che la classe singleton abbia un unico costruttore privato, in modo da impedire l'istanziazione diretta della classe.

La classe fornisce inoltre un metodo getter statico che ritorna sempre la stessa istanza della classe, creandola preventivamente o alla prima chiamata del metodo, e memorizzandone il riferimento in un attributo privato anch'esso statico.

Come esempio da esporre, ho riutilizzato alcuni diagrammi UML creati per la progettazione di una cache singleton, appunto: si tratta della classe MASingletonCache.

Questo primo diagramma, meglio caso d'uso o use case, chiarisce il contesto iniziale del progetto, requisiti e scelte risolutive, che hanno determinato successivamente la scelta dell'implementazione del suddetto componente singleton.
Caso d'uso SingletonMi spiace per la freccia di include che non è stata completamente inclusa nell'immagine, comunque ha inizio dal caso d'uso Deve gestire accessi concorrenziali e termina all'attore Binary Safe Read and Write Methods.

Nel prossimo diagramma UML, definito di classe, viene rappresentato nel dettaglio il componente, con le sue proprietà e i suoi metodi, e si potranno apprezzare sia l'architettura con cui è costruito sia tutti i requisiti richiesti dal design pattern singleton.Diagramma di classe UML Singleton

Alla prossima,
MA

PHP: Leggere Files Remoti

Sebbene PHP consenta di accedere a risorse remote in differenti modi, attraverso l'utilizzo di wrapper tra protocolli, io personalmente preferisco utilizzare le librerie CURL, che sono ormai parte integrante del pacchetto d'installazione scaricabile direttamente dal sito php.net.

Le CURL sono di fatto un pacchetto di librerie gratuite da utilizzare da riga di comando che sono state integrate in PHP e permettono di eseguire tantissime cose tra le quali trasferire file con la sintassi degli URL via FTP, HTTP, HTTPS ecc...

Per conoscerne di più, si può dare un'occhiata a php.net ed anche a Haxx (inventori).

Per eseguire tutte queste operazioni ho incapsulato il processo di lettura di files remoti in una classe base PHP che riporto come esempio. Si tratta di una classe PHP che utilizzo tuttora.

E' molto semplice, infatti è costituita da un costruttore che riceve come parametro la URL dove risiede il file da recuperare e leggere, e possiede due coppie di metodi setter/getter per gestire le due proprietà url e content. Riporto il codice sorgente della classe commentandolo nei suoi punti critici:

class RemoteFileReader{
  private $url = "";
  private $content = "";

  function __construct($url) {
    $this->url = $url;

    //Testo l'esistenza delle cURL lib  
    if (function_exists('curl_init')) {
      //Inizializzo una nuova Risorsa
      $ch = curl_init();

      //Imposto l'URL da agganciare
      curl_setopt($ch, CURLOPT_URL, $url);
    
      //Siccome non mi interessa alcun Header
      //ma solo il contenuto del file remoto
      //imposto a zero la richiesta di Header
      curl_setopt($ch, CURLOPT_HEADER, 0);
    
      //Siccome la risposta non la voglio visualizzare
      //sul browser ma la voglio conservare imposto 1
      //alla proprieta' CURLOPT_RETURNTRANSFER
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    
      //Imposto uno user agent per simulare un browser
      curl_setopt($ch, CURLOPT_USERAGENT,
        'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5)'
        . ' Gecko/20041107 Firefox/1.0');
    
      //Setto la proprieta' content della classe con
      //il contenuto della risorsa remota
      $this->content = curl_exec($ch);
    
      //Chiudo la connessione e rilascio la risorsa
      curl_close($ch);
    }
    else {
      //Le librerie non sono installate: restituisco FALSE
      $this->content = FALSE;
    }
  }

  //Metodi Getters/Setters
  public function getContent(){
    return $this->content;
  }

  public function setContent($c){
    $this->content = $c;
  }

  public function getUrl(){
    return $this->url;
  }

  public function setUrl($c){
    $this->url = $c;
  }
}

La classe é molto semplice e tutto sommato anche l'uso delle librerie. Ora vediamo come utilizzare questa classe. Supponiamo di richiamarla in uno script PHP:

include_once 'CRemoteFileReader.php';

//URL da agganciare
$url_feed = "http://punto-informatico.it/fader/pixml.xml";

//Istanzio un oggetto RemoteFileReader
$rfR = new RemoteFileReader($url_feed);

//Test delle librerie
if (! $rfR->getContent()) {
  echo 'Librerie CURL non installate.';
  exit;
}

//Visualizzo il contenuto del file remoto
echo $rfR->getContent();

Tutto qui! Direi niente male.
Alla prossima,
MA.