Code snippets for symfony 1.x

Navigation

Snippets by user Matt Robinson

Adding stemming to Doctrine Searchable

Stemming reduces a word to its common stem, so if you search on Google for "Knit", you'll also be searching for "Knits" and "Knitting". Sadly, Doctrine's Searchable behaviour doesn't support stemming out of the box, but it's pretty easy to add.

I've chosen to use the PECL 'stem' extension in this implementation because it contains the excellent Porter2 stemmer. You may be able to find a free pure PHP implementation of the algorithm somewhere, or decide that the original Porter stemmer is good enough. I couldn't.

You'll need to compile and install the PECL stem extension, and make sure it's loaded.

The stemming analyzer class

Create lib/MyAnalyzer.class.php:

    class MyAnalyzer extends Doctrine_Search_Analyzer_Standard
    {
 
      public function analyze($text)
      {
 
        // First run the standard analyzer. This will do a lot of tidying on 
        // the text and remove stopwords, so we only need to do the stemming
        $text = parent::analyze($text);
 
        foreach ($text as &$keyword) {
          $keyword = stem_english($keyword);
        }
 
        return $text;
      }
    }
 

Making your project use it

First, create a doctrine listener, lib/MyConnectionListener.class.php

    class MyConnectionListener extends Doctrine_EventListener
    {
 
      public function postConnect(Doctrine_Event $e)
      {
          $entity_table = Doctrine_Core::getTable('Entity');
          $entity_table->getTemplate('Doctrine_Template_Searchable')
                       ->getPlugin()
                       ->setOption('analyzer', new MyAnalyzer);
        }
      }
    }
 

Then edit your ProjectConfiguration class:

    public function configureDoctrineConnection(Doctrine_Connection $conn)
    {
      $conn->addListener(new MyConnectionListener)
    }
 

Finally, in your frontend code, make sure you stem your search terms before running a query.

    $terms = 'my search string';
 
    $stemmer = new MyAnalyzer();
    $terms = join($stemmer->analyze($terms), ' ');
 
    $results = Doctrine::getTable('Entity')->search($terms);
 

A couple of notes:

by Matt Robinson on 2009-11-14, tagged doctrine  search  searchable  stemming 

Master & Slave connections in Doctrine 1.2 and Symfony 1.3

Doctrine's built-in support for multiple connections follows a baffling use-case that allows different Model objects to be saved on different connections. A more common usage of multiple connections is to send read commands to a slave database and write commands to a master. Here's how to do it.

  1. Declare your connections in databases.yml as usual, name one of them master and one of them slave.

  2. Override Doctrine's default Query and Record objects. Edit config/ProjectConfiguration.class.php and add the following method:

    public function configureDoctrine(Doctrine_Manager $manager)
    {
        // Configure custom query and custom record classes
        $manager->setAttribute(Doctrine::ATTR_QUERY_CLASS, 'MyQuery');
        $options = array('baseClassName' => 'MyRecord');
        sfConfig::set('doctrine_model_builder_options', $options);  
    }
     
  3. Create your MyQuery class in lib/MyQuery

    class MyQuery extends Doctrine_Query
    {
      public function preQuery()
      {
          // If this is a select query then set connection to the slave
          if (Doctrine_Query::SELECT == $this->getType()) {
              $this->_conn = Doctrine_Manager::getInstance()->getConnection('slave');
          // All other queries are writes so they need to go to the master
          } else {
              $this->_conn = Doctrine_Manager::getInstance()->getConnection('master');
          }
      }
    }
     
  4. Create your MyRecord class in lib/MyRecord.class.php

    abstract class MyRecord extends sfDoctrineRecord
    {
        public function save(Doctrine_Connection $conn = null)
        {
          $conn = Doctrine_Manager::getInstance()->getConnection('master');
          parent::save($conn);
        }
    }
     
  5. If you're using a Doctrine behaviour that creates new tables, like the Versionable behaviour, tell it to use MyRecord too.

    Entity:
      ActAs:
        Versionable:
          builderOptions:
            baseClassName: MyRecord
     
  6. Finally, rebuild your model

    ./symfony doctrine:build --all-classes
     

If you have multiple slaves, modify the ProjectConfiguration to pick one on startup then change the method on your Query object to retrieve it.

by Matt Robinson on 2009-11-13, tagged database  doctrine