- Tue
- 30
- Sep
- 08
Zend Search Lucene ( applicazione reale Zend Framework p.10 )
Di in Php, Zend Framework Controls: +-close
Il componente Zend_Search_Lucene
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 Zend_Search_Lucene, un motore di ricerca di tipo full text basato sull’ Apache Lucene project, un motore di ricerca per Java.
Zend_Search_Lucene crea un indice 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:
- Ricerca per indice di coerenza: i risultati di maggior rilievo vengono restituiti in cima alla lista
- Possibilità di utilizzo di numerosi metodi di ricerca: phrase queries, boolean queries, wildcard queries, proximity queries, range queries e molte altre.
- Ricerca per un campo specifico (ad esempio titolo, autore, descrizione )
Tipi di campo
| Field Type | Stored | Indexed | Tokenized | Binary |
|---|---|---|---|---|
| Keyword | Yes | Yes | No | No |
| UnIndexed | Yes | No | No | No |
| Binary | Yes | No | No | Yes |
| Text | Yes | Yes | Yes | No |
| UnStored | No | Yes | Yes | No |
Questa tabella ci mostra i tipi di campo che possiamo inserire nell’indice di Zend_Search_Lucene e come questi vengono trattati. Vediamoli più nel dettaglio:
- Keyword 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.
- UnIndexed 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.
- Binary 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.
- Text 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.
- UnStored I campi di tipo UnStored sono indicizzati ma non sono memorizzati nell’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.
Come Funziona
Il funzionamento di Zend_Search_Lucene e del suo indice è rappresentato dal seguente schema
Ogni documento inserito nell’indice è una istanza di Zend_Search_Lucene_Document, ed ogni documento contiene degli oggetti di tipo Zend_Search_Lucene_Field, che contengono i dati. Dopo aver effettuato la query il risultato ritornato è un array contenente dei risultati, ognuno di questi un oggetto di tipo Zend_Search_Lucene_Search_QueryHit.
Unificare i documenti
L’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’utente dobbiamo unificarli, ovvero scegliere quali campi comuni inserire nel documento dell’indice. Il seguente un esempio di unificazione dei documenti da aggiungere all’indice:
| Field Name | Type | Notes |
|---|---|---|
| class | UnIndexed | La classe a cui appartengono i dati memorizzati. Abbiamo necessità di conoscerla in quanto è necessaria per creare la corretta url per il documento. |
| key | UnIndexed | La chiave per i dati memorizzati. Solitamente è l’id del record. Abbiamo necessità di conoscerla in quanto è necessaria per creare la corretta url per il documento. |
| docRef | Keyword | Un identificatore univoco per il record. Servirà al fine di modifica ed eliminazione |
| title | Text | Il titolo del record utile ai fini di ricerca e di visualizzazione |
| contents | UnStored | 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’indice. |
| summary | UnIndexed | Contiene informazioni per la visualizzazione. Non è utile al fine della ricerca. |
| createdBy | Text | Creatore del documento. Memorizzato e ricercabile. |
| dateCreated | Keyword | Data di creazione. Non necessita di essere parsato da Lucene ( Tokenized NO ) ma è memorizzato ed utile al fine della ricerca. |
Creazione di un documento
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
Paesidelmondo_Search_Lucene_Document
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));
}
}
La classe è molto semplice e contiene solo il costruttore a cui passeremo il valore dei campi del documento.
Il problema successivo è creare i documenti ed aggiungerli all’indice. Il seguente esempio, mostra come fare:
$index = Zend_Search_Lucene::open($path); $doc = new Paesidelmondo_Search_Lucene_Document( $class, $key, $title, $contents, $summary, $createdBy, $dateCreated ); $index->addDocument($doc);
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’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’utilizzo di un design pattern chiamato Observer Pattern
Observer Pattern
L’Observer Pattern utilizza il concetto di notifica. Un oggetto detto observer registra interesse verso un altro oggetto, detto observable e quando qualcosa succede ad observable, l’oggetto observer 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.
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');
}
}
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’interno dell’array statico solo dopo aver effettuato alcuni controlli. La registrazione dell’oggetto observer dev’essere fatta nel bootstrap, vedremo tra non molto l’oggetto observer ed il codice da inserire nel bootstrap.
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’array degli observer registrati, passandogli inoltre, il nome dell’evento e l’istanza del modello che ha scatenato l’evento.
L’oggetto Observer
L’oggetto che utilizzeremo come observer si chiamerà SearchIndexer ed in quanto è un modelo sarà salvato in application/models
application/models/SearchIndexer
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();
}
}
L’oggetto SearchIndexer è l’oggetto observer dell’oggetto Paesidelmondo_Db_Table_Row_Observerable, che quindi è l’oggetto observable. I modelli row level per cui vogliamo implementare la funzionalità di ricerca estendono Paesidelmondo_Db_Table_Row_Observerable, quindi ognuno di essi è un oggetto observable.
SearchIndexer contiene al suo interno i seguenti etodi statici, vediamoli nel dettaglio:
- setIndexDirectory definisce dove nel filesystem sarà memorizzato l’indice. Da utilizzarsi nel bootstrap.
- observeTableRow ogni volta che un oggetto observable viene modificato o creato, observeTableRow viene avvisato, rendendo disponibili il tipo di evento scatenato e l’oggetto observable. Quindi, in base all’evento, possono essere effettuate delle modifiche all’indice, come per esempio l’aggiunta all’indice.
- getDocument questo metodo controlla se l’oggetto $row ( oggetto observable ) possiede un metodo getSearchIndexField. In caso positivo un documento valido per l’aggiunta all’indice viene ritornato.
- _addToIndex questo metodo si occupa di aggiungere il documento al’indice
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’oggetto Paesi.
application/models/Paese
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();
}
}
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’indice. Questo metodo è quello che viene richiamato dal metodo SearchIndexer::getDocument ogni volta che un oggetto observable avverte l’observe che una modifica è avvenuta. Ad ogni chiamata dei metodi _insert e _update, l’omonimo metodo della classe parente ( Paesidelmondo_Db_Table_Row_Observerable ) viene chiamato, e questo si occuperà di inviare notifica all’observer indicado l’evento scatenante.
Il Bootstrap
Nel nostro file di bootstrap ( index.php nella root ), dovremo aggiungere le seguenti linee di codice:
SearchIndexer::setIndexDirectory(ZEND_ROOT.'/var/search_index');
Paesidelmondo_Db_Table_Row_Observerable::attachObserver('SearchIndexer');
Queste definiscono dove nel filesystem salvare l’indice e registrano l’oggetto SearchIndexer come oggetto observer per le istanze di Paesidelmondo_Db_Table_Row_Observerable, i modelli observable.
Infine, avrete sicuramente notato che sia per l’evento di inserimento che per l’evento di modifica SearchIndexer::observeTableRow aggiunge l’oggetto nell’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’indice controlli la sua esistenza, ed in caso, lo elimini prima di reinserirlo.
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);
}
}
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.
Il controller searchController
Ogni volta che si effettuerà una ricerca, il risultato di questa sarà visualizzato dall’index action del searchController. Inoltre il searchController conterrà la reindexAction, action con la quale si potranno reindicizzare tutti i documenti indicizzabili.
application/controllers/SearchController.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;
}
}
Le seguenti sono le view rispettivamente per la index e la reindex action
index.phtml
<h1><?php echo $this->escape($this->title);?></h1>
<?php if ($this->messages) : ?>
<div id="error">
There was a problem:
<ul>
<?php foreach ($this->messages['q'] as $message) : ?>
<li><?php echo $message;?></li>
<?php endforeach; ?>
</ul>
</div>
<?php else
>
<p>You searched for <?php echo $this->escape($this->q); ?>.
<?php echo count($this->results);?> results found.
</p>
<ul>
<?php foreach ($this->results as $result) : ?>
<li><a href="<?php echo $this->getSearchResultUrl($result->class, $result->key); ?>">
<?php echo $this->escape($result->title);?></a>
<div class="summary">
<?php echo $this->escape($result->summary);?>
(<?php echo $result->score;?>) </div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
reindex.phtml
<h1><?php echo $this->escape($this->title);?></h1> <?php foreach ($this->messages as $message) : ?> <?php echo $this->escape($message); ?><br /> <?php endforeach; ?>
Per concludere ecco il codice del form da inserire dove volete, per esempio nell’header.php, in modo tale che sia sempre visibile in ogni pagina
<div style="float: right;"> <h3>Search:</h3> <form method="get" action="<?php echo $this->baseUrl;?>/search"> <fieldset> <legend>Ricerca</legend> <label for="text">Nome Utente:</label><input type="text" name="q" value=""> <input type="submit" name="search" value="Go"> </fieldset> </form> </div>
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.
Related posts:
- Luke: Lucene Index Toolbox di Andrzej Bialecki per Zend_Search_Lucene Verificare il contenuto dell'indice di Lucene Se avete seguito questo...
- Neobazaar annunci gratuiti: esempio di una applicazione sviluppata con Zend Framework Nasce Neobazaar.com, annunci gratuiti in Italia: interamente sviluppato con Zend...
- Zend Framework: gestione dei moduli ed esempio modulo di amministrazione Come usare Zend_Layout per la gestione dei moduli In quest'articolo...
- Zend Framework: Zend_Form con ReCaptcha Utilizzare il webservice ReCaptcha per validare i form ReCaptcha è...
Related posts brought to you by Yet Another Related Posts Plugin.












Sarebbe decisamente più utile una guida che posti anche un esempio provvisto di download dell’applicazione già pronta, sopratutto per Lucene, non di facilissima applicazione su siti già creati.
Almeno con una applicazione esterna basterebbe fare dei link…e sopratutto, non sarebbe meglio fare in modo che il caro Lucene cerchi in MySql?
Queste piccole mancanze rendono la guida…non scadente…ma…assai incompleta, ecco…
e poi…non ci sono i nomi dei file precisi, ad esempio, l’Observer Pattern che file è precisamente? dove va? perchè?
Spero di aver reso l’idea.
Francesco
Ciao, innanzitutto ti ringrazio per il commento, avendo pochi riscontri per ora mi è difficile migliorare nell’esposizione. Cerco di ripondere ale domande che mi ha fatto.
1) Non sarebbe meglio fare in modo che il caro Lucene cerchi in MySql?
No. Lucene è un motore di ricerca a se stante. Quindi utilizza il suo indice, che come ho detto nell’articolo sono dei file che crea al’interno di una directory che gli specifichiamo. MySql lo utilizzi per creare e aggiornare il suo indice. Per le ricerchè non sarà utilizzato MySql, ma l’ndice di Lucene, che conterranno gli stessi dati per via dell’observer pattern.
2) l’Observer Pattern che file è precisamente? dove va? perchè?
l’Observer Pattern, come dice il suo nome, è un Pattern. Cos’è un pattern? Un pattern è “una soluzione progettuale generale a un problema ricorrente”, come puoi leggera da wikipedia http://it.wikipedia.org/wiki/Design_pattern. Esistono dei libri interi sui pattern. Il pattern di cui stiamo parlando ora e l’Observer Pattern, in cui 1 oggetto ‘osserva’ altri oggetti. In questo caso, l’oggetto che osserva è il modello SearchIndexer. Gli oggetti ‘osservati’ sono le istanze di Zend_Db_Table_Row_Observerable, ovvero quei modelli che si occupano di inserire e modificare i dati nel database, la classe Paese in questo caso.
Infine, il progetto scaricabile: lo rivedo un attimino, magari eliminando cose inutili ed inserendo qualche dato nel db e lo metto in download nell’ultimo tutorial scritto, quello su Zend Mail. Ma mi ci vorrà qualche giorno ultimamente mi manca proprio il tempo, come avrai notato non scrivo da giorni.
Fammi sapere se hai altre domande.
Ciao
Di Domande ce ne sono sempre tantissime, ad esempio non ho capito molto bene come fare per implementare ad esempio Lucene in un sito già in produzione, anzi, in costruzione in cui però è stata prevista in ritardo la necessità di dover inserire qualcosa come un campo di ricerca.
Poi, come lo collego a Mysql (o Oracle che sia)? è spinosa come questione…ho letto altrove che altrimenti bisognerebbe creare un search engine fulltext, cosa che credo sia fuori discussione
Se poi hai bisogno di un sito-esempio, posso mandarti quello che sto facendo io tabelle mysql comprese, potresti inserirlo, spiegare come hai fatto e metterlo nei download…sarebbe sicuramente MOLTO più chiaro
spero in tua solerte risposta per risolvere questo gravoso problema, poi se vuoi, una volta compreso bene, posso aiutarti a scrivere una guida…più efficace, per incapaci come me
Francesco
Ovviamente, chiederei anche uno script base per poter fare in modo che Mysql salvi gli indici nella cartella index…
lo so che chiedo molto, ma proprio non so dove mettere le mani!
Mi rendo conto che l’agomento è abbastanza difficile, insieme all’acl uno dei più complessi. Da questa guida forse non si evince da dove iniziare, ma dalle tue domande forse ho capito cosa non ti quadra, provo a farti qualche domanda :
1) Hai compreso che il codice del componente Zend_Search_Lucene crea un SUO indice, che sono dei files contenuti in una directory che gli specifichiamo, , e che quindi non è direttamente legato al tipo di database che utilizzi?
2) Hai compreso che ogni record del tuo db che vuoi rendere ricercabile deve essere aggiunto a questo indice?
3) Hai provato a creare il SearchController? La reindexAction di questo controller si occupa di creare i file dell’indice, più nel dettaglio, di aggiungere i documenti ( leggi record del tuo db ) a questo.
4) Hai esteso Zend_Search_Lucene in modo tale che addDocument elimini il documento ( leggi record del tuo db ) dall’indice se già presente, prima di riaggiungerlo?
Comunque mi va bene, inviami il tuo progetto, posso dedicare qualche ora a cercare di integrare Lucene. Invialo su kaiohken1982 DOT hotmail DOT com.
Ciao
Ciao sono molto interessato a questa applicazione se quando hai guardato quello di francesco metti il codice in scarica te ne sarei molto grato.
eheheheeh comincia a farsi la fila
e si perchè è una cosa molto interessante ma allo stesso tempo difficile!!!
dai Sergio! siamo tutti con te
Ho aggiunto alcune funzionalità aggiuntive a quelle già viste in questo articolo, a breve, nel giro di 2 o 3 giorni spero, pubblicherò un seguito con la possibilità di scaricare l’applicazione completa funzionante, abbiate ancora un po’ di pazienza…
Hey! I like your post “arch Lucene ( applicazione reale Zend Framework p.10 ) | Hello World!” so well that I like to ask you whether I should translate into German and linking back. Greetings Engel
[...] avete seguito questo articolo o comunque state lavorando con un’applicazione fatta con Zend Framework che utilizzi il [...]