<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Hello World: &#187; Apache Lucene project</title>
	<atom:link href="http://razorblade.netsons.org/tag/apache-lucene-project/feed/" rel="self" type="application/rss+xml" />
	<link>http://razorblade.netsons.org</link>
	<description>Programmazione web e oltre: php5, Zend Framework, jQuery, Actionscript 3.0, Sandy 3D Engine e altro</description>
	<lastBuildDate>Fri, 28 Aug 2009 18:23:41 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Zend Search Lucene e UTF-8 Unicode con Zend Framework</title>
		<link>http://razorblade.netsons.org/2008/10/27/zend-search-lucene-e-utf-8-unicode-con-zend-framework/</link>
		<comments>http://razorblade.netsons.org/2008/10/27/zend-search-lucene-e-utf-8-unicode-con-zend-framework/#comments</comments>
		<pubDate>Mon, 27 Oct 2008 04:12:43 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Php]]></category>
		<category><![CDATA[Zend Framework]]></category>
		<category><![CDATA[Apache Lucene project]]></category>
		<category><![CDATA[applicazioni con zend framework]]></category>
		<category><![CDATA[charset]]></category>
		<category><![CDATA[multibyte]]></category>
		<category><![CDATA[php_mbstring]]></category>
		<category><![CDATA[uft8_decode]]></category>
		<category><![CDATA[unicode]]></category>
		<category><![CDATA[UTF-8]]></category>
		<category><![CDATA[utf8_encode]]></category>
		<category><![CDATA[zend framework]]></category>
		<category><![CDATA[Zend_Search_Lucene]]></category>
		<category><![CDATA[ZF]]></category>

		<guid isPermaLink="false">http://razorblade.netsons.org/?p=43</guid>
		<description><![CDATA[<h2>Visualizzare e ricercare correttamente stringhe multibyte</h2>

<p>In questo articolo riprendiamo ciò che si era detto in <a href="http://razorblade.netsons.org/2008/09/30/zend-search-lucene-applicazione-reale-zend-framework-p10/">quest'altro articolo</a> a proposito del componente Zend_Search_Lucene, facendo delle importanti aggiunte riguardo la gestione dell'indicizzazione e ricerca di stringhe multibyte, ovvero di quelle stringhe contenenti testo in lingue tipo russo, cinese, giapponese ecc... Inoltre anche il noto problema dei caratteri accentati è eliminato per sempre. <a href="http://razorblade.netsons.org/2008/10/27/zend-search-lucene-e-utf-8-unicode-con-zend-framework/">[...] Continua</a></p>


Related posts:<ol><li><a href='http://razorblade.netsons.org/2009/04/24/luke-lucene-index-toolbox-di-andrzej-bialecki-per-zend_search_lucene/' rel='bookmark' title='Permanent Link: Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene'>Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene</a> <small>Verificare il contenuto dell'indice di Lucene Se avete seguito questo...</small></li><li><a href='http://razorblade.netsons.org/2009/02/02/zend-framework-gestione-dei-moduli-ed-esempio-modulo-di-amministrazione/' rel='bookmark' title='Permanent Link: Zend Framework: gestione dei moduli ed esempio modulo di amministrazione'>Zend Framework: gestione dei moduli ed esempio modulo di amministrazione</a> <small>Come usare Zend_Layout per la gestione dei moduli In quest'articolo...</small></li><li><a href='http://razorblade.netsons.org/2008/12/05/zend-framework-zend_form-con-recaptcha/' rel='bookmark' title='Permanent Link: Zend Framework: Zend_Form con ReCaptcha'>Zend Framework: Zend_Form con ReCaptcha</a> <small>Utilizzare il webservice ReCaptcha per validare i form ReCaptcha è...</small></li></ol>

Related posts brought to you by <a href='http://mitcho.com/code/yarpp/'>Yet Another Related Posts Plugin</a>.]]></description>
			<content:encoded><![CDATA[<h2>Visualizzare e ricercare correttamente stringhe multibyte</h2>
<p>In questo articolo riprendiamo ciò che si era detto in <a href="http://razorblade.netsons.org/2008/09/30/zend-search-lucene-applicazione-reale-zend-framework-p10/">quest&#8217;altro articolo</a> a proposito del componente Zend_Search_Lucene, facendo delle importanti aggiunte riguardo la gestione dell&#8217;indicizzazione e ricerca di stringhe multibyte, ovvero di quelle stringhe contenenti testo in lingue tipo russo, cinese, giapponese ecc&#8230; Inoltre anche il noto problema dei caratteri accentati è eliminato per sempre. </p>
<p>Prima però vedremo come implementare la corretta visualizzazione di queste effettuando alcune modifiche al codice php ed html ma anche al database.</p>
<p>Questo articolo non vuole essere una spiegazione approfondita su cosa sia un stringa multibyte, sui charset esistenti e sulla loro storia, se non siete a conoscenza di cosa stia parlando forse è meglo effettuare prima una <a href="http://www.google.it/search?hl=it&#038;q=unicode+multibyte+utf8&#038;btnG=Cerca&#038;meta=lr%3Dlang_it">ricerca</a>.
</p>
<p>Al termine di questo articolo è presente il progetto completo di tutti gli sviluppi e modifiche discusse sino ad oggi</p>
<p><h2>Database ed UTF-8</h2>
</p>
<p>UTF-8 è una codifica Unicode multi-byte ASCII-compatibile, ed è la codifica che utilizzeremo come standard per il nostro progetto. Per creare un database MySql che utilizzi UTF-8 dovremo specificare il set di caratteri MySQL utf8_unicode_ci come collation all&#8217;atto della creazione del database.</p>
<pre name="code" class="php">
CREATE DATABASE `miodb` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
</pre>
</p>
<p>Lo stesso genere di collation dovrà essere specificato per i campi di testo contenuti all&#8217;interno delle tabelle del database, in ogni caso è una cosa che avviene automaticamente utilizzando PhpMyAdmin.</p>
<h2>Salvataggio dei dati</h2>
<p>All&#8217;atto dell&#8217;inserimento e modifica dei dati, dovremmo fare molta attenzione che questi siano encodati prima di essere inseriti. In una normale applicazione potremmo ricorrere all&#8217;utilizzo delle funzioni <a href="http://it.php.net/manual/it/function.utf8-encode.php">utf8_encode</a> ed <a href="http://it.php.net/manual/it/function.utf8-decode.php">utf8_decode</a>, rispettivamente per la codifica ( prima del salvataggio ) e la decodifica ( dopo essere state recuperati dal db ), in Zend Framework la codifica UTF-8 viene gestita internamente, quindi non dovremo utilizzare queste funzioni per inserire dati nel database. Tuttavia le utilizzeremo per decodificare i record estratti dal database prima dell&#8217;inserimento nell&#8217;indice di Lucene.</p>
<p><h2>Il codice HTML</h2>
</p>
<p>Infine dovremmo specificare nel tag HEAD il corretto META per il charset</p>
<pre name="code" class="php">
&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
</pre>
<h2>Modifiche al codice di Lucene</h2>
<p>Qui di seguito vedremo passo passo le modifiche effettuate al codice in qualche modo relazionato con la gestione di Zend_Search_Lucene. Tutte queste modifiche sono incluse nel file in download, quindi non inserirò il file nella sua completezza.</p>
<p>Per prima cosa modifichiamo il metodo getSearchIndexFields nella classe Paese, la classe row level di Paesi.</p>
<pre name="code" class="php">
    public function getSearchIndexFields(){
      $user = $this->findParentRow('Utenti');
      $reviews = $this->findRecensioni();
      $result = array();
      $result['class'] = 'Paese';
      $result['key'] = $this->id;
      $result['title'] = $this->nome;
      $result['contents'] = $this->provincia;
      $result['summary'] = "";
      if(count($reviews) > 0){
        foreach($reviews as $review) {
          $result['contents'] .= "\n".utf8_decode($review->descrizione);
          $result['summary'] .= "\n".mb_substr($review->descrizione,0,40);
        }
      }
      $result['createdBy'] = $user->name;
      $result['dateCreated'] = $this->inserimento_data;
      return $result;
    }
</pre>
<p>Per fare in modo di indicizzare correttamente il contenuto del database, codificato in UTF-8, dobbiamo prima decodificarlo. Il campo testuale che subisce la decodifica è il campo contenente il corpo di una probabile recensione. Da notare che la chiave &#8217;summary&#8217; contiene un&#8217;anteprima di quello che contiene la recensione, ovvero i primi 40 caratteri. Siccome si tratta di un campo multibyte, utilizziamo la funzione <a href="http://it.php.net/manual/it/function.mb-substr.php">mb_substr</a>. Notate che l&#8217;utilizzo delle funzioni multibyte richiede che sia installata la relativa estensione per php ( per chi usa WampServer in locale: PHP -> PHP Extension -> php_mbstring ).</p>
<p>Infine aggiungiamo 2 righe di codice al bootstrap index.php:</p>
<pre name="code" class="php">
  Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('UTF-8');
  Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8());
</pre>
<p>Con questo codice diciamo a Lucene di interpretare le eventuali stringhe di ricerca come stringhe UTF-8 e di utilizzare il medesimo encoding per la gestione dei documenti nel suo indice, per maggiori informazioni potete consultare la <a href="http://framework.zend.com/manual/en/zend.search.lucene.charset.html#zend.search.lucene.charset.description">guida ufficiale</a>.</p>
<h2>Conclusione</h2>
<p>Ho apportato diverse altre modifiche al codice, ad esempio allo stile e ad alcune views. Inoltre ho aggiunto il supporto per la gestione degli errori 404 ( file non trovato ) e degli errori dell&#8217;applicazione, che vedremo in un prossimo articolo.</p>
<p>Come annunciato, potete tra poco scaricare il progetto comprensivo di database con alcuni dati di esempio in 4 lingue diverse, italiano, giapponese, russo e hindi ( o almento credo.. )</p>
<p>Non sono presenti link che rimandano ai controllers presenti ma se avete letto gli articoli passati dovreste sapere quali sono&#8230; in ogni caso i controller attualmente presenti sono:</p>
<ul class="listato">
<li>index ( ovviamente )</li>
<li>search</li>
<li>supporto</li>
<li>paese</li>
<li>auth</li>
<li>ajax</li>
</ul>
<p>Avete inoltre bisogno delle seguenti informazioni:</p>
<ul class="listato">
<li>username e password per accedere sono Admin &#8211; admin</li>
<li>il controller supporto invia email ( anche da locale ) se inserite le credenziali di un account email compatibile</li>
<li>prima di effettuare ricerche dovete richiamare la reindex action del search controller</li>
</ul>
<h2><a href="http://razorblade.netsons.org/files/paesidelmondo.rar">DOWNLOAD</a></h2>
<div class="ratings">Note: There is a rating embedded within this post, please visit this post to rate it.</div>


<p>Related posts:<ol><li><a href='http://razorblade.netsons.org/2009/04/24/luke-lucene-index-toolbox-di-andrzej-bialecki-per-zend_search_lucene/' rel='bookmark' title='Permanent Link: Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene'>Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene</a> <small>Verificare il contenuto dell'indice di Lucene Se avete seguito questo...</small></li><li><a href='http://razorblade.netsons.org/2009/02/02/zend-framework-gestione-dei-moduli-ed-esempio-modulo-di-amministrazione/' rel='bookmark' title='Permanent Link: Zend Framework: gestione dei moduli ed esempio modulo di amministrazione'>Zend Framework: gestione dei moduli ed esempio modulo di amministrazione</a> <small>Come usare Zend_Layout per la gestione dei moduli In quest'articolo...</small></li><li><a href='http://razorblade.netsons.org/2008/12/05/zend-framework-zend_form-con-recaptcha/' rel='bookmark' title='Permanent Link: Zend Framework: Zend_Form con ReCaptcha'>Zend Framework: Zend_Form con ReCaptcha</a> <small>Utilizzare il webservice ReCaptcha per validare i form ReCaptcha è...</small></li></ol></p>
<p>Related posts brought to you by <a href='http://mitcho.com/code/yarpp/'>Yet Another Related Posts Plugin</a>.</p>]]></content:encoded>
			<wfw:commentRss>http://razorblade.netsons.org/2008/10/27/zend-search-lucene-e-utf-8-unicode-con-zend-framework/feed/</wfw:commentRss>
		<slash:comments>52</slash:comments>
		</item>
		<item>
		<title>Zend Search Lucene ( applicazione reale Zend Framework p.10 )</title>
		<link>http://razorblade.netsons.org/2008/09/30/zend-search-lucene-applicazione-reale-zend-framework-p10/</link>
		<comments>http://razorblade.netsons.org/2008/09/30/zend-search-lucene-applicazione-reale-zend-framework-p10/#comments</comments>
		<pubDate>Mon, 29 Sep 2008 23:07:28 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Php]]></category>
		<category><![CDATA[Zend Framework]]></category>
		<category><![CDATA[Apache Lucene project]]></category>
		<category><![CDATA[applicazioni con zend framework]]></category>
		<category><![CDATA[full text]]></category>
		<category><![CDATA[Observer Pattern]]></category>
		<category><![CDATA[zend framework]]></category>
		<category><![CDATA[Zend_Search_Lucene]]></category>
		<category><![CDATA[Zend_Search_Lucene_Document]]></category>
		<category><![CDATA[Zend_Search_Lucene_Field]]></category>
		<category><![CDATA[Zend_Search_Lucene_Search_QueryHit]]></category>
		<category><![CDATA[ZF]]></category>

		<guid isPermaLink="false">http://razorblade.netsons.org/?p=35</guid>
		<description><![CDATA[<h2>Il componente Zend_Search_Lucene</h2>
<p>La possibilità di poter effettuare una ricerca che restituisca quello che stavamo cercando con il minimo sforzo è una caratteristica essenziale per un sito che si rispetti. Zend Framework ci viene incontro con il componente <b>Zend_Search_Lucene</b>, un motore di ricerca di tipo <b>full text</b> basato sull' <a href="http://lucene.apache.org/java/docs/" title="Apache Lucene project">Apache Lucene project</a>, un motore di ricerca per Java.</p>

<p>Zend_Search_Lucene crea un <b>indice</b> di documenti. Questo indice viene salvato direttamente nel filesystem, in una directory a nostra scelta, senza la necessità di avere <a href="http://razorblade.netsons.org/2008/09/30/zend-search-lucene-applicazione-reale-zend-framework-p10/">[...] Continua</a></p>


Related posts:<ol><li><a href='http://razorblade.netsons.org/2009/04/24/luke-lucene-index-toolbox-di-andrzej-bialecki-per-zend_search_lucene/' rel='bookmark' title='Permanent Link: Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene'>Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene</a> <small>Verificare il contenuto dell'indice di Lucene Se avete seguito questo...</small></li><li><a href='http://razorblade.netsons.org/2009/08/28/neobazaar-annunci-gratuiti-esempio-di-una-applicazione-sviluppata-con-zend-framework/' rel='bookmark' title='Permanent Link: Neobazaar annunci gratuiti: esempio di una applicazione sviluppata con Zend Framework'>Neobazaar annunci gratuiti: esempio di una applicazione sviluppata con Zend Framework</a> <small>Nasce Neobazaar.com, annunci gratuiti in Italia: interamente sviluppato con Zend...</small></li><li><a href='http://razorblade.netsons.org/2009/02/02/zend-framework-gestione-dei-moduli-ed-esempio-modulo-di-amministrazione/' rel='bookmark' title='Permanent Link: Zend Framework: gestione dei moduli ed esempio modulo di amministrazione'>Zend Framework: gestione dei moduli ed esempio modulo di amministrazione</a> <small>Come usare Zend_Layout per la gestione dei moduli In quest'articolo...</small></li></ol>

Related posts brought to you by <a href='http://mitcho.com/code/yarpp/'>Yet Another Related Posts Plugin</a>.]]></description>
			<content:encoded><![CDATA[<h2>Il componente Zend_Search_Lucene</h2>
<p>La possibilità di poter effettuare una ricerca che restituisca quello che stavamo cercando con il minimo sforzo è una caratteristica essenziale per un sito che si rispetti. Zend Framework ci viene incontro con il componente <b>Zend_Search_Lucene</b>, un motore di ricerca di tipo <b>full text</b> basato sull&#8217; <a href="http://lucene.apache.org/java/docs/" title="Apache Lucene project">Apache Lucene project</a>, un motore di ricerca per Java.</p>
<p>Zend_Search_Lucene crea un <b>indice</b> di documenti. Questo indice viene salvato direttamente nel filesystem, in una directory a nostra scelta, senza la necessità di avere a disposizione un database server. Tra le caratteristiche possiamo elencare:</p>
<ul class='listato'>
<li>Ricerca per indice di coerenza: i risultati di maggior rilievo vengono restituiti in cima alla lista</li>
<li>Possibilità di utilizzo di numerosi metodi di ricerca: phrase queries, boolean queries, wildcard queries, proximity queries, range queries e molte altre.</li>
<li>Ricerca per un campo specifico (ad esempio titolo, autore, descrizione )</li>
</ul>
<h2>Tipi di campo</h2>
<table border="1" summary="Zend_Search_Lucene_Field Types">
<colgroup>
<col />
<col />
<col />
<col />
<col />
  </colgroup>
<thead>
<tr>
<th>Field Type</th>
<th>Stored</th>
<th>Indexed</th>
<th>Tokenized</th>
<th>Binary</th>
</tr>
</thead>
<tbody>
<tr>
<td>Keyword</td>
<td>Yes</td>
<td>Yes</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>UnIndexed</td>
<td>Yes</td>
<td>No</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>Binary</td>
<td>Yes</td>
<td>No</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Text</td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td>UnStored</td>
<td>No</td>
<td>Yes</td>
<td>Yes</td>
<td>No</td>
</tr>
</tbody>
</table>
<p>Questa tabella ci mostra i tipi di campo che possiamo inserire nell&#8217;indice di Zend_Search_Lucene e come questi vengono trattati. Vediamoli più nel dettaglio:</p>
<ul class='listato'>
<li><b>Keyword</b> I campi indicati come keyword sono memorizzati e indicizzati, il che significa che possono essere ricercati e visualizzati tra i risultati di ricerca. Le parole dei campi keyword devono essere separate. Dei buoni candidati per questo tipo di campo sono ad esempio i campi enumerated.</li>
<li><b>UnIndexed</b> I campi UnIndexed non sono ricercabili ma vengono ritornati tra i risultati di ricerca. Buoni candidati ad appartenere a questa tipologia di campo sono, ad esempio, i campi chiave primaria, i timestamps, percorsi di filesystem.</li>
<li><b>Binary</b> I campi Binary non sono indicizzati ma possono essere ritrovati tra i risultati di ricerca. Vi appartengono quei dati salvati come stringa binaria, ad esempio una icona o una thumbnail.</li>
<li><b>Text</b> I campi Text sono memorizzati ed indicizzati, quindi saranno utili ai fini della ricerca ed inoltre saranno restituiti tra i risultati. Ne fanno parte, per eempio, quei campi che contengono titoli, descrizioni ecc, quindi varchar, text ecc.</li>
<li><b>UnStored</b> I campi di tipo UnStored sono indicizzati ma non sono memorizzati nell&#8217;indice. Questo genere di campo è da utilizzarsi per testi molto lunghi, in quanto, pur essendo ricercabili, non occupano spazio su disco. Nel caso si utilizzi un database, i dati per la visualizzazione potranno essere recuperati da questo.</li>
</ul>
<h2>Come Funziona</h2>
<p>Il funzionamento di Zend_Search_Lucene e del suo indice è rappresentato dal seguente schema</p>
<p>Ogni documento inserito nell&#8217;indice è una istanza di <b>Zend_Search_Lucene_Document</b>, ed ogni documento contiene degli oggetti di tipo <b>Zend_Search_Lucene_Field</b>, che contengono i dati. Dopo aver effettuato la query il risultato ritornato è un array contenente dei risultati, ognuno di questi un oggetto di tipo <b>Zend_Search_Lucene_Search_QueryHit</b>.</p>
<h2>Unificare i documenti</h2>
<p>L&#8217;indice può contenere dei documenti di tipi differenti, come pagine, recensioni, profili utente ecc. Per fare in modo che i risultati di ricerca siano comprensibili per l&#8217;utente dobbiamo unificarli, ovvero scegliere quali campi comuni inserire nel documento dell&#8217;indice. Il seguente  un esempio di unificazione dei documenti da aggiungere all&#8217;indice:</p>
<table border=1>
<colgroup>
<col />
<col />
<col />
  </colgroup>
<thead>
<tr>
<th>Field Name</th>
<th>Type</th>
<th>Notes</th>
</tr>
</thead>
<tr>
<td>class</td>
<td>UnIndexed</td>
<td>La classe a cui appartengono i dati memorizzati. Abbiamo necessità di conoscerla in quanto è necessaria per creare la corretta url per il documento.</td>
</tr>
<tr>
<td>key</td>
<td>UnIndexed</td>
<td>La chiave per i dati memorizzati. Solitamente è l&#8217;id del record. Abbiamo necessità di conoscerla in quanto è necessaria per creare la corretta url per il documento.</td>
</tr>
<tr>
<td>docRef</td>
<td>Keyword</td>
<td>Un identificatore univoco per il record. Servirà al fine di modifica ed eliminazione</td>
</tr>
<tr>
<td>title</td>
<td>Text</td>
<td>Il titolo del record utile ai fini di ricerca e di visualizzazione</td>
</tr>
<tr>
<td>contents</td>
<td>UnStored</td>
<td>Il contenuto principale del record. Ricercabile ma come detto in precedenza non memorizzato in quanto questo potrebbe essere un gran quantitativo di testo che appesantirebbe troppo l&#8217;indice.</td>
</tr>
<tr>
<td>summary</td>
<td>UnIndexed</td>
<td>Contiene informazioni per la visualizzazione. Non è utile al fine della ricerca.</td>
</tr>
<tr>
<td>createdBy</td>
<td>Text</td>
<td>Creatore del documento. Memorizzato e ricercabile.</td>
</tr>
<tr>
<td>dateCreated</td>
<td>Keyword</td>
<td>Data di creazione. Non necessita di essere parsato da Lucene ( Tokenized NO ) ma è memorizzato ed utile al fine della ricerca.</td>
</tr>
</table>
<h2>Creazione di un documento</h2>
<p>Come detto in precedenza, ogni documento è una istanza della classe Zend_Search_Lucene_Document. Siccome siamo giunti al punto di avere il design del documento ben definito ( la tabella di qui sopra ), possiamo procedere con il creare una classe personalizzata che estenda Zend_Search_Lucene_Document che ci semplifichi la creazione di un documento. La classe in questione si chiamerà Paesidelmondo_Search_Lucene_Document</p>
<p><b>Paesidelmondo_Search_Lucene_Document</b></p>
<pre name="code" class="php">
  class Paesidelmondo_Search_Lucene_Document extends Zend_Search_Lucene_Document {
    public function __construct($class, $key, $title,  $contents, $summary, $createdBy, $dateCreated) {
      $this->addField(Zend_Search_Lucene_Field::Keyword( 'docRef', $class.":".$key));
      $this->addField(Zend_Search_Lucene_Field::UnIndexed( 'class', $class));
      $this->addField(Zend_Search_Lucene_Field::UnIndexed( 'key', $key));
      $this->addField(Zend_Search_Lucene_Field::Text( 'title', $title));
      $this->addField(Zend_Search_Lucene_Field::UnStored( 'contents', $contents));
      $this->addField(Zend_Search_Lucene_Field::UnIndexed( 'summary', $summary));
      $this->addField(Zend_Search_Lucene_Field::Keyword( 'createdBy', $createdBy));
      $this->addField(Zend_Search_Lucene_Field::Keyword( 'dateCreated', $dateCreated));
    }
  }
</pre>
<p>La classe è molto semplice e contiene solo il costruttore a cui passeremo il valore dei campi del documento.</p>
<p>Il problema successivo è creare i documenti ed aggiungerli all&#8217;indice. Il seguente esempio, mostra come fare:</p>
<pre name="code" class="php">
$index = Zend_Search_Lucene::open($path);
$doc = new Paesidelmondo_Search_Lucene_Document(
  $class, $key,
  $title, $contents,
  $summary, $createdBy, $dateCreated
);
$index->addDocument($doc);
</pre>
<p>Quello che adesso bisogna fare è ripetere questo codice per ogni classe modello a cui la funzionalità di ricerca debba essere applicata. Ogni modelo chiaramente conosce tutto a proposito dei dati da ricercare, quello che non sa è come aggiungere questi dati all&#8217;indice del motore di ricerca, in quanto, se lo sapesse, sarebbe troppo strettamente legato ad esso e renderebbe difficile un eventuale sostituzione del motore di ricerca con un altro. La soluzione di questo problema è l&#8217;utilizzo di un design pattern chiamato <b>Observer Pattern</b></p>
<h2>Observer Pattern</h2>
<p>L&#8217;Observer Pattern utilizza il concetto di notifica. Un oggetto detto <b>observer</b> registra interesse verso un altro oggetto, detto <b>observable</b> e quando qualcosa succede ad <b>observable</b>, l&#8217;oggetto <b>observer</b> prende le azioni appropriate. Le nostre classi observable sono i nostri modelli ed i dati a cui siamo interessati sono istanze di Zend_Db_Table_Row_Abstract, quindi la estendiamo creando Paesidelmondo_Db_Table_Row_Observerable.</p>
<pre name="code" class="php">
  class Paesidelmondo_Db_Table_Row_Observerable extends Zend_Db_Table_Row_Abstract {
    protected static $_observers = array();

    public static function attachObserver($class){
      if (!is_string($class) || !class_exists($class) || !is_callable(array($class , 'observeTableRow'))) {
        return false;
      }
      if (!isset(self::$_observers[$class])) {
        self::$_observers[$class] = true;
      }
      return true;
    }

    public static function detachObserver($class){
      if (!isset(self::$_observers[$class])) {
        return false;
      }
      unset(self::$_observers[$class]);
      return true;
    }

    protected function notifyObservers($event){
      if (!empty(self::$_observers)) {
        foreach (array_keys(self::$_observers) as $observer) {
          call_user_func(array($observer , 'observeTableRow'), $event, $this);
        }
      }
    }

    protected function _insert(){
      self::notifyObservers('pre-insert');
    }

    protected function _postInsert(){
      self::notifyObservers('post-insert');
    }

    protected function _update(){
      self::notifyObservers('pre-update');
    }

    protected function _postUpdate(){
      self::notifyObservers('post-update');
    }

    protected function _delete(){
      self::notifyObservers('pre-delete');
    }

    protected function _postDelete(){
      self::notifyObservers('post-delete');
    }
  }
</pre>
<p>Questa classe contiene la proprietà statica $_observers in quanto la lista degli observers è indipendente dal modello, quindi per qualsiasi modello deve poter essere recuperata la stassa lista di observers. Il metodo statico attachObserver è quello che registra un oggetto observer e lo inserisce all&#8217;interno dell&#8217;array statico solo dopo aver effettuato alcuni controlli. La registrazione dell&#8217;oggetto observer dev&#8217;essere fatta nel bootstrap, vedremo tra non molto l&#8217;oggetto observer ed il codice da inserire nel bootstrap.</p>
<p>Il metodo detachObserver serve a rimuovere un observer precedentemente registrato e quindi giungiamo al metodo notifyObservers che richiama il metodo observeTableRow per ogni observer presente nell&#8217;array degli observer registrati, passandogli inoltre, il nome dell&#8217;evento e l&#8217;istanza del modello che ha scatenato l&#8217;evento. </p>
<h2>L&#8217;oggetto Observer</h2>
</p>
<p>L&#8217;oggetto che utilizzeremo come observer si chiamerà <b>SearchIndexer</b> ed in quanto è un modelo sarà salvato in application/models</p>
<p><b>application/models/</b><b>SearchIndexer</b></p>
<pre name="code" class="php">
  class SearchIndexer { 

    protected static $_indexDirectory;

    public static function setIndexDirectory($directory){
    	if(!is_dir($directory)) {
    		throw new Exception('Directory for SearchIndexer is invalid ('. $directory .')');
    	}
    	self::$_indexDirectory = $directory;
    }

    public static function getIndexDirectory(){
    	return self::$_indexDirectory;
    }

    public static function observeTableRow($event, $row){
      switch($event){
        case 'post-insert':
        case 'post-update':
          $doc = self::getDocument($row);
          if ($doc !== false){
            self::_addToIndex($doc);
          }
        break;
      }
    }

    public static function getDocument($row) {
      if(method_exists($row, 'getSearchIndexFields')){
        $fields = $row->getSearchIndexFields($row);
        $doc = new Paesidelmondo_Search_Lucene_Document(
          $fields['class'], $fields['key'],
          $fields['title'], $fields['contents'],
          $fields['summary'], $fields['createdBy'],
          $fields['dateCreated']
        );
        return $doc;
      }
      return false;
    }

    protected static function _addToIndex($doc){
      try {
        $index = Paesidelmondo_Search_Lucene::open(self::$_indexDirectory);
      } catch (Exception $e) {
        $index = Paesidelmondo_Search_Lucene::create(self::$_indexDirectory);
      }
      $index->addDocument($doc);
      $index->commit();
    }
  }
</pre>
<p>L&#8217;oggetto SearchIndexer è l&#8217;oggetto <b>observer</b> dell&#8217;oggetto Paesidelmondo_Db_Table_Row_Observerable, che quindi è l&#8217;oggetto <b>observable</b>. I modelli row level per cui vogliamo implementare la funzionalità di ricerca estendono Paesidelmondo_Db_Table_Row_Observerable, quindi ognuno di essi è un oggetto <b>observable</b>.</p>
<p>SearchIndexer contiene al suo interno i seguenti etodi statici, vediamoli nel dettaglio: </p>
<ul class='listato'>
<li><b>setIndexDirectory</b> definisce dove nel filesystem sarà memorizzato l&#8217;indice. Da utilizzarsi nel bootstrap.</li>
<li><b>observeTableRow</b> ogni volta che un oggetto observable viene modificato o creato, observeTableRow viene avvisato, rendendo disponibili il tipo di evento scatenato e l&#8217;oggetto observable. Quindi, in base all&#8217;evento, possono essere effettuate delle modifiche all&#8217;indice, come per esempio l&#8217;aggiunta all&#8217;indice.</li>
<li><b>getDocument</b> questo metodo controlla se l&#8217;oggetto $row ( oggetto observable ) possiede un metodo getSearchIndexField. In caso positivo un documento valido per l&#8217;aggiunta all&#8217;indice viene ritornato. </li>
<li><b>_addToIndex</b> questo metodo si occupa di aggiungere il documento al&#8217;indice</li>
</ul>
<p>Ora non ci resta che vedere un esempio di un modello observable che estenda Paesidelmondo_Db_Table_Row_Observerable, a questo scopo creeremo la classe Paese, il modello row level dell&#8217;oggetto Paesi.</p>
<p>application/models/<b>Paese</b></p>
<pre name="code" class="php">
  class Paese extends Paesidelmondo_Db_Table_Row_Observerable
  {
    public function getSearchIndexFields(){
      $user = $this->findParentRow('Utenti');
      $reviews = $this->findRecensioni();
      $result = array();
      $result['class'] = 'Paese';
      $result['key'] = $this->id;
      $result['title'] = $this->nome;
      $result['contents'] = $this->provincia;
      foreach ($reviews as $review) {
        $result['contents'] .= "\n".$review->descrizione;
      }
      $result['summary'] = substr($review->descrizione,0,40);
      $result['createdBy'] = $user->name;
      $result['dateCreated'] = $this->inserimento_data;
      return $result;
    }

    protected function _insert(){
      if(is_null($this->created_by)) {
        $user = Zend_Auth::getInstance()->getIdentity();
        $this->created_by = (int)$user->id;
      }
      if(is_null($this->date_created)) {
        $this->date_created = date('Y-m-d H:i:s');
      }
      if(is_null($this->date_updated)) {
        $this->date_updated = date('Y-m-d H:i:s');
      }
      parent::_insert();
    }

    protected function _update(){
      $this->date_updated = date('Y-m-d H:i:s');
      parent::_update();
    }
  }
</pre>
<p>Il metodo getSearchIndexFields() ritorna un array contenente i dati utili per la creazione di una istanza di Zend_Search_Lucene_Document. Ogni modello contenente dati indicizzabili dovrebbe contenere questo metodo, in quanto è quello che si occupa di unificare i campi dei dati con i campi del design dell&#8217;indice. Questo metodo è quello che viene richiamato dal metodo SearchIndexer::getDocument ogni volta che un oggetto observable avverte l&#8217;observe che una modifica è avvenuta. Ad ogni chiamata dei metodi _insert e _update, l&#8217;omonimo metodo della classe parente ( Paesidelmondo_Db_Table_Row_Observerable ) viene chiamato, e questo si occuperà di inviare notifica all&#8217;observer indicado l&#8217;evento scatenante.</p>
<h2>Il Bootstrap</h2>
<p>Nel nostro file di bootstrap ( index.php nella root ), dovremo aggiungere le seguenti linee di codice:</p>
<pre name="code" class="php">
  SearchIndexer::setIndexDirectory(ZEND_ROOT.'/var/search_index');
  Paesidelmondo_Db_Table_Row_Observerable::attachObserver('SearchIndexer');
</pre>
<p>Queste definiscono dove nel filesystem salvare l&#8217;indice e registrano l&#8217;oggetto SearchIndexer come oggetto observer per le istanze di Paesidelmondo_Db_Table_Row_Observerable, i modelli observable.</p>
<p>Infine, avrete sicuramente notato che sia per l&#8217;evento di inserimento che per l&#8217;evento di modifica SearchIndexer::observeTableRow aggiunge l&#8217;oggetto nell&#8217;indice, ma non lo modifica mai. Questo comportamento perchè Zend_Search_Lucene lo estenderemo in modo tale che il metodo addDocument, prima di effettuare un inserimento di un documento nell&#8217;indice controlli la sua esistenza, ed in caso, lo elimini prima di reinserirlo.</p>
<pre name="code" class="php">
  class Paesidelmondo_Search_Lucene extends Zend_Search_Lucene { 

    public static function create($directory){
      return new Zend_Search_Lucene_Proxy(new Paesidelmondo_Search_Lucene($directory, true));
    }

    public static function open($directory){
      return new Zend_Search_Lucene_Proxy(new Paesidelmondo_Search_Lucene($directory, false));
    }

    public function addDocument(Zend_Search_Lucene_Document $document){
      // check document doesn't already exist - docRef should be unique
      $docRef = $document->docRef;
      $term = new Zend_Search_Lucene_Index_Term($docRef, 'docRef');
      $query = new Zend_Search_Lucene_Search_Query_Term($term);
      $results = $this->find($query);
      if(count($results) > 0) {
        foreach($results as $result){
          $this->delete($result->id);
        }
      }
      return parent::addDocument($document);
    }
  }
</pre>
<p>Nel caso non lo abbiate notato, sarà chiamato Paesidelmondo_Search_Lucene piuttosto che Zend_Search_Lucene in quanto in SearchIndexer::_addToIndex viene richiamata la prima e non la seconda, quindi la nuova funzionalità di addDocument è già presente nel codice postato senza dover effettuare altre modifiche.</p>
<h2>Il controller searchController</h2>
<p>Ogni volta che si effettuerà una ricerca, il risultato di questa sarà visualizzato dall&#8217;index action del searchController. Inoltre il searchController conterrà la reindexAction, action con la quale si potranno reindicizzare tutti i documenti indicizzabili.</p>
<p>application/controllers/<b>SearchController.php</b></p>
<pre name="code" class="php">
  class SearchController extends Zend_Controller_Action {
    public function init(){
      $response = $this->getResponse();
      $response->insert('header', $this->view->render('header.phtml'));
      $response->insert('menu', $this->view->render('menu.phtml'));
      $response->insert('columnLeft', $this->view->render('columnLeft.phtml'));
      $response->insert('columnRight', $this->view->render('columnRight.phtml'));
      $response->insert('footer', $this->view->render('footer.phtml')); 

      // [ eventuale controllo di verifica ACL ]
    }

    public function indexAction() {
      $this->view->title = 'Search Results';
      $filters = array('q' => array('StringTrim' , 'StripTags'));
      $validators = array('q' => array('presence' => 'required'));
      $input = new Zend_Filter_Input($filters, $validators, $_GET);
      if ($input->isValid()) {
        $this->view->messages = '';
        $q = $input->getEscaped('q');
        $this->view->q = $q;
        try {
          $index = Paesidelmondo_Search_Lucene::open(SearchIndexer::getIndexDirectory());
          $results = $index->find($q);
        } catch (Exception $e) {
          $results = array();
        }
        $this->view->results = $results;
      } else {
        $this->view->messages = $input->getMessages();
      }
    }

    public function reindexAction(){
    	$this->view->title = 'Search Reindex';
    	$messages = array();
    	$index = Paesidelmondo_Search_Lucene::create(SearchIndexer::getIndexDirectory());
    	$paesidelmondo = new Paesi();
    	$paesi = $paesidelmondo->fetchAll();
    	foreach($paesi as $paese) {
    		$doc = SearchIndexer::getDocument($paese);
    		$index->addDocument($doc);
    		$messages[] = 'Added Place: ' . $doc->title . ' - docRef: ' . $doc->docRef;
    	}
    	$messages[] = '';
    	$index->commit();
    	$messages[] = 'Total documents in index: ' . $index->numDocs();
    	$this->view->messages = $messages;
    }
  }
</pre>
<p>Le seguenti sono le view rispettivamente per la index e la reindex action</p>
<p><b>index.phtml</b></p>
<pre name="code" class="php">
  &lt;h1&gt;&lt;?php echo $this-&gt;escape($this-&gt;title);?&gt;&lt;/h1&gt;

  &lt;?php if ($this-&gt;messages) : ?&gt;
    &lt;div id="error"&gt;
      There was a problem:
      &lt;ul&gt;
        &lt;?php foreach ($this-&gt;messages['q'] as $message) : ?&gt;
        &lt;li&gt;&lt;?php echo $message;?&gt;&lt;/li&gt;
        &lt;?php endforeach; ?&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;?php else <img src='http://razorblade.netsons.org/wp-includes/images/smilies/icon_confused.gif' alt=':?' class='wp-smiley' /> &gt;
    &lt;p&gt;You searched for &lt;?php echo $this-&gt;escape($this-&gt;q); ?&gt;.
    &lt;?php echo count($this-&gt;results);?&gt; results found.
    &lt;/p&gt;

    &lt;ul&gt;
      &lt;?php foreach ($this-&gt;results as $result) : ?&gt;
      &lt;li&gt;&lt;a href="&lt;?php echo $this-&gt;getSearchResultUrl($result-&gt;class, $result-&gt;key); ?&gt;"&gt;
        &lt;?php echo $this-&gt;escape($result-&gt;title);?&gt;&lt;/a&gt;
        &lt;div class="summary"&gt;
        &lt;?php echo $this-&gt;escape($result-&gt;summary);?&gt;
        (&lt;?php echo $result-&gt;score;?&gt;) &lt;/div&gt;
      &lt;/li&gt;
      &lt;?php endforeach; ?&gt;
    &lt;/ul&gt;

  &lt;?php endif; ?&gt;
</pre>
<p><b>reindex.phtml</b></p>
<pre name="code" class="php">
&lt;h1&gt;&lt;?php echo $this-&gt;escape($this-&gt;title);?&gt;&lt;/h1&gt;

&lt;?php foreach ($this-&gt;messages as $message) : ?&gt;
&lt;?php echo $this-&gt;escape($message); ?&gt;&lt;br /&gt;
&lt;?php endforeach; ?&gt;
</pre>
<p>Per concludere ecco il codice del form da inserire dove volete, per esempio nell&#8217;header.php, in modo tale che sia sempre visibile in ogni pagina</p>
<pre name="code" class="php">
&lt;div style="float: right;"&gt;
&lt;h3&gt;Search:&lt;/h3&gt;
&lt;form method="get" action="&lt;?php echo $this-&gt;baseUrl;?&gt;/search"&gt;
 &lt;fieldset&gt;
  &lt;legend&gt;Ricerca&lt;/legend&gt;
  &lt;label for="text"&gt;Nome Utente:&lt;/label&gt;&lt;input type="text" name="q" value=""&gt;
  &lt;input type="submit" name="search" value="Go"&gt;
 &lt;/fieldset&gt;
&lt;/form&gt;
&lt;/div&gt;
</pre>
<p>Si conclude qui la guida al componente Zend_Search_Lucene, ma come spesso accade, non tutto è stato detto! La parte sui possibili identificatori utilizzabili nelle stringhe query e su come effettuare i vari tipi di queries ( Multi-Term Query, Wildcard ecc ) è stata volutamente tralasciata e sarà ripresa in futuro.</p>
<div class="ratings">Note: There is a rating embedded within this post, please visit this post to rate it.</div>


<p>Related posts:<ol><li><a href='http://razorblade.netsons.org/2009/04/24/luke-lucene-index-toolbox-di-andrzej-bialecki-per-zend_search_lucene/' rel='bookmark' title='Permanent Link: Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene'>Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene</a> <small>Verificare il contenuto dell'indice di Lucene Se avete seguito questo...</small></li><li><a href='http://razorblade.netsons.org/2009/08/28/neobazaar-annunci-gratuiti-esempio-di-una-applicazione-sviluppata-con-zend-framework/' rel='bookmark' title='Permanent Link: Neobazaar annunci gratuiti: esempio di una applicazione sviluppata con Zend Framework'>Neobazaar annunci gratuiti: esempio di una applicazione sviluppata con Zend Framework</a> <small>Nasce Neobazaar.com, annunci gratuiti in Italia: interamente sviluppato con Zend...</small></li><li><a href='http://razorblade.netsons.org/2009/02/02/zend-framework-gestione-dei-moduli-ed-esempio-modulo-di-amministrazione/' rel='bookmark' title='Permanent Link: Zend Framework: gestione dei moduli ed esempio modulo di amministrazione'>Zend Framework: gestione dei moduli ed esempio modulo di amministrazione</a> <small>Come usare Zend_Layout per la gestione dei moduli In quest'articolo...</small></li></ol></p>
<p>Related posts brought to you by <a href='http://mitcho.com/code/yarpp/'>Yet Another Related Posts Plugin</a>.</p>]]></content:encoded>
			<wfw:commentRss>http://razorblade.netsons.org/2008/09/30/zend-search-lucene-applicazione-reale-zend-framework-p10/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
	</channel>
</rss>
