Symfony2: Dependency Injection Container in Formular/AbstractType verwenden

| 8 Kommentare

Das Gute an neuen Projekten ist ja immer, dass Dinge kennenlernt, die man sonst nicht brauchte oder die man nie machen musste, weil sie schon irgendwie gelöst wurden.Wobei ich inzwischen festgestellt habe, dass man mit Symfony2 zwar sehr schnell die Basics generieren kann wie Entities und das CRUD-Handling, aber dass die Zeit bei den Feinheiten schnell wieder verbraucht wird.

In meinem Fall brauchte ich ein einfaches Formular zum Bearbeiten von Entities mit einer ManyToOne-Beziehung und im Dropdown sollten nur Objekte auswählbar sein, die dem angemeldeten Benutzer gehören. Da ich leider keine “Symfony2-Magic-Lösung” fand, habe ich einfach etwas selber gebaut. Es ist klein und handlich und vielleicht kann es ja jemand gebrauchen.

Ich gehe jetzt einfach mal von einem funktionsfähigem Formular mit Dropdown aus bei der nur noch die Anzeige eingeschränkt werden muss.

Der Symfony2 Formbuilder unterstützt die Option “query_builder” mit der wir eine Callbackfunktion zum Filtern nutzen können.

class SomeType extends AbstractType {
  public function buildForm(FormBuilderInterface $builder,
                            array $options) {
    $builder
      ->add('items', 'entity', array(
        'query_builder' => function($er) {
          return $er->createQueryBuilder('i');
        }
      ));
  }
}

Nun brauchen wir noch den aktuellen Benutzer, bzw. den DI-Container über den wir an alles kommen. Sicher könnte man auch einfach den Benutzer beim Erstellen des Formulares mit an den Konstruktor übergeben, aber dann müsste man jedes Formular anders Behandeln. Ich nutze gerne Konventionen und vereinheitliche Dinge, die nach dem gleichen Muster funktionieren. Dazu gehört unter Anderem auch mein CRUD-Kontroller.

Dafür musste ich zwei kleine Sachen machen. Als Erstes habe ich eine erweiterte Klasse gebaut, die von AbstractType erbt und das ContainerAwareInterface implementiert.

class ContainerAwareType extends AbstractType
  implements ContainerAwareInterface {

  private $container;

  // Getter / Setter
}

Dadurch kann der Container dem Formularobjekt übergeben werden und wir kommen an den aktuellen Benutzer. Im zweiten Schritt habe ich die Controller-Klasse erweitert, bzw. nur eine Methode darin. Für verschiedene Zwecke habe ich eine eigene Klasse, so dass es keinen zusätzlichen Aufwand macht.

class Controller extends SymfonyController {
  public function createForm($type, $data = null,
                             array $options = array()) {

    if($type instanceof ContainerAwareInterface) {
      $type->setContainer( $this->get('service_container') );
    }

    return parent::createForm($type, $data, $options);
  }
}

Und das war eigentlich schon fast alles. Jetzt nur noch schnell das Formular anpassen.

class SomeType extends ContainerAwareType {
  public function buildForm(FormBuilderInterface $builder,
                            array $options) {

    $sc = $this->getContainer()->get('security.context');
    $user = $sc->getToken()->getUser();

    $builder
      ->add('items', 'entity', array(
        'query_builder' => function($er) use ($user) {
          return $er->createQueryBuilder('i')
                    ->where('u.owner = :o wner')
                    ->setParameter('owner', $user);
        }
      ));
  }
}

Nun bekommt der angemeldete Benutzer nur noch seine Datensätze in der Auswahl zu sehen und wir können das jetzt überall verwenden. Mir ist eben beim Schreiben noch aufgefallen, dass sich die komplette Query auch in die jeweilige Repository-Klasse der Entity verlagern ließe, dann wäre im Form weniger Code und es wäre auch wiederverwendbarer.

8 Kommentare

  1. Das mit dem QueryBuilder in den Forms ist echt fein. Aber die Verwendung des DI-Containers finde ich hier ein bisschen kompliziert. Genau dafür sind doch die Services wie geschaffen. Natürlich ist es praktisch, wenn man von einer eigenen Controller-Klasse erbt, die createForm überschreibt und den Container injiziert, aber mit dem ganzen Contaier ist der Scope zu groß.
    Lieber den FromType als Service deklarieren und über Constructor-Injection nur die Services hineinreichen, die man wirklich braucht. Erfordert halt, das man im Controller den FromType über den Container abruft anstatt createForm zu verwenden.
    Hat beides seine Vor- und Nachteile. Diskussionswürdig ;D

  2. Mir schien es wiederrum etwas zu viel jedes Formular irgendwo als Service zu registrieren, zumal das nicht alle brauchen und ich dann im abstrakten Kontroller unterscheiden müsste. Für meinen Zweck passte dieser Weg ganz gut. Hätte ich es aber spezieller und brächte das Form an mehreren Stellen, hätte ich vielleicht auch den Service gewählt.

  3. Jedes Formular braucht man ja nicht als Service deklarieren. Hauptsächlich nur die, wo man auch DI braucht. Mir gefällt die Idee aber. Man bräuchte nur ein zusätzliches Marker Interface, der Service darf halt nur Setter-Injection verwenden.
    Im abstrakten Controller dann auf das Marker Interface abfragen, und anstatt der übergebenen FormType Instanz, eine Instanz über den DI-Container beziehen.
    Wie gesagt, hat alles Vor- und Nachteile. Sollte ja keine Kritik sein.

  4. Das ContainerAwareInterface-Interface gibt es schon bei Symfony, da braucht es kein eigenen Marker. Ein wenig hatte ich ja die Hoffnung, dass es auch schon etwas Magic in Symfony gibt, die das Interface abfragt.

  5. ContainerAwareInterface ist eigentlich nur dafür da, denn Setter für den Container zu “markieren”. Aber sonst, Symfony ist mächtig aber im Core ist recht wenig Magic. Dafür gibt es viele tolle Bundles die ziemlich viel Magic bringen. SensioFrameworkExtraBundle z.B. ist aber im Standardbundle schon dabei.

  6. Editieren geht ja nicht. Einmal ins JMSDiExtraBundle geblickt, fällt das umständliche deklarieren eines FormType als Service dank Annotation komplett weg. http://jmsyst.com/bundles/JMSDiExtraBundle/1.1/annotations#formtype

    Entweder man stellt sich komplett auf @FormType und $this->formFactory->create(…) um, oder erweitert die abstrakte Klasse (somit muss man nicht jeden Type als Service deklarieren)

    if(/** abfrage ob FormType Service oder auf weiteres Markierer Interface **/) {
    return $this->formFactory->create($type->getName(), $data, $options);
    }

    Nur so Ideen, die dein Blogpost bei mir anregten ^^

  7. Auch eine gute Idee. Das DIExtraBundle bietet schon ein paar nette Sachen an.

  8. Pingback: Symfony2: Unabhängiger mit eigenen Basisklassen – ebene7

Hinterlasse eine Antwort

Pflichtfelder sind mit * markiert.


Schlagwörter: A/B-Test, AbstractType, Adapter, AddOn, Administration, Ajax, Alühn, Alühn2, Amazon, Animation, Annotations, Anonyme Klasse, Ant, Apache, API, Array, ArrayAccess, Attachment, Auftrag, Ausbildung, Auswertung, Authentifizierung, AutoLoader, AWS, Backup, Bedienung, Bedingung, Benchmark, Berechtigung, Berlin, Bildbearbeitung, Bildschirmfoto, Blog, Blogroll, BOM, Bootstrap, Bot, Browser, Bugtracker, Byte Order Mark, Bücher, Cache, CakePHP, Call-Center, Callback, CamelCase, Canvas, Captcha, CDN, Cheatsheet, CLI, Clickout, Closure, Cloud, CodeSniffer, Collection, Community, Comparator, Config, Contest, Controller, Converter, CouchDB, Countable, Cronjob, CRUD, CSS, CSV, CustomLibrary, Custom_Model, Daemon, Data Mapper, Datei, Datenbank, Datensicherung, Datenstruktur, Datentypen, Dating, Datum, Debug, Decorator, Dekorierer, Design, Design Patterns, Doctrine, Dokumentation, Dump, Duplikat, each, EC2, Eclipse, Email, Entwicklung, Entwurfsmuster, Enum, Erweiterung, Event, Eventhandling, Exception-Handling, Extension, Facebook, Factory, Fallback, Fehler, Fehlermeldung, Filter, Firefox, Flash, flexigrid, Foreach, Formatierung, Formular, Framework, FTP, Funktion, Futon, ga:pi(), Getter, Gnome, Google Analytics, Hash, Hash-Bang, Header, htaccess, HTML5, htpasswd, HTTP, HTTPS, IDE, If, Implementierung, InnoDB, Interceptor, Interface, Internet Explorer, isset, Iterator, Java, JavaScript, Job, jQuery, Kommentar, Konfiguration, Konsole, Kontrollstruktur, kopieren, kostenlos, Kundenbetreuung, Late Static Binding, Layout, Links, Linux, Listeners, Lizenz, Logging, Löschen, Magento, Magic Methods, Manual, ManyToMany, Marketing, Methode, Model, Monolog, MVC, MySQL, NetBeans, Network, Nirvanix, Objekt, Observable, Observer, OneToMany, Online Tool, OOP, Open Source, Operator, OR-Mapper, Order, ORM, O’Reilly, Parameter, Partnersuche, Passwort, Performance, PHP, php.ini, PHP hates me, phpMyAdmin, PHPUnit, Plugin, Point and Click, Popup, Praktikum, Proxy, Prüfsumme, Prüfung, QR-Code, Qualitätssicherung, Query, Queue, Redesign, Refactoring, Reflection, Repository, Request, Response, Responsive Design, Rest-API, Rockstar, Rollback, Routing, S3, Samba, Scheifen, Schleife, Schutz, Screenshot, Secure Shell, Selbstreferenz, Server, Setter, setTimeout, Shop, Sicherheit, Sicherung, Sichtbarkeit, Singleton Pattern, Skin, SOAP, Social Network, Software, Sortierung, Sourcecode, Spam, Speicherproblem, Spickzettel, SPL, Splittest, SSH, SSL, Stammtisch, Statement, static, Statistik, Status, Stellvertreter, Strategy Pattern, Stream, String, Stuttgart, Stylesheet, Subversion, Sun VirtualBox, Support, SVN, Switch, Symfony, Symfony2, Symfony Live, Tag, Template, Template Method, Ternär Operator, Testing, Theme, Thumbnail, Tool, Tour, Tracking, Twig, Twitter, Type-Cast, Ubuntu, Umwandlung, Underscore, unset, Update, Upload, Url, User Story, Validierung, Vererbung, Versionskontrolle, Versionsnummer, Verzweigung, Video, Videospiel, Virtualisierung, Visitor Pattern, Vorschaubild, walk, Warteschlange, Webserver, Webservice, Weiterleitung, Werkzeug, Windows, WindowsAzure, WordPress, Wrapper, Writer, XML, Youtube, Zeitschleife, Zeitsteuerung, Zend Framework, Zend_Application, Zend_Cloud, Zend_CodeGenerator, Zend_Http_Client, Zend_Reflection, Zend_Service, ZPress, Zugangskontrolle, Zugriffsmethode