Manuel Boy Coder Blog

Intelligente Webentwicklung

Doctrine ORM 1.1, Searchable und Tags…

leave a comment

Doctrine bietet ja von Haus aus einige sehr interessante Erweiterungen die über Behaviors eingebunden werden können. So auch das “Searchable” Behavior, das einen guten Job macht und – wenn man sich an die Searchable Dokumentation hält – auch super funktioniert.

Zumindest so lange bis man sich dazu entschließt, auch abhängige Daten aus Relationen zum Suchindex eines Records hinzuzufügen. So möchte ich gerne, dass bei der Suche nach einem Tag, auch der Record der mit diesem Tag versehen ist, gefunden wird. Wie das ganze zu bewerkstelligen ist, zeige ich in den nächsten Absätzen.

Tag-Relation

Da ich noch Doctrine 1.1 verwende, und das Taggable-Behavior bei mir nicht wirklich gut funktiniert hat, habe ich mir einfach selbst eine kleine Tag-Struktur gebastelt, die aus einer Many-to-Many-Relation besteht:

Tag:
  columns:
    id:
      primary: true
      autoincrement: true
      type: integer(10)
    name:
      type: string(255)
      unique: true
 
ArticleTag:
  columns:
    article_id:
      type: integer
      primary: true
    tag_id:
      type: integer
      primary: true
  relations:
    Article:
      local: article_id
      foreign: id
    Tag:
      local: tag_id
      foreign: id
 
Article:
  actAs:
    Searchable:
      fields: [headline, content, Tags]
  columns:
    id:
      primary: true
      autoincrement: true
      type: integer(10)
    headline:
      type: string(255)
    content:
      type: string
  relations:
    Tags:
      class: Tag
      local: article_id
      foreign: tag_id
      refClass: ArticleTag

Optimisten würden denken, dass mit der Angabe von “Tags” in der “Searchable”-Relation schon alles erledigt ist. Doch leider ist das erst die halbe Miete. Denn die Tags werden zwar indiziert, allerdings werden sie dabei mit dem String “array” in der Datenbank abgelegt. Verständlich, denn die Relation ist beim indizieren ein Array aus Tags. Aber wie lässt sich das umgehen?

Der Doctrine Search Analyzer

Bei der Erzeugung des Such-Index wird auf einen Search-Analyzer zurückgegriffen. Diese Klasse ist dafür zuständig die einzelnen Worte die in den Index gelangen sollen vorzubereiten. Der Standard-Analyzer entfernt Leer- und Sonderzeichen. Und genau diese Funktion kann uns dabei helfen die Tags korrekt in der Datenbank zu speichern. Dazu erzeugen wir eine neue Klasse im Models-Verzeichnis “ArticleAnalyzer”:

<?php

class ArticleAnalyzer implements Doctrine_Search_Analyzer_Interface
{
    public function analyze($text, $encoding = null)
    {
    	if(is_array($text)) {
    		$tag_string = '';
    		foreach($text as $tag) {
    			if(isset($tag['name'])) {
    				$tag_string .= $tag['name']. ' ';
    			}
    		}
    		$text = $tag_string;
    	}
    	$text = strip_tags($text);
    	$analyzer = new Doctrine_Search_Analyzer_Standard();
	return $analyzer->analyze($text, $encoding);
    }
}
?>

Die Klasse behandelt Arrays als Liste von Tags und leitet diese einzelnen Begriffe dann korrekt an den Standard-Analyzer weiter. Nun müssen wir dem Such-Plugin nur noch unseren eigenen Analyzer zuweisen, damit dieser auch seine Arbeit verrichten kann.

Einen eigenen Search Analyzer zuweisen

Die Zuweisung erfolgt ganz einfach über die Model-Klasse, in diesem Fall “Article”:


	public function preSave(Doctrine_Event $event)
	{
		$search = Doctrine::getTable('Article')->getTemplate('Doctrine_Template_Searchable')->getPlugin();
		$search->setOption('analyzer', new ArticleAnalyzer());
	}

Ab sofort wird beim Speichern des Entities unser eigener “ArticleAnalyzer” verwendet, der auch die gewünschten Tags in der Datenbank ablegt.

Bonus: Tags bei jedem Speichern indizieren

Es existiert noch ein weiteres kleines Problem: Wenn beim Speichern des Article-Objekts lediglich Tags verändert wurden, so werden diese Änderungen nicht in den Index übernommen, da Doctrine merkt, dass nur das Tag-Objekt, und nicht das Article-Objekt von Änderungen betroffen ist und somit auch die Eventlistener nicht greifen.

Deshalb forcieren wir das Update des Suchindexes bei jedem save()-Aufruf:

	public function postSave(Doctrine_Event $event)
	{
		$article = $event->getInvoker();
		$search = Doctrine::getTable('Article')->getTemplate('Doctrine_Template_Searchable')->getPlugin();
		$search->updateIndex($article->toArray());
	}

Das wars auch schon ;-) Mit diesen kleinen Kniffen werden also auch unsere Tags indiziert. Natürlich lässt sich diese Methode auch auf andere Relationen übertragen.

Ähnliche Artikel:

  1. CakePHP Tutorial: Videos zu einem Model hinzufügen
  2. PHP IDS und das Zend Framework

Written by manuel

December 3rd, 2009 at 10:36 pm

Posted in PHP

Tagged with , , , ,

Leave a Reply