Heute will ich etwas über Symfony Filter zum Besten geben. Vorab eine kleine Einführung zum
sfTextReplacementPlugin, welches es ermöglicht Text auf einer Website in einer beliebigen TTF-Schriftart als (cachbare) Grafik auszugeben um so beispielsweise h1/h2-Überschriften zu ersetzen. Die Einbindung erfolgt über einen simplen Funktionsaufruf, der an jeder gewünschten Stelle gemacht werden muss, hier ein Beispiel:
<?php use_helper('TextReplacement') ?>
<?php echo graphical_text
('Dies ist meine Überschrift') ?> <p>hier fängt dann der Content der Seite an...</p>
Das Plugin bietet von Haus aus die Möglichkeit, die Seite nach dem Laden mit Hilfe des der
js_replace_text()Helper-Methode per Javascript zu durchsuchen um gewisse Tags durch die entsprechenden Bilder ersetzen zu lassen - doch was, wenn der Browser Javascript deaktiviert hat? Zudem wird hierbei ein zusätzliches Javascript File includiert was auch nicht unbedingt sein muss.
Für einen naiven Programmierer ist es natürlich ärgerlich, diese Replacements in ein großes Projekt zu integrieren weil er jede einzelne Überschrift durch den entsprechenden Funktionsaufruf ersetzt - viel eleganter geschieht dies mittels Filter. Da ich die Ersetzungen nur im Content Bereich der Webseite (also in dem Bereich, der auf jeder Seite anders ist) vorgenommen werden sollen, liegt es auf der Hand, eine abstrakte Klasse zu definieren, die eben diesen Bereich isoliert um ihn dann von einer Subklasse ersetzen zu lassen (eine zusätzliche Anforderung in diesem Projekt war nämlich das Ersetzen einzelner Wörter duch Wikipedia Links). Die (Super-)Klasse sieht in meinem Fall so aus:
<?php
/**
* abstract class to extract contents of template
*
* @author Christoph Hautzinger <mail at hautzinger.info>
*/
abstract class ContentFilter extends sfFilter
{
protected $content;
/**
* extracts pages main content and passes it to self::replace()
*
* @param sfFilter $filterChain
*/
public function execute($filterChain)
{
// Execute next filter
$filterChain->execute();
$timer = sfTimerManager::
getTimer(get_class($this).
' Filter');
$response = $this->getContext()->getResponse();
$content = $response->getContent();
$startMark = '<!-- ContentFilter:START -->';
$stopMark = '<!-- ContentFilter:STOP -->';
// determine section where replacementes are done
$startPos =
strpos($content,
$startMark);
$stopPos =
strpos($content,
$stopMark,
$startPos) +
strlen($stopMark);
$this->
content =
substr($content,
$startPos,
$stopPos -
$startPos);
// do replacements
$this->replace();
$response->setContent(
substr($content,
0,
$startPos) .
$this->
content .
substr($content,
$stopPos) );
$timer->addTime();
}
/**
* should be overwritten in subclass
*/
abstract protected function replace();
}
Zu beachten ist hierbei, dass der Content im dem die Ersetzungen vorgenommen werden sollen im Template durch die beiden Kommentare
<!-- ContentFilter:START --> und
<!-- ContentFilter:STOP --> eingeschlossen werden muss. Der Code enthält keinerlei Magic und sollte auch ohne PHP-Kenntnisse leicht verständlich sein.
Nun aber wieder zurück zum eigentlichen Problem: Wir haben nach einer Lösung gesucht, diese Ersetzungen global vorzunehmen und die sieht wie folgt aus:
<?php
/**
* replaces all h1, h2 tags with its adequate graphical_text()-call replacements
*
* @author Christoph Hautzinger <mail at hautzinger.info>
*/
class TextReplacementFilter extends ContentFilter
{
/**
* searches for all h1 and h2 tags and passes result to self::callbackHeadline()
*/
protected function replace()
{
sfLoader::
loadHelpers(array('TextReplacement'));
}
/**
* replace all matches with a specific h1 tag and our textreplacement-call
* @return string
*/
protected function callbackHeadline($matches)
{
return '<h1>Google Searchword1, Google Searchword2: '.$matches[2].'</h1>' . PHP_EOL .
graphical_text($matches[2], 'headline') .'<br/>';
}
}
Zusätzlich wird noch eine h1-Überschrift in das Dokument eingefügt, um eine bessere Indizierung durch Suchmaschinen zu gewährleisten - man darf natürlich nicht vergessen, sämtliche dieser "speziellen" h1 Tags per CSS auf
display: none zu setzen und den Filter in der filters.yml zu aktivieren.
Und wir sehen: Mit relativ wenig Code haben wir wieder sehr viel erreicht, wenn man die Vorzüge eines hervorragenden Frameworks nutzt. Sicherlich sind reguläre Ausdrücke langsam und preg_replace_callback noch langsamer - dennoch finde ich den Einsatz hier angebracht, zumal die Ausführung des Filters auf eine mittelgroße Seite angewandt nur ca. 6ms in Anspruch nimmt - und sollte sich später die gesamte Anwendung einmal wegen diesen 6ms aufhängen, so kann man immer noch ein Refactoring in Betracht ziehen und auf triviale Stringmanipulation umsteigen

.