<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ebene7 &#187; Symfony2</title>
	<atom:link href="http://blog.ebene7.com/schlagwort/symfony2/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.ebene7.com</link>
	<description></description>
	<lastBuildDate>Tue, 04 Jun 2013 18:57:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Twig: Flexibel arbeiten mit Blöcken</title>
		<link>https://blog.ebene7.com/2013/03/10/twig-flexibel-arbeiten-mit-bloecken/</link>
		<comments>https://blog.ebene7.com/2013/03/10/twig-flexibel-arbeiten-mit-bloecken/#comments</comments>
		<pubDate>Sun, 10 Mar 2013 18:00:54 +0000</pubDate>
		<dc:creator>Daniel</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Bootstrap]]></category>
		<category><![CDATA[Symfony2]]></category>
		<category><![CDATA[Template]]></category>
		<category><![CDATA[Twig]]></category>

		<guid isPermaLink="false">http://blog.ebene7.com/?p=4025</guid>
		<description><![CDATA[Durch die Arbeit mit Symfony2 lerne ich immer mehr auch die Vorteile und hohe Flexibilität von Twig zu schätzen. Es bietet einfach eine Menge Funktionen von Hause aus und lässt sich zudem auch einfach erweitern. Aber das nur am Rande. &#8230; <a href="https://blog.ebene7.com/2013/03/10/twig-flexibel-arbeiten-mit-bloecken/">Weiterlesen <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Durch die Arbeit mit Symfony2 lerne ich immer mehr auch die Vorteile und hohe Flexibilität von Twig zu schätzen. Es bietet einfach eine Menge Funktionen von Hause aus und lässt sich zudem auch einfach erweitern. Aber das nur am Rande.</p>
<p>In einem Projekt an dem ich gerade arbeite verwende ich ein Bootstrap-Grid und will gewisse Teile nur rendern, wenn sie verwendet werden bzw. Inhalte bereitstellen. Dazu müssen entsprechende Blöcke überprüft und CSS-Klassen angepasst werden.</p>
<p><span id="more-4025"></span>Erstmal das Basis-Template ohne irgendwelche Anpassungen (base.html.twig):</p>
<pre>&lt;html&gt;
  &lt;head&gt;...&lt;/head&gt;
  &lt;body&gt;
    &lt;div class="container-fluid"&gt;
      &lt;div class="row-fluid"&gt;

        &lt;div class="span3 sidebar-left"&gt;
        {% block sidebar_left %}{% endblock %}
        &lt;/div&gt;

        &lt;div class="span4 content"&gt;
        {% block content %}{% endblock %}
        &lt;/div&gt;

        &lt;div class="span3 sidebar-right"&gt;
        {% block sidebar_right %}{% endblock %}
        &lt;/div&gt;

      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>
<p>Der Nachteil an diesem Konstrukt ist recht offensichtlich. Das Layout ist starr und leere Elemente würden viel Platz verschwenden. Um das zu vermeiden können wir das Template etwas intelligenter machen. Normalerweise hat Logik in Templates nichts zu suchen, aber in dem Fall geht es allein um die Anzeige.</p>
<p>Die Blöcke lassen sich in Variablen schreiben und dann prüfen.</p>
<pre>{% set sbleft = block('sidebar_left') %}

{% if sbleft is not empty %}
  {# mach irgendwas #}
{% endif %}</pre>
<p>Durch diesen einfachen Code lassen sich die einzelnen Teile im Template nun einfach steuern. Auch wenn es ein paar Zeilen Code braucht, aber das wird später vielfach wieder eingespart.</p>
<pre>{% set sbleft = block('sidebar_left') %}
{% set sbrigth = block('sidebar_right') %}

{% if sbleft is empty and sbright is empty %}
  {% set contentClass = 'span12' %}
{% elseif sbleft is empty or sbright is empty %}
  {% set contentClass = 'span8' %}
  {% set sidebarClass = 'span4' %}
{% else %}
  {% set contentClass = 'span6' %}
  {% set sidebarClass = 'span3' %}
{% endif %}

&lt;html&gt;
  &lt;head&gt;...&lt;/head&gt;
  &lt;body&gt;
    &lt;div class="container-fluid"&gt;
      &lt;div class="row-fluid"&gt;

        {% if sbleft is not empty %}
        &lt;div class="{{ sidebarClass }} sidebar-left"&gt;
        {% block sidebar_left %}{% endblock %}
        &lt;/div&gt;
        {% endif %}

        &lt;div class="span4 content"&gt;
        {% block content %}{% endblock %}
        &lt;/div&gt;

        {% if sbright is not empty %}
        &lt;div class="{{ sidebarClass }} sidebar-right"&gt;
        {% block sidebar_right %}{% endblock %}
        &lt;/div&gt;
        {% endif %}

      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>
<p>Damit  ist dieses Template nun schlau genug und weiß wann welcher Bereich angezeigt werden soll und in welcher Größe. Die Klassen für die Sidebars können auch getrennt für jede Seite vergeben werden, wenn der Aufbau nicht symetrisch sein soll.</p>
<p>In einem anderen Template können wir wie gewohnt damit arbeiten.</p>
<pre>{% extends "::base.html.twig" %}

{% block content %}
Hier der Seiteninhalt.
{% endblock %}

{% block sidebar_right %}
Hier die rechte Sidebar.
{% endblock %}</pre>
<p>Viel Spaß beim Probieren! Kommentare, Anmerkungen oder Fragen sind wie immer willkommen.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.ebene7.com/2013/03/10/twig-flexibel-arbeiten-mit-bloecken/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Symfony2: Custom ParamConverter für HTTP-Requests erstellen</title>
		<link>https://blog.ebene7.com/2013/01/31/symfony2-custom-paramconverter-fuer-http-requests-erstellen/</link>
		<comments>https://blog.ebene7.com/2013/01/31/symfony2-custom-paramconverter-fuer-http-requests-erstellen/#comments</comments>
		<pubDate>Thu, 31 Jan 2013 21:00:49 +0000</pubDate>
		<dc:creator>Daniel</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Converter]]></category>
		<category><![CDATA[Parameter]]></category>
		<category><![CDATA[Request]]></category>
		<category><![CDATA[Symfony2]]></category>

		<guid isPermaLink="false">http://blog.ebene7.com/?p=3978</guid>
		<description><![CDATA[Eines der vielen kleinen Goodies die Symfony2 zu bieten hat ist der ParamConverter. Die ParamConverter übersetzen die Parameter beim HTTP-Request nach den gewünschten Vorgaben. So können zum Beispiel Datenmodelle direkt geladen oder Datum-Strings als DateTime-Objekte genutzt werden. Wenn man mit &#8230; <a href="https://blog.ebene7.com/2013/01/31/symfony2-custom-paramconverter-fuer-http-requests-erstellen/">Weiterlesen <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Eines der vielen kleinen Goodies die Symfony2 zu bieten hat ist der ParamConverter. Die ParamConverter übersetzen die Parameter beim HTTP-Request nach den gewünschten Vorgaben. So können zum Beispiel Datenmodelle direkt geladen oder Datum-Strings als DateTime-Objekte genutzt werden. Wenn man mit einem Standardsetup arbeitet, sind diese Converter direkt dabei.</p>
<p>Aber auch eigene Converter sind schnell geschrieben und einsatzfähig. Sämtliche Möglichkeiten des <a href="http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html" target="_blank">ParamConverters</a> stehen in der Dokumentation.<br />
<span id="more-3978"></span></p>
<p>Als Ausgangspunkt haben wir in dem Beispiel eine Action, die mit einer ID aufgerufen wird.</p>
<pre>&lt;?php
namespace E7\DemoBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use E7\DemoBundle\Model\Post;

class DefaultController extends Controller {
    /**
     * @Route("/test/{postId}")
     * @ParamConverter()
     * @param   Post $post
     */
    public function testAction(Post $post) {
      // some usefull code
    }
}</pre>
<p>Über die Annotation @ParamConverter weiß das System nun, dass der Parameter übersetzt werden soll. Die bereits zu Beginn erwähnten Converter sind bereits registriert. Um nun eine eigene Logik zu verwenden, brauchen wir einen eigenen Übersetzter. Die Klasse muss das Interface ParamConverterInterface implementieren.</p>
<p>Die Schnittstelle ist sehr einfach gehalten und bietet alls was man braucht. Die Methode supports() muss ein TRUE oder FALSE zurückgeben, ob die Klasse die Anfrage übersetzen kann und apply() mach dann den Rest.</p>
<pre>&lt;?php
namespace E7\DemoBundle\Request\ParamConverter;

use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ModelParamConverter implements ParamConverterInterface {
    function apply(Request $request, ConfigurationInterface $configuration) {
        $name = $configuration-&gt;getName();
        $idParam = $name . 'Id';
        $class = $configuration-&gt;getClass();

        if (!$request-&gt;attributes-&gt;has($idParam)) {
            return false;
        }

        $id = $request-&gt;attributes-&gt;get($idParam);

        $model = loadModelById($id);
        $request-&gt;attributes-&gt;set($param, $model);

        if (null === $model-&gt;getId()) {
            throw new NotFoundHttpException(sprintf('%s object not found.', $class));
        }

        return true;
    }

    function supports(ConfigurationInterface $configuration) {
        return modelClassExists($class);
    }
}</pre>
<p>Die Klasse soll schematisch zeigen, wie ein Converter funktionieren könnte. Die beiden Funktionen loadModelById() und modelClassExists() stehen nur als Pseudocode da. Alle verfügbaren Werte können über die Annotation $configuration abgerufen werden. Über $configuration kommen wir an den Namen des Parameter und den TypeHint.</p>
<pre>Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter Object
(
    [name:protected] =&gt; post
    [class:protected] =&gt; E7\DemoBundle\Model\Post
    [options:protected] =&gt; Array
        (
        )

    [optional:protected] =&gt;
    [converter:protected] =&gt; model
)</pre>
<p>Bevor unser Converter überhaupt beachtet wird, müssen wir ihn in der services.yml als Service registrieren.</p>
<pre>services:
    request.param_converter:
        class: E7\DemoBundle\Request\ParamConverter\ModelParamConverter
        tags:
            - { name: request.param_converter, converter: model }</pre>
<p>Ab jetzt sollte alles zusammenarbeiten und wir können nach belieben Requestparameter umformen. Viel Spaß!</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.ebene7.com/2013/01/31/symfony2-custom-paramconverter-fuer-http-requests-erstellen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony2: Globale Variablen in Routen</title>
		<link>https://blog.ebene7.com/2012/12/13/symfony2-globale-variablen-in-routen/</link>
		<comments>https://blog.ebene7.com/2012/12/13/symfony2-globale-variablen-in-routen/#comments</comments>
		<pubDate>Thu, 13 Dec 2012 05:00:17 +0000</pubDate>
		<dc:creator>Daniel</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Annotations]]></category>
		<category><![CDATA[Konfiguration]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Symfony2]]></category>

		<guid isPermaLink="false">http://blog.ebene7.com/?p=3922</guid>
		<description><![CDATA[Für ein Projekt wollte ich ein Prefix für die Routen verwenden und dieses an einer zentralen Stelle konfigurieren können. In diesem Fall sollte damit der Admin-/Backendbereich abgegrenzt werden. Nach etwas Suchen fand ich eine Möglichkeit in der Symfony Dokumentation. Ab &#8230; <a href="https://blog.ebene7.com/2012/12/13/symfony2-globale-variablen-in-routen/">Weiterlesen <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Für ein Projekt wollte ich ein Prefix für die Routen verwenden und dieses an einer zentralen Stelle konfigurieren können. In diesem Fall sollte damit der Admin-/Backendbereich abgegrenzt werden.</p>
<p>Nach etwas Suchen fand ich eine Möglichkeit in der <a href="http://symfony.com/doc/current/cookbook/routing/service_container_parameters.html" target="_blank">Symfony Dokumentation</a>.</p>
<p>Ab Version 2.1 kann ein Parameter in der Konfigurations-Datei gespeichert werden.</p>
<pre># app/config/config.yml
parameters:
    backend_prefix: admin</pre>
<p>Dieser Parameter kann dann z.B. in der Route-Annotation oder der Routing Konfiguration verwendet werden.</p>
<pre>/**
 * @Route("/%backend_prefix%/user")
 */
class UserBackendController extends Controller {
    /**
     * @Route("/")
     */
    public function indexAction() {
        /* do something */
    }
}</pre>
<p>Durch das Prefix ist dieser Bereich nicht nur optisch im URL abgehoben, sondern kann durch die <a href="http://symfony.com/doc/current/book/security.html" target="_blank">Security Konfiguration</a> auch leicht nur für einzelne Nutzergruppen freigegeben werden.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.ebene7.com/2012/12/13/symfony2-globale-variablen-in-routen/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Symfony Live 2012 in Berlin</title>
		<link>https://blog.ebene7.com/2012/11/26/symfony-live-2012-in-berlin/</link>
		<comments>https://blog.ebene7.com/2012/11/26/symfony-live-2012-in-berlin/#comments</comments>
		<pubDate>Mon, 26 Nov 2012 05:00:16 +0000</pubDate>
		<dc:creator>Daniel</dc:creator>
				<category><![CDATA[Events]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Symfony]]></category>
		<category><![CDATA[Symfony Live]]></category>
		<category><![CDATA[Symfony2]]></category>

		<guid isPermaLink="false">http://blog.ebene7.com/?p=3861</guid>
		<description><![CDATA[In der vergangenen Woche fand am 22. und 23.11. 2012 die Symfony Live 2012 in Berlin statt. Ich hatte ein Ticket für den Freitag und will den Tag kurz für euch zusammenfassen. Das Event fand in einer Kirche statt und &#8230; <a href="https://blog.ebene7.com/2012/11/26/symfony-live-2012-in-berlin/">Weiterlesen <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>In der vergangenen Woche fand am 22. und 23.11. 2012 die <a href="http://berlin2012.live.symfony.com/index.html" target="_blank">Symfony Live 2012 in Berlin</a> statt.</p>
<p>Ich hatte ein Ticket für den Freitag und will den Tag kurz für euch zusammenfassen.<span id="more-3861"></span></p>
<p>Das Event fand in einer Kirche statt und startete nach dem Frühstücksbuffet fast pünktlich. Der Saal war gut besetzt und dank der Technik konnte man auch aus den hinteren Reihen den Vorträgen gut folgen. Wenn ich die Anmoderation richtig in Erinnerung habe, waren Besucher aus 19 Ländern dabei.</p>
<p>Die Sitzplätze fand ich etwas beengt und das kostenlose WLAN war leider die meiste Zeit überlastet, aber beides für mich eher nebensächlich.</p>
<p>Alle Vorträge und die Moderation wurden erwartungsgemäß auf Englisch gehalten und waren dank der erfahrenden Sprecher meist nicht nur informativ, sondern auch recht unterhaltsam.</p>
<p>Meine persönlichen Favoriten an dem Tag waren der Vortrag &#8220;Symfony2 Form Tricks&#8221; von Bernhard Schussek (symmetry <img src='https://blog.ebene7.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> ) und die Keynote von Fabien Potencier. Alle Themen der beiden Tage sind auch auf der <a href="http://berlin2012.live.symfony.com/sessions.html" target="_blank">Symfony-Seite</a> zu finden.</p>
<p>In vielen Vorträgen wurde gezeigt wie einfach verschiedenen Aufgaben in Symfony2 umgesetzt werden können und wie der Code wiederverwendbar bleibt. Nach dem Motto &#8220;Don&#8217;t Reinvent The Wheel&#8221;.</p>
<p>Vieles wird dabei über Konventionen vereinheitlicht und gleichzeitig vereinfacht und immer mehr Konfigurationseinstellungen können über Annotations im Code mitgegeben werden. Auch das Eventsystem und Listerners spielen weiter eine große Rolle.</p>
<p>Aus meiner Sicht hat sich der Tag gelohnt. Vieles war bei durch die Arbeit mit Symfony2 bereits bekannt, aber es war doch noch genug Neues dabei. Für mich ist es immer interessant zu sehen, wie andere Entwickler bestimmte Aufgaben lösen und warum der jeweilige Weg gewählt wurde.</p>
<p>Falls auch jemand von euch an einem der beiden Tage dabei war, schreibt gerne einen Kommentar wie es euch gefallen hat.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.ebene7.com/2012/11/26/symfony-live-2012-in-berlin/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony2: Action in einem anderen Twig-Template ohne Layout rendern</title>
		<link>https://blog.ebene7.com/2012/09/28/symfony2-action-in-einem-anderen-twig-template-ohne-layout-rendern/</link>
		<comments>https://blog.ebene7.com/2012/09/28/symfony2-action-in-einem-anderen-twig-template-ohne-layout-rendern/#comments</comments>
		<pubDate>Fri, 28 Sep 2012 04:00:01 +0000</pubDate>
		<dc:creator>Daniel</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tipps und Tricks]]></category>
		<category><![CDATA[Layout]]></category>
		<category><![CDATA[Symfony2]]></category>
		<category><![CDATA[Twig]]></category>

		<guid isPermaLink="false">http://blog.ebene7.com/?p=3818</guid>
		<description><![CDATA[Mit Twig hat man als Entwickler viele Freiheiten und kann sehr flexible Templates erstellen. So ist es auch möglich Seiten durch Vererbung in ein Layout zu packen oder über die Action das Template ohne Layout in ein anderes zu rendern. &#8230; <a href="https://blog.ebene7.com/2012/09/28/symfony2-action-in-einem-anderen-twig-template-ohne-layout-rendern/">Weiterlesen <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Mit Twig hat man als Entwickler viele Freiheiten und kann sehr flexible Templates erstellen. So ist es auch möglich Seiten durch Vererbung in ein Layout zu packen oder über die Action das Template ohne Layout in ein anderes zu rendern.</p>
<p><span id="more-3818"></span>Dazu kann im Template die aufgerufene Route abgefragt werden, um gewisse Teile anzuzeigen oder nicht.</p>
<pre>{% extends app.request.attributes.get('_route') == '_internal' ?
'::empty.html.twig' :
'MyAppMyBundle::layout.html.twig' %}</pre>
]]></content:encoded>
			<wfw:commentRss>https://blog.ebene7.com/2012/09/28/symfony2-action-in-einem-anderen-twig-template-ohne-layout-rendern/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony2: Event-Listener über Annotations registrieren</title>
		<link>https://blog.ebene7.com/2012/02/27/symfony2-event-listener-ueber-annotations-registrieren/</link>
		<comments>https://blog.ebene7.com/2012/02/27/symfony2-event-listener-ueber-annotations-registrieren/#comments</comments>
		<pubDate>Mon, 27 Feb 2012 05:00:04 +0000</pubDate>
		<dc:creator>Daniel</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Annotations]]></category>
		<category><![CDATA[Eventhandling]]></category>
		<category><![CDATA[Observer]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[Symfony2]]></category>

		<guid isPermaLink="false">http://blog.ebene7.com/?p=3449</guid>
		<description><![CDATA[In den letzten Monaten habe ich hier leider nicht sehr viel geschrieben und freue mich daher um so mehr, wenn ich dann mal wieder über ein spanndendes Thema schreiben kann. So dann auch, wie ich finde, heute. Es geht um &#8230; <a href="https://blog.ebene7.com/2012/02/27/symfony2-event-listener-ueber-annotations-registrieren/">Weiterlesen <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>In den letzten Monaten habe ich hier leider nicht sehr viel geschrieben und freue mich daher um so mehr, wenn ich dann mal wieder über ein spanndendes Thema schreiben kann. So dann auch, wie ich finde, heute.</p>
<p>Es geht um zwei einfache, aber zugleich sehr nützliche Dinge, die das PHP-Framework Symfony2 mitbringt: Ein einfaches, flexibles Event-Handling und Annotations.<span id="more-3449"></span></p>
<p>Für mich waren die letzten drei Wochen auch die ersten Gehversuche mit Symfony2 und bislang wurden alle meine Erwartungen bestätigt. Das Framework ist sehr flexibel, aber auch recht komplex. Wer allerdings andere MVC-Frameworks wie Zend kennt, der wird sich auch hier schnell zurecht finden.</p>
<p>So, genug Lob an der Stelle. Neben der positiven Eigenschafft eines Eventhandlingsystems fiel mir auch gleich die Art auf, wie sich in Symfony2 Controllerrouten mit Annotations konfigurieren lassen.</p>
<p>Leider habe ich nichts Vergleichbares für Eventlistener gefunden und die klassische Konfiguration über YML gefiel mir dann auch nicht mehr so recht. Das nahm ich dann also gleich mal als praxisnahes Kennenlernbeispiel her und habe mir da was gebaut.</p>
<p>Als erstes braucht es immer einen Auslöser, damit die Annotations verarbeitet werden. Dafür habe ich mich an das Event &#8220;kernel.request&#8221; gehängt, das noch vor dem Kontrolleraufruf verarbeitet werden kann.</p>
<p>Einen speziellen Listener hatte ich erst dazwischen, habe es dann später aber wieder etwas vereinfacht. Mein Code entspricht vielleicht nicht allen Symfony-Richtlinien, aber zum Arbeiten und Erklären reicht es aus.</p>
<p>Zum Verarbeiten der Annotations habe ich eine einfache Klasse aufgebaut und mich dabei an den &#8220;Route()&#8221;-Annotations orientiert. Die Methode &#8220;findClass()&#8221; ist aus bestehendem Code &#8220;recycled&#8221;, da sie anders schwerer erreichbar wäre.</p>
<pre>&lt;?php
namespace MyNamespace\Annotations;

use Doctrine\Common\Annotations\Reader;
use Symfony\Component\EventDispatcher\EventDispatcher;

class AnnotatedEventClassLoader
{
  private $reader;
  private $eventDispatcher;
  private $eventAnnotationClass  = 'MyNamespace\\Annotations\\Event';

  /**
   * Constructor
   *
   * @param    Reader $reader
   * @param    EventDispatcher $eventDispatcher
   * @return    void
   */
  public function __construct(
    Reader $reader, EventDispatcher $eventDispatcher) {
    $this-&gt;reader = $reader;
    $this-&gt;eventDispatcher = $eventDispatcher;
  }

  public function init() {
    $listenersDir = realpath(__DIR__ . '/../../Listener');

    $iterator = new \RecursiveIteratorIterator(
      new \RecursiveDirectoryIterator($listenersDir),
        \RecursiveIteratorIterator::LEAVES_ONLY);

    foreach ($iterator as $file) {
      if (!$file-&gt;isFile() || '.php' !== substr($file-&gt;getFilename(), -4)) {
        continue;
      }

      if ($class = $this-&gt;findClass($file)) {
        $reflClass = new \ReflectionClass($class);
        if ($reflClass-&gt;isAbstract()) { continue; }

        foreach($reflClass-&gt;getMethods() as $method) {
          $annotation = $this-&gt;reader-&gt;getMethodAnnotation($method, $this-&gt;eventAnnotationClass);
          if (!$annotation instanceof Event || empty($annotation)) { continue; }
          $this-&gt;registerEvents($annotation, $class, $method-&gt;getName());
        }
      }
    }
  }

  /**
   *
   * @param    \MyNamespace\Annotations\Event $eventAnnotation
   * @param    string|object $class
   * @param    string $method
   * @return   \MyNamespace\Annotations\AnnotatedEventClassLoader
   */
  public function registerEvents($eventAnnotation, $class, $method) {
    $listener = is_object($class) ? $class : new $class();

    foreach ($eventAnnotation-&gt;getEvents() as $event) {
      $this-&gt;eventDispatcher-&gt;addListener(
        $event, array($listener, $method), $eventAnnotation-&gt;getPriority()
      );
    }
    return $this;
  }

  /**
   * Returns the full class name for the first class in the file.
   *
   * @param string $file A PHP file path
   *
   * @return string|false Full class name if found, false otherwise
   */
  protected function findClass($file) {
    $class = false;
    $namespace = false;
    $tokens = token_get_all(file_get_contents($file));
    for ($i = 0, $count = count($tokens); $i &lt; $count; $i++) {
      $token = $tokens[$i];

      if (!is_array($token)) { continue; }

      if (true === $class &amp;&amp; T_STRING === $token[0]) {
        return $namespace.'\\'.$token[1];
      }

      if (true === $namespace &amp;&amp; T_STRING === $token[0]) {
        $namespace = '';
        do {
          $namespace .= $token[1];
          $token = $tokens[++$i];
         } while ($i &lt; $count &amp;&amp; is_array($token) &amp;&amp; in_array($token[0], array(T_NS_SEPARATOR, T_STRING)));
       }

       if (T_CLASS === $token[0]) {
         $class = true;
       }

       if (T_NAMESPACE === $token[0]) {
         $namespace = true;
       }
     }

     return false;
   }
}</pre>
<p>Damit der Loader später auch seinen Dienst tut, muss er noch als Listener für das &#8220;kernel.request&#8221;-Event bekannt gemacht werden. Dafür habe ich die Datei &#8220;app/config/annotations.xml&#8221; mit folgendem Inhalt angelegt.</p>
<pre>&lt;?xml version="1.0" ?&gt;

&lt;container xmlns="http://symfony.com/schema/dic/services"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"&gt;

  &lt;parameters&gt;
    &lt;parameter key="myapp.event.loader.class"&gt;MyNamespace\Annotations\AnnotatedEventClassLoader&lt;/parameter&gt;
  &lt;/parameters&gt;

  &lt;services&gt;
    &lt;service id="myapp.event.loader"&gt;
      &lt;tag name="kernel.event_listener" event="kernel.request" method="init" /&gt;
      &lt;argument type="service" id="annotation_reader" /&gt;
      &lt;argument type="service" id="event_dispatcher" /&gt;
    &lt;/service&gt;
  &lt;/services&gt;
&lt;/container&gt;</pre>
<p>Die Konfiguration muss dann auch noch der AppKernel-Klasse mitgeteilt werden und damit ist der Loader eingebunden.</p>
<pre>class AppKernel extends Kernel {
  // other methods...

  public function registerContainerConfiguration(LoaderInterface $loader) {
    // ...
    $loader-&gt;load(__DIR__ . '/config/annotations.xml');
  }
}</pre>
<p>Annotations werden durch eigene Klassen verarbeitet und können dadurch mit beliebiger Logik versehen werden. Alles was einer Annotation als Parameter mitgegeben wird, bekommt die Klasse als Array im Constructor übergeben.</p>
<p>Für die Registrierung von Eventlistener brauchte ich nur die Events, die hier kommasepariert übergeben werden und gegebenenfalls noch die Priotität, in dem Fall optional. Die Klasse selber wird auch durch eine Annotation als solche gekennzeichnet.</p>
<pre>&lt;?php
namespace MyNamespace\Annotations;

/**
 * @Annotation
 */
class Event {
  private $events = array();
  private $priority = 0;

  public function __construct($options) {
    if (isset($options['value'])) {        
      $this-&gt;setEvents($options['value']);
    }

    $this-&gt;setPriority(
      isset($options['priority']) ? $options['priority'] : 0 );
  }

  public function getEvents() {
    return $this-&gt;events;
  }

  public function setEvents($events) {
    if(!is_array($events)) {
      $events = array_map('trim', explode(',', $events));
    }
    $this-&gt;events = $events;
    return $this;
  }

  public function getPriority() {
    return $this-&gt;priority;
  }

  public function setPriority($priority) {
    $this-&gt;priority = (int)$priority;
      return $this;
  }
}</pre>
<p>Nun sollte alles an PHP-Code und Konfiguration an seinem Platz sein, aber wie funktioniert das jetzt genau?</p>
<p>Durch das Event wird der Loader benachrichtigt, dass die Listener initialisiert werden sollen. Dazu wird das Verzeichnis &#8220;Listener&#8221; nach Klassen mit Annotations durchsucht, welche dann von der Annotation-Event-Klasse verarbeitet werden. Innerhalb des Loader werden dann die Listener für die Events registriert und können von da an verwendet werden.</p>
<p>Die Untersuchung des PHP-Codes funktioniert mit Reflections, was ansich immer den Ruf hat langsam zu sein. Der AnnotationReader verfügt über ein eigenes Caching und macht sich daher zeitlich nicht negativ bemerkbar.</p>
<p>Der Loader durchsucht das gesamte Verzeichnis in beliebiger Tiefe nach Dateien, daher bleibt man in der Namensvergabe frei.</p>
<p>Ein Listener könnte dann zum Beispiel so aussehen:</p>
<pre>&lt;?php
namespace MyNamespace\Bundle\Listener\Deep\Folder\Structure\Test;

use MyNamespace\Annotations\Event;

class Listener {
    /**
     * @Event("foo.bar, bar.foo", priority="1")
     * @param type $event
     */
    public function onFooBar1($event) {
        echo __METHOD__;
    }

    /**
     * @Event("foo.bar", priority="20")
     * @param type $event
     */
    public function onFooBar2($event) {
        echo __METHOD__;
    }
}</pre>
<p>Ein Aufruf aus dem Kontroller funktioniert dann wie gewohnt über den EventDispatcher, der fast überall als Service erreichbar ist.</p>
<pre>public function indexAction() {        
  $this-&gt;get('event_dispatcher')-&gt;dispatch('foo.bar');
}

// oder</pre>
<pre>public function indexAction() {
  $someEvent = new SomeEvent();       
  $this-&gt;get('event_dispatcher')-&gt;dispatch('foo.bar', $someEvent);
}</pre>
<p>Wie gesagt sind das meine ersten Versuche mit Symfony und der Code ist nicht optimiert. Die Funktionen des Loaders sollten noch weiter in Unterobjekte verteilt werden, damit das ganze noch flexibler wird und auch der gecodete Teil mit dem Verzeichnisnamen ist verbesserungfähig.</p>
<p>Ich konnte dabei wieder ein paar Sachen lernen und vielleich spart es ja dem einen oder anderen von euch etwas Zeit, falls ihr das braucht.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.ebene7.com/2012/02/27/symfony2-event-listener-ueber-annotations-registrieren/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
