Friday, July 25, 2014

Zend Lucene in Symfony Projects

In this article we will introduce you with an easy way to implement the Zend Lucene Full Text Search project in Symfony 1.4 Doctrine ORM website. We will present things a little differently then the original documentation of Symfony. We will try to explain the principle of implementation of a search engine and not providing you with code to use on set. There are things that are very specific and individual to each project and it is necessary to understand the principle according to the situation. Once you have learned how to insert this functionality in the application you can put the elements like autocompleter, advanced search and etc. They are not the subject of this article. Originally you need to download Zend Framework in the following directory:

installing Zend Lucene on symfony website
When you are testing on the localhost, it is not a problem to leave all the files on Zend Framework, but when you are uploading to the server you can leave only the following files and folders from Zend:

• Exception.php
• Loader/
• Autoloader.php
• Search/

//exams/config/ProjectConfiguration.class.php
Now we have to add these lines of code at ProjectConfiguration.class.php
Zend Lucene Full Text Search in Symfony websites
  1. static protected $zendLoaded = false;
  2. static public function registerZend() {
  3. if (self::$zendLoaded) {
  4. return;
  5. }
  6. set_include_path(sfConfig::get('sf_lib_dir').'/vendor'.PATH_SEPARATOR.get_include_ path());
  7. require_once sfConfig::get('sf_lib_dir').'/vendor/Zend/Loader/Autoloader.php';
  8.  
  9. Zend_Loader_Autoloader::getInstance();
  10. self::$zendLoaded = true;
  11. Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());
  12.  
  13. ini_set("iconv.internal_encoding", 'UTF-8');
  14. Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8');
  15. }
If you read the lesson for Zend Lucene integration in the official documentation of Symfony Framework fron here, you will notice that this code has some differences than the present example there. The differences are that we add some additional lines of code and the search works with Cyrillic letters. The next step is to choose a table from the database, which will crawl. In the example, the table is a test. In the application it is represented by two classes. It is necessary to orientate yourself to some of the things we show you because your projects’ name of the classes will be different. But once you learn the technique, it will not be a problem for you.
Programming Zend Lucene in Symfony Projects
In testTable.class.php we enter the following function:
  1. static public function getLuceneIndex() {
  2. ProjectConfiguration::registerZend();
  3. if (file_exists($index = self::getLuceneIndexFile())) {
  4. return Zend_Search_Lucene::open($index);
  5. } else {
  6. return Zend_Search_Lucene::create($index);
  7. }
  8. }
  9. static public function getLuceneIndexFile() {
  10. return sfConfig::get('sf_data_dir').'/exams.'.sfConfig::get('sf_environment').'.index';
  11. }
With this function we create folder, that looks like this:
Developing Zend Lucene in Symfony Website Projects
In this folder we keep the indexes of your entered data that are called to check on demand. Naturally, in this last row:
  1. return sfConfig::get('sf_data_dir').'/exams.'.sfConfig::get('sf_environment').'.index';
You can change the folder name to whatever you want. Now it is necessary to do two more things:
The first is to change the function save() in test.class.php
  1. public function save(Doctrine_Connection $conn = null) {
  2.  
  3. // ...
  4.  
  5. $ret = parent::save($conn);
  6. $this->updateLuceneIndex();
  7. return $ret;
  8. }
and then create the following two functions in the same file:
  1. public function delete(Doctrine_Connection $conn = null) {
  2. $index = testsTable::getLuceneIndex();
  3.  
  4. foreach ($index->find('pk:'.$this->getId()) as $hit) {
  5. $index->delete($hit->id);
  6. }
  7. return parent::delete($conn);
  8. }
  9.  
  10. public function updateLuceneIndex() {
  11. $index = testsTable::getLuceneIndex();
  12.  
  13. // Премахване на съществуващият запис
  14. foreach ($index->find('pk:'.$this->getId()) as $hit) {
  15. $index->delete($hit->id);
  16. }
  17. $doc = new Zend_Search_Lucene_Document();
  18.  
  19. // Съхраняваме първичният ключ на елемента за проверка при търсене
  20. $doc->addField(Zend_Search_Lucene_Field::Keyword('pk', $this->getId()));
  21.  
  22. // Индексираме поле
  23. $doc->addField(Zend_Search_Lucene_Field::UnStored('name', $this->getName(), 'utf-8'));
  24. //$doc->addField(Zend_Search_Lucene_Field::UnStored('description', $this->getDescription(), 'utf-8'));
  25.  
  26. $index->addDocument($doc);
  27. $index->commit();
  28. }
In the first function has there is nothing to change. In the second, the fields:
  1. $doc->addField(Zend_Search_Lucene_Field::UnStored('name', $this->getName(), 'utf-8'));
  2. //$doc->addField(Zend_Search_Lucene_Field::UnStored('description', $this->getDescription(), 'utf-8'));
appear to have the value index and search displayed.
This was our the first part of implementing to our application. Now in CMD write:
Symfony Website and Zend Lucene - web development
And now comes to creation of the route.
Symfony Website and powerfull search engine
In routing.yml we create the following path:
  1. search:
  2. url: /search
  3. param: { module: tests, action: search }
In this case, the module which will be our search engine is the tests module. Item is search.
The controller should look like this:
  1. public function executeSearch(sfWebRequest $request) {
  2. //проверяваме дали имаме post и дали този post ни е изпратил параметър query
  3. if ($request->isMethod('post') || $request->getParameter('query')) {
  4. $query = $request->getParameter('query');
  5. //проверяваме дали заявката за търсене е по голяма или равна на 3 символа
  6.  
  7. if (strlen($query)>=3) {
  8. //ако е >=3 символа изпращаме запитване и извличаме резултатите в променливата query
  9. $this->query = Doctrine_Core::getTable('tests')->getForLuceneQuery($query);
  10. }
  11. } else {
  12. //ако условието за post не е изпълнено тогава извеждаме текст : няма резултат
  13. $this->renderText('No results');
  14. }
  15. }
In SearchSuccess.php we have to develop simple code:
  1. if (isset($query) && count($query) != 0) {
  2. foreach($query as $tests):
  3. echo $tests--->getCategories()->getName();
  4. endforeach;
  5. } еlse {
  6. еcho no results”;
  7. }
In the if expression we check whether there is the variable $query and whether it contains result. Then with the foreach expression we show the result. In the example the field is only one, but in normal circumstances the variable $test contains more the one variable (name, category etc. …). Naturally with html & css we shape the results in a way we want.
//your_project/lib/model/doctrine/testTable.class.php
Now, we go back to the class tesTable.class.php and add the following function:
  1. public function getForLuceneQuery($query) {
  2. $hits = self::getLuceneIndex()->find($query);
  3. $pks = array();
  4. foreach ($hits as $hit) {
  5. $pks[] = $hit->pk;
  6. }
  7. if (empty($pks)) {
  8. return array();
  9. }
  10. $q = $this->createQuery('t')
  11. ->where('t.active = ?', 1)
  12. ->whereIn('t.id', $pks)
  13. ->limit(3);
  14.  
  15. return $q->execute();
  16. }
You can look at your controller and see that this function is called after we have entered a search term.
In part:
  1. $q = $this->createQuery('t')
  2. ->where('t.active = ?', 1)
  3. ->whereIn('t.id', $pks)
  4. ->limit(3);
  5. return $q->execute();
the simple query that we do, is checking whether the given element in the table is active and put limit 3. Thus in the match, the search will give only 3 results. Of course you can create custom queries that
meet the requirements and needs of your application.
And the last part is the form. To have permanently visible form, we will put the code in the layout.php file.
Programming Zend Lucene in web projects
In the form controller we point that after the “submit”, request is sent to the search controller. There they pass through function getForLuceneQuery($query) and if the result of the in query is displayed through the template searchSuccess.php.
We hope this article is useful for developing your websites. Even if you are not successful the first time, do not be disappointed. Nothing is too easy and for everything takes hard work and experience. Our advice is to check the way of implementation of Zend Lucene in the official web site of Symfony Framework at the jobeet project and learn our article too. The most important thing is do not stop testing.