Symfony 3 Workshop

Dieser Workshop befasst sich mit der Einrichtung und Nutzung des PHP-Frameworks Symfony in Version 3. Dies soll ein Guide sein, der sich besonders an solche richtet, die noch keine Erfahrung mit diesem Framework haben. Es werden einige Kenntnisse in PHP und Linux vorausgesetzt, allerdings nichts, was „Onkel Google“ nicht beheben könnte.

Der Guide strebt keine Vollständigkeit an, alle Angaben sind ohne Gewähr. Vorschläge zur Verbesserung oder Fehlerkorrekturen gern unten in die Kommentare!

Aufgrund der schnellen Weiterentwicklung des Frameworks sind einige der unten aufgeführten Artikel bereits mit Vorsicht zu genießen! Zum Beispiel wird sich für Symfony 4.x das Dateisystem größtenteils ändern!

Stand: Januar 2017
Symfony: Version 3.2.x

Inhalte

  1. Symfony installieren (Win + MAC + Linux)
  2. Symfony in PhpStorm einrichten
  3. Composer hinzufügen und Bundles installieren
  4. Das Dateisystem und Namenskonventionen
  5. Die Environments: dev, prod und test
  6. Die Konsole nutzen
  7. Dependencies verwalten: Windows vs. Unix
  8. Die Profiler-Debugging-Leiste
  9. Routing
  10. Formulare und Form-Theming
  11. Requests, Responses und Sessions

Weiteres

  1. Die Dokumentation
  2. Einen Twig-Service erstellen
  3. Einen eigenen Konsolenbefehl erstellen
  4. Symfony auf einen simplen FTP deployen

Symfony installieren

Voraussetzungen

Andere Quellen

Die Knp-University hat bereits ein Installations und Einsteiger Tutorial für Symfony erstellt. Hier wird allerdings nur erklärt, wie der Symfony-Apache-Server gestartet wird, ohne eine Datenbank.

Die Vorbereitungen

Für Windows: Download und Installation von XAMPP erfüllt alle nötigen Voraussetzungen.

Für Mac und Linux: Entweder XAMPP installieren (hier die einfachste Möglichkeit), die Installation von LAMPP-Server oder die entsprechenden Pakete manuell installieren und konfigurieren (unvollständig):

$ sudo apt-get install php7.0-cli php7.0-xml php7.0-intl php7.0-mysql mysql-server

Die simple Installation

Schritt 1: Symfony downloaden

Zuerst muss die binäre Datei symfony von dieser Seite heruntergeladen werden. Dies funktioniert per Kommandozeile und wird auch auf der Seite erklärt.

Für Windows in der CMD: php -r "file_put_contents('symfony', file_get_contents('https://symfony.com/installer'));"

Für MAC und Linux:

$ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony 
$ sudo chmod a+x /usr/local/bin/symfony

(Stand: Dezember 2016)

Schritt 2: Ein Projekt erstellen

Mit php symfony new {my_project} wird ein neues Projekt erstellt. {my_project} ist dabei eine beliebige Bezeichnung und lediglich der Name für den neuen Ordner, den Symfony anlegt. Die Binärdatei lädt daraufhin alle nötigen Daten vom Symfony-Projekt-Server.

Für die Erstellung weiterer Projekte empfiehlt es sich, vorher ein symfony auto_update auszuführen, um die Binärdatei auf den neuesten Stand zu bringen. Hier empfiehlt es sich auf die Version zu achten: Die aktuellste Version für Symfony 3.1.x ist 3.1.10. Zum aktuellen Stand des Artikels ist Symfony 3.2.2 bereits released. Ein symfony auto_update auf 3.1.x wird aber nur auf 3.1.10 aktualisiert werden! Neuere Versionen müssen wieder manuell von Server geholt werden.

Fehleranalyse: Bei der Erstellung des Projekts mit Symfony über XAMPP unter Windows tritt häufig ein Fehler auf: „SSL certificate error: unable to get local issuer certificate“. In diesem Fall muss dem PHP-CLI ein Zertifikat nachgereicht werden. Eine Anleitung gibt es hier.

Tipp: Die meisten Projekte verwendet eine Versionierungsverwaltung. Git auf einer Windows-Maschine zu installieren macht zusätzlich Sinn, da mit Git auch eine Bash auf MINGW-Basis installiert wird. Diese eignet sich als Kommandozeile besser, als das Windows-eigene CMD-Tool.

Schritt 3: Das Projekt testen

Man wechsle nun einfach in den erstellten Projektordner und führe ein php bin/console server:run aus. Wurde das Projekt korrekt initialisiert, findet man unter http://localhost:8000/ im Browser die Testseite von Symfony.

Für den MySQL-Support aktiviert man einfach den MySQL-Dienst in XAMPP.

Ein Symfony Projekt in PhpStorm einrichten

Ein Projekt hinzufügen

Unter File -> Open directory wählt man einfach den erstellten Projektordner aus. PhpStorm importiert diesen automatisch als neues Projekt. Ist das Symfony Plugin bereits installiert, erkennt die IDE die Verzeichnisstruktur und bietet sofort die Einstellungen zum Plugin an.

Tipp: Existiert bereits ein Git-Repository für ein Symfony-Projekt, klont man das Projekt einfach über VCS -> Checkout from Version Control -> Git mit der passenden URL. Achtung: Bevor man loslegen kann, muss der /vendor Ordner zuerst per Composer installiert werden!

Plugins installieren

Der besondere Vorteil von PhpStorm gegenüber anderen IDEs sind die speziell für Symfony entwickelten Plugins. Es gibt insgesamt 3 Plugins, die die Arbeit mit Symfony vereinfachen:

  1. Das Symfony Plugin
  2. PHP Annotations
  3. PHP Toolbox

Unter File -> Settings -> Plugins klickt man auf „Browse repositories“, sucht und installiert die Plugins. Weitere Plugins sind optional. Danach muss PhpStorm neu gestartet werden.

Plugins einstellen

Unter File -> Settings -> Languages & Frameworks -> PHP -> Symfony werden alle Einstellungen zum Symfony-Projekt getroffen. Die Einstellungen gelten immer nur für das aktuelle Projekt und müssen daher für jedes folgende Projekt erneut getroffen werden!

Zuerst muss das Plugin über die Checkbox „Enable Plugin for this Projekt“ aktiviert werden. Außerdem sollten die URLs für „Path to urlGenerator.php“ zu var/cache/dev/appDevUrlGenerator.php und „Translation Root Path“ zu var/cache/dev/translations verändert werden. Weitere Einstellungen in diesem Fenster sind optional.

Außerhalb des Einstellungs-Fensters empfiehlt es sich, den Ordner /src als „Sources Root“ sowie den Ordner /var als „Excluded“ und /test als „Test Sources Root“ zu markieren. Die Einstellungen findet man per Rechtsklick -> Mark Directory As. Auch nach diesen Einstellungen muss PhpStorm neu gestartet werden.

Code-Standards einstellen

Zwar existieren in PhpStorm für Symfony eigene Code-Standard Bibliotheken, diese sind mit den offiziellen Code-Standards aber nicht ganz konform. Zitat: „Symfony follows the standards defined in the PSR-0, PSR-1, PSR-2 and PSR-4 documents.“ und diese lassen sich einstellen.

Dazu unter File -> Settings -> Editor -> Code & Style -> PHP bei Scheme „default“ auswählen und danach auf der rechten Seite Set from... -> Predefined Style -> PSR1/PSR2 auswählen. Um das Schema zu speichern auf Manage... -> default auswählen -> Save As... und unter einem beliebigen Namen speichern. Diese Einstellungen gelten wieder nur für das aktuelle Projekt, können aber exportiert oder kopiert werden!

Sonstige Einstellungen (optional)

  • File Encodings auf UTF-8 stellen
  • Zeilenumbrüche auf CRLF (für Git)

Einen Composer installieren

Da auch Symfony auf einen AutoLoader baut, wird ein Composer für das interne Management benötigt. Beim Composer handelt es sich um einen Abhängigkeitsmanager, der die require() sowie die include() Funktionen von PHP durch einen Namensraum ersetzt. Für die Arbeit mit einen Composer wird mindestens PHP Version 5.3.2 benötigt.

Installation

Die Installation wird mit ein paar PHP Befehlen ausgeführt. Alternativ kann man auch eine .phar Datei direkt herunterladen. Auf der Download-Seite des Projekts wird die Installation im Detail beschrieben. Eine globale Installation (oder Installation für den angemeldeten Nutzer) macht durchaus Sinn.

Möchte man den Composer lieber pro Projekt verwenden und arbeitet mit Git, sollte die .gitignore die Zeile /composer.phar enthalten.

PhpStorm macht es sogar noch einfacher: Tools -> Composer -> Init Composer... und dann einfach den blauen Link „Click here to download from getcomposer.org“ anklicken.

Ein Bundle installieren

Hierfür werden 3 Schritte ausgeführt:

  1. Dem Composer das neue Paket hinzufügen: Der befehl folgt dem Schema composer require "contributor/package-name:version" und fügt der composer.json die Zeile hinzu. Damit wird das Paket heruntergeladen und in der composer.json als Abhängigkeit aufgeführt.
  2. Symfony das Bundle zur Verfügung stellen: Zusätzlich muss das Bundle in der /app/AppKernel.php gelistet werden. Dafür fügt man dem $bundles-Array eine Zeile hinzu. Wie genau diese Zeile aussieht, steht in der jeweiligen Dokumentation des Bundles.
  3. Das Bundle konfigurieren (optional): Viele Bundles sind so komplex, dass sie eigene Parameter liefern, die meistens in der /app/config/config.yml hinterlegt werden.

Bundles können auf unterschiedlichsten Seiten angeboten werden. Die größten und bekanntesten für Symfony sind Packagist und die Symfony-Dokumentation selbst. In der Doku finden sich etliche Seiten, die sowohl Installation, als auch Verwendung des Bundles beschreiben. Ein häufig installiertes Bundle ist zum Beispiel DoctrineMigrations.

Tipp: Mit der Option --no-scripts wird die Ausführung der „scripts“-Sektion in der composer.json unterbunden. Darin werden Installationsschritte für Symfony ausgeführt. Dies Ausführung zu unterbinden kann sinnvoll sein, um Zeit zu sparen, wenn zum Beispiel mehrere Pakete hintereinander installiert werden.

Das Dateisystem

Der Symfony Verzeichnisbaum besteht aus nur wenigen Ordnern:

Projekt/
  |-- app/
  |   |-- config/
  |   |-- Resources/
  |   |-- AppKernel.php
  |
  |-- bin/
  |   |-- console (executable)
  |   |-- symfony_requirements (executable)
  |
  |-- src/
  |   |-- (all my bundles ...)
  |
  |-- tests/
  |   |-- (all my phpunit tests ...)
  |
  |-- var/
  |   |-- cache/
  |   |-- logs/
  |   |-- sessions/
  |
  |-- vendor/
  |   |-- (symfony files and bundles)
  |
  |-- web/ (public)
      |-- app.php
      |-- app_dev.php

Dabei macht /vendor die ganze Basis aus. Hier werden alle Symfony Dateien als Bundles abgelegt. Auch externe Plugin Bundles finden sich hier. Dieser Ordner wird nicht in ein Repository übertragen, da er ersten zu groß ist und zweitens jeder Zeit durch den Composer heruntergeladen werden kann.

Im Ordner /bin befinden sich zwei Executables für die Steuerung der Konsole und die Überprüfung der Voraussetzungen für Symfony.

Die meiste Zeit verbringt der Entwickler in /app, /src und /web. Während in ersterem hauptsächlich Konfigurationen vorgenommen und statischer Inhalt angelegt wird, befindet sich das „Herz“ der Anwendung in /src. Hier erstellt man nacheinander verschiedene Bundles um die Anwendung zu fertigen. Das /web Verzeichnis ist das einzige öffentliche Verzeichnis. Hier werden Bilder, Assets – wie CSS und JavaScript – sowie andere erreichbare Dateien abgelegt. Symfony verwendet diesen Ordner als „root“ auf dem Server.

Der Ordner /tests ist für eben solche Funktionalität vorbehalten. Symfony sucht nach Übereinstimmungen zum /src Verzeichnis, um Testabläufe auf dortige Bundles anzuwenden. Alle Tests folgen dem PHPUnit Standard, wobei Symfony ein entsprechendes Bundle und eine ordentlich lange Dokumentationsseite dafür parat hat.

Zuletzt werden in /var der Cache, eventuelle Sessions und die Logs abgelegt. Je Nach Environment ist Symfony auf alle Vorgänge (dev) oder nur Fehler (prod) beschränkt und schreibt alles in eine entsprechende {env}.log. Gerade während der Entwicklung nimmt die dev.log extrem viel Inhalt auf. Das sollte also hin und wieder beachtet werden. Die Logs können aber auch in der Konfiguration angepasst und so z.B. per Rotation auf Tage aufgeteilt werden.

Die Namenskonventionen

Symfony setzt für bestimmte Projektdateien bestimmte Namen voraus. Hierbei gilt die „CamelCase“ Schreibweise als globale Voraussetzung. Ein kleine (unvollständige) Übersicht:

  • Bundle Loader müssen auf „Bundle“ enden und sich in /src/{BundleName}/ befinden
    z.B. AppBundle.php
  • Steuerungsdateien in Bundles müssen auf „Controller“ enden und sich in /src/{BundleName}/Controller/ befinden
    z.B. AcmeController.php
  • Für Objekte existiert keine Konvention, außer dass sie sich in /src/{BundleName}/Entity/ befinden müssen.
  • Doctrine Depots müssen auf „Repository“ enden und sich in /src/{BundleName}/Repository/befinden
    z.B. UserEntityRepository.php
  • Formulare müssen auf „Form“ enden und sich in /src/{BundleName}/Form/befinden
    z.B. UserForm.php
  • Assert-Validatoren müssen auf „Validator“ enden und sich in /src/{BundleName}/Validator/Constraints/befinden
    z.B. HardwareSignatureValidator.php
  • Twig Templates haben keine Konvention, müssen aber entweder in /app/Resources/views/ oder in /src/{BundleName}/Resources/views/ hinterlegt werden.
  • Übersetzungsdateien müssen der Form messages.{location}.{format} entsprechen und sich in /app/Resources/translations/ oder in /src/{BundleName}/Resources/translations/ befinden. Weiterhin sind {location} das Länderkürzel (z.B. „en“ oder „de_DE“) und {format} entweder „yml“, „xml“ oder „php“.

Die Symfony Environments

Ein neues Symfony-Projekt startet mit 3 Environments: dev, prod und test. Eine solche „Umgebung“ erlaubt dabei die Ausführung des Projekts mit unterschiedlichen Konfigurationen. Jede der 3 Env’s hat dabei schon eine bestimmte Rolle, es können aber auch eigene Environments definiert werden.

Grundlegend werden Environments durch eine Config-Datei unter /app/config/ definiert. So existieren bereits config_dev.yml, config_prod.yml und config_test.yml. Jede dieser Dateien importiert den Inhalt der config.yml, globale Parameter sollten also hier definiert werden.

Das „dev“-Environment

Dieses Env ist mit Eigenschaften bestückt, die den Entwicklungsprozess vereinfachen und mit mehr Informationen versehen sollen. Hier existiert zusätzlich eine routing_dev.yml, die mit dem Profiler zusammenhängt. Nur in dev hat man Zugriff auf die Steuerleiste am unteren Rand der Website, die Umgebungsvariablen, Fehler, Exceptions, Kommunikation, Datenbankabfragen, Pathing, Übersetzung, Benutzer und noch vieles mehr anzeigt. Über das interne Logging wird jeder Schritt nachvollziehbar gemacht.

In der config_dev.yml lässt sich zum Beispiel einstellen, dass Redirects unterbrochen werden, damit sich Umleitungen besser zurückverfolgen lassen. Zusätzlich können auch Status-Errors (404, 500, etc.) über einen Testpfad ausprobiert werden.

Das „test“-Environment

Dieses Env gehört mit zum Development. Es kommt erst einmal ohne viele Einstellungen daher, kann also persönlich eingerichtet werden. Durch wenige Codezeilen hat man es schnell mit einer eigenen Datenbank versehen. Das ist speziell für PHP-Unit-Tests praktisch zu handhaben.

Das „prod“-Environment

Dieses Env ist die eigentliche Plattform, die auf den Produktivserver installiert wird. Hier sind alle Development-Tools ausgeschaltet und die Performance auf das Maximum erhöht.

Der Spezialfall unter den Config-Dateien

Neben den config{_...}.yml Dateien existiert eine parameters.yml. Hier werden wichtige Konstanten, wie die Datenbank-Credentials hinterlegt. Deshalb gehört die Datei nicht zu einem Git-Repository (siehe Eintrag in der .gitignore). Auf jedem Server kann also eine eigene parameters.yml existieren.

Beachtet werden muss allerdings, dass die Config-Datei an die parameters.yml.dist gebunden ist. Jede Variable muss sowohl in der einen, als auch in der anderen Datei existieren, sonst gibt es Probleme beim composer install (siehe „scripts“-Sektion der composer.json)!

Die Symfony-Konsole

Gemeint ist hier die PHP-Executable, die sich im Projekt unter bin/console eines jeden Symfony-Projekts befindet. Die Konsole wird über ein Terminal mittels php bin/console angesprochen. Hierfür muss sich das Terminal bereits im Projektverzeichnis befinden. Gibt man den Befehl genau so ein, erhält man Informationen zum Projekt und eine Liste aller möglichen ausführbaren Befehle:

# php bin/console
Symfony version 3.2.4 - app/dev/debug

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -e, --env=ENV         The Environment name. [default: "dev"]
      --no-debug        Switches off debug mode.
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  help                                    Displays help for a command
  list                                    Lists commands
  ...

Unter den Befehlen befinden sich viel Feedback zum Debuggen. Zum Beispiel wird mit debug:router eine Liste aller gefundenen ansteuerbaren Seiten generiert.

Die am häufigsten genutzten Befehle sind assets:install, cache:clear, server:run und Generatoren für Objekte oder Bundles. Es empfiehlt sich, diese Generatoren zu nutzen, statt die Benötigten Dateien selbst zu erstellen, da z.B. die Erstellung eines Bundles nicht nur aus der Erstellung einer Datei besteht. Außerdem wird durch die Generatoren der Zustand von Symfony überprüft und eventuelle Probleme mit Namenskonventionen vermieden.

Über die Parameter Optionen lässt sich zusätzlich das Environment festlegen: php bin/console cache:clear --env=prod.

Dependencies in Symfony verwalten

Symfony bietet die Archivierung eigener Dateien (Twig-Templates, Übersetzungen, Stylesheets, JavaScripts, Bilder, Icons, etc.) auf unterschiedliche Weise an:

  1. Twig-Templates werden zuerst unter /app/Resources/views, Übersetzung-Dateien unter app/Resources/translations gesucht. Werden dort keine Matches gefunden, wird der Reihe nach jedes Bundle in der gleichen Weise unter /{MyBundle}/Resources durchsucht.
  2. Error-Seiten müssen immer unter /app/Resources/TwigBundle/views/Exception abgelegt werden.
  3. Stylesheets, JavaScripts, Bilder, etc. müssen in /web hinterlegt werden. Alternativ, um die Übersicht zu wahren, können diese Dateien auch im zugehörigen Bundle unter /{MyBundle}/Resources/public verstaut werden. Dies hat allerdings Nachteile (siehe unten).

Die Punkte 1 und 2 stellen kein größeres Problem dar. Symfony verwaltet diese fast automatisch und für die Nutzung von Twig-Templates gibt es die automatische Vervollständigung in PhpStorm.

Ganz anders bei den restlichen Dateien: Hier muss die Addressierung in Twig über eine Twig-eigene Funktion realisiert werden. Mit {{ asset('assets/my-script.js') }} wird der Pfad zur Datei my-script.js aus /web/assets abgebildet und zwar nur aus dem Web-Verzeichnis!

Das Betriebssystem-Problem

Das Problem an der ganzen Verwaltung ist allerdings das Betriebssystem, auf dem gearbeitet wird: Da das eigentliche Root-Verzeichnis der öffentliche Ordner /web ist, müssen vor dem Deployen alle Dependencies in dieses Verzeichnis kopiert werden.

Hiefür bietet Symfony Hilfe über die Konsole: php bin/console assets:install erstellt eine Kopie aller sich in /{MyBundle}/Resources/public befindlichen Dateien in den Web-Ordner.

Je nach Betriebssystem ist die Vorgehensweise aber anders, denn wo Linux-basierte Systeme einen Symlink erstellen, kopiert Windows stupide alle Dateien von A nach B.

Im Endeffekt bedeutet dies, dass man auf einer Windows-Maschine nach jeder Veränderung des Public-Verzeichnisses den Konsolenbefehlt erneut ausführen müsste, um das Projekt auf den neues Stand zu bringen. Via {{ asset() }} können dann auch nur die kopierten Dateien in /web adressiert werden. Das ist nervig, sorgt bei Vergessen für unnötige Frustration und kostet viel Zeit!

Lösungen

  • Für wirklich große Projekte, bei denen die Ordnung eine große Rolle spielt, oder Entwicklungen mit vielen Bundles bleibt nur der Umstieg auf Linux.
  • „Kleinere“ Projekte sollten alle Abhängigkeiten einfach direkt in /web aufbewahren.
  • Man könnte einen Grunt-Service schreiben, der die nötigen Aktualisierungen auf Windows automatisch nach /web bringt.

Der Profiler und Dumps

Gleich zu Beginn eines neuen Projektes, fällt die dunkle Leiste am unteren Bildschirmrand ins Auge. Das ist der Symfony-Profiler.
Die Entwicklungshilfe erscheint standardmäßig nur im Environment dev und bei entsprechender Aktivierung der Konfiguration in der /app/config/config_dev.yml:

web_profiler:
    toolbar: true
    intercept_redirects: false

Die Konfiguration kann natürlich auch auf andere Environments kopiert werden. Eine weitere Voraussetzung für das Erscheinen des Profilers, ist das Vorhandensein einer Standard-HTML-Syntax:

<html>
    <head></head>
    <body></body>
</html>

Was macht diese Leiste?

Zuerst einmal liefert sie Informationen, die besonders in der Entwicklungsphase Vorteile bringen. Dazu gehört die aktuelle Position, die Herkunft sowie der Status.

Symfony 3 Workshop: Profiler HTTP Tab

Je nach Konfiguration können zusätzlich Informationen zu Übersetzung, angemeldete Benutzer oder Datenbankstatistiken angezeigt werden.

Symfony 3 Workshop: Profiler Database Tab

Zuletzt gibt Symfony auch Auskunft über seinen aktuellen Zustand, der verwendeten PHP-Version und dem Rootverzeichnis für die Symfony-App. Weitere Vorteile: Der aktuelle Token sowie eine phpinfo() und die Symfony-Dokumentation sind verlinkt.

Symfony 3 Workshop: Profiler PHP Info

Ein Klick auf die Leiste oder das Token öffnen den eigentlichen Profiler. Hier wird eine noch detailliertere Übersicht aller Schritte geboten, denn Symfony speichert jeden Seitenaufbau mit einem eigenen Token.

Symfony 3 Workshop: Profiler Details

Zu finden sind unter anderem: Der Status von Request und Response (mit übergebenen Variablen), Header-Informationen, der HTTP-Status, Cookies, eine Performance-Analyse, Einsicht in die Logfiles, getriggerte Events, Dumps und Informationen zu aktuell verwendeten Paketen.
Am besten, man verschafft sich selbst einen Überblick!

Einen Dump ausgeben

Auch bei der Arbeit mit PHP muss hin und wieder auf den Inhalt einer Variable zugegriffen werden, um den Ursprung eines Fehlers zu finden und zu beheben. Für komplexere Variablen ist dies zumeist durch var_dump() oder print_r() gelungen. Diese existieren zwar auch in Symfony, jedoch stellt das Framework eine verbesserte Version zur Verfügung: Den dump().

Der Dump kann in jeder Situation verwendet werden und dabei jede nur mögliche Variable analysieren (auch komplexe Objekte). In PHP einfach dump($myVar); oder in Twig {{ dump(myVar) }} eingeben erzeugt solch eine Ausgabe:

Symfony 3 Workshop: Dump einer Entität

Im oberen Beispiel steht ein „-“ für private und ein „+“ für public. Ein „#“ steht für eine Funktion, wobei nicht alle Funktionen beachtet werden. Enthaltene komplexe Objekte können zur Laufzeit „ausgeklappt“ werden.
Zusätzlich werden in Strings auch „\n“ und weitere Steuerzeichen betrachtet und Werte wie null und booleanische Werte angezeigt, die z.B. in print_r() ignoriert würden.

Das Routing in Symfony

Das Routing ist eine Tool um URLs schöner und lesbarer zu machen. Statt index.php?article_id=57 lässt sich ein /read/intro-to-symfony zudem viel einfacher merken! Um das Routing in einem Symfony-Projekt zu realisieren, können 4 verschiedene Formate genutzt werden: Annotationen, YAML, XML und PHP. Die Entscheidung liegt beim Entwickler. Die Vorgehensweise sollte jedoch über die komplette Arbeitszeit konstant bleiben.

Für folgende Beispiele wird mit Annotationen gearbeitet. Diese haben vor allem den Vorteil, dass keine zusätzliche Datei justiert werden muss und Verlinkungen direkt einer Funktion zugeordnet werden können.

Das Routing wird durch das Paket Sensio\Bundle\FrameworkExtraBundle\Configuration realisiert und über Annotationen mit @Route("/my/route") für Funktionen und Klassen definiert. Der erste Parameter definiert die Query in der Adresszeile, alle anderen Parameter sind optional. Zusätzlich sollte allerdings immer der Parameter „name“ gesetzt werden, da dieser eine eineindeutige Zuordnung innerhalb von Symfony ermöglicht!

Statische Verlinkung

/**
 * @Route("/blog", name="blog_list")
 */
public function listAction()
{
    // ...
}

Dynamische Verlinkung

/**
 * @Route("/blog/{slug}", name="blog_show")
 */
public function showAction($slug)
{
    // $slug ist der dynamische Teil der URL und wird als Variable übergeben
    // z.B. ergibt /blog/yay-routing für $slug='yay-routing'
}

Hinweis: Die Reihenfolge spielt eine Rolle! Egal welches Format genutzt wird, Symfony identifiziert die Route durch eine Top -> Bottom Suche. Steht demnach eine dynamische Route über einer statischen, kann die statische u.U. niemals erreicht werden.

Verlinkung mit Abhängigkeiten

/**
 * @Route("/articles/{_locale}/{year}/{slug}.{_format}",
 *     defaults={"_format": "html"},
 *     requirements={
 *         "_locale": "en|fr",
 *         "_format": "html|rss",
 *         "year": "\d+"
 *     }
 * )
 */
public function showAction($_locale, $year, $slug)
{
    // ...
}

Die Wildcards in einer @Route können zusätzlich mit „requirements“ beschränkt werden, um sie nur für bestimmte Eingaben zulässig zu machen. Über „defaults“ werden Standardwerte gesetzt, falls für diese Parameter kein Wert angegeben wurde.

Methods und Security

Es können noch weitere Beschränkungen außerhalb der @Route definiert werden: Via @Method() können nur bestimmte HTTP-Methoden („POST“, „GET“, „PUT“ und/oder „DELETE“) zugelassen werden. Via @Security() können nur bestimmte Nutzer („has_role(‚ROLE_ADMIN‘)“) Zugriff auf diesen Link bekommen.

Formulare und Form-Theming

Eine der Kernkomponenten von Websites sind die Formulare. Und natürlich hat auch Symfony hierfür einen Workflow entworfen. Wird das Projekt zusätzlich mit Doctrine realisiert, bietet dies weitere Vorteile und vereinfacht das Datenmanagement enorm. Aber auch ohne ein ORM ist das Form-Tool äußerst mächtig.

Ein Formular ohne Doctrine

class SimpleForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstname', TextType::class, [
                'label' => 'Vorname',
                'attr' => [
                    'class' => 'form-control',
                ],
            ])
            ->add('lastname', TextType::class, [
                'label' => 'Nachname',
                'attr' => [
                    'class' => 'form-control',
                ],
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Senden',
                'attr' => [
                    'class' => 'btn btn-primary',
                ],
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => null,
        ]);
    }
}

Diese Klasse befindet sich unter src/{BundleName}/Form/ und muss auch dem Suffix „Form“ enden. Voraussetzung sind die beiden Methoden buildForm(FormBuilderInterface $builder, array $options), die die Zusammensetzung des Formulars beschreibt und configureOptions(OptionsResolver $resolver), mit der externe sowie Default-Variablen definiert werden können. In letzterer muss das Attribut data_class immer gesetzt werden, notfalls auf null.

Das Formular einbetten

Um das Formular zu nutzen, muss es in einem Controller aufgerufen und in einem Twig-Template dargestellt werden. Eine einfach Standard-Routine könnte so aussehen:

/**
 * @Route("/my-form", name="my_form")
 * @param Request $request
 * @return Response
 */
public function createAction(Request $request)
{
    // create a form
    $form = $this->createForm(MyForm::class, null);

    // handle request
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        // do something with this data            
        // like insert it into DB

        return $this->redirectToRoute('somewhere_else');
    }

    return $this->render(':forms:myFormTemplate.html.twig', [
        'form' => $form->createView(),
    ]);
}

Wie genau das Formular aufgebaut wird und in welcher Reihenfolge, wird in dem Twig-Template entschieden. Um Zugriff auf die einzelnen Komponenten der Form zu bekommen, gibt es mehrere Möglichkeiten. Die Dokumentation gibt einen detaillierten Einblick dazu. Ein sehr einfacher Weg besteht aus nur 3 Befehlen:

{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}

Formulare auf Basis von Objekten

Im oberen Beispiel ist noch relativ viel Arbeit nötig, um die Formulardaten entsprechend auszuwerten und gegebenenfalls zu verwenden. Viel einfach hingegen wird es, wenn man ein Objekt als Datenbasis angibt. Praktischerweise basieren Datenbankobjekte bei Doctrine ebenfalls auf Objektklassen. So lassen sich diese einfach kombinieren! Ein entsprechendes Objekt könnte so aussehen:

class User
{
    private $firstName;

    private $lastName;

    public function getFirstName()
    {
        return $this->firstName;
    }

    public function setFirstName($firstName)
    {
        $this->firstName = $firstName;
        return $this;
    }

    public function getLastName()
    {
        return $this->lastName;
    }

    public function setLastName($lastName)
    {
        $this->lastName= $lastName;
        return $this;
    }
}

Im Formular muss die dazu das Attribut data_class => User::class gesetzt werden (alternativ über den Namespace AppBundle\Entity\User). Im Controller ändert sich folgendes:

/**
 * @Route("/my-form", name="my_form")
 * @param Request $request
 * @return Response
 */
public function createAction(Request $request)
{
    // create the object
    $user = new User();

    // create a form
    $form = $this->createForm(MyForm::class, $user);

    // handle request
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $firstName = $user->getFirstName();

        // do something with this data            
        // like insert it into DB

        return $this->redirectToRoute('somewhere_else');
    }

    return $this->render(':forms:myFormTemplate.html.twig', [
        'form' => $form->createView(),
    ]);
}

Die Formulardaten werden hier automatisch auf das Objekt abgebildet. Hält sich das Objekt wiederum an die Doctrine Konventionen, kann es auch direkt in die Datenbank geschrieben werden.

Formulare anpassen

Formulare zu bauen und die Daten zu verwalten ist relativ einfach. Komplizierter hingegen ist es, Formulare im Twig-Template auch optisch hochwertig aussehen zu lassen. Aber auch hierfür hält Symfony etwas parat: Das Form-Theming.

Formulare werden standardmäßig aus Twig-Blöcken zusammengesetzt. Diese sind definiert in einem Template namens form_div_layout.html.twig, zu finden unter vendor/symfony/symfony/src/symfony/Bridge/Twig/Resources/views/Form/. Und genau diese Blöcke können überschrieben werden.

Jedes Item in einem Formular entspricht einer form_row. Verändert man also nichts, verpackt Symfony jedes Item in einen leeren Div, da der Block dazu wie folgt aussieht:

{%- block form_row -%}
    <div>
        {{- form_label(form) -}}
        {{- form_errors(form) -}}
        {{- form_widget(form) -}}
    </div>
{%- endblock form_row -%}

Möchte man diesen überflüssigen Div loswerden, wird der Block einfach überschrieben. Zusätzlich muss dem Template die Information übermittelt werden, dass der neue Block den Default-Block überschreiben soll:

{% form_theme form _self %}

{% block form_row %}
    {{- form_label(form) -}}
    {{- form_errors(form) -}}
    {{- form_widget(form) -}}
{% endblock %}


{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}

Form-Themes lassen sich auch in gesonderte Dateien auslagern und wiederverwenden. In diesem Fall wird das Flag _self durch die Datei ersetzt.

Requests, Responses und Sessions

Um eine Website im Browser darzustellen, schickt der Client einen Request an den Server, der mit einer Response antwortet. Diese Response enthält Daten, die nach den Berechnungen des Servers eine adäquate Antwort zum Request ergeben.

Symfony hat sich auch um diesen Prozess gekümmert und den Workflow dazu in Objekte verpackt. Sowohl Request, als auch Response enthalten Daten und Statusvariablen. Die meisten Variablen werden dabei aus den PHP-Internen (z.B. $_POST, $_GET, etc.) bezogen. Die Internen Statusvariablen sollten daher nicht mehr verwendet werden!

Zugriff auf den Request bekommt man innerhalb von Symfony fast überall. In einem Controller genügt es, ihn einfach in die Parameter einer Funktion aufzunehmen:

public function editAction($licenceID, Request $request)
{
    // do something
}

Dabei ist es egal, in welche Funktionen der Request eingefügt wird, wie viele Parameter bereits existieren und an welcher Stelle er in die Parameterliste eingefügt wird. Mit einem dump() erhält man folgende Ausgabe:

Symfony 3 Workshop: Request Dump

Der Eintrag „request“ hält dieselben Variablen wie die globale $_POST, in „query“ wird $_GET gespeichert, usw. Der Request hat zudem einen eigenen Eintrag für übersendete Parameter: In „attributes“ werden immer der aktuelle Controller, die dazugehörige Route und, wenn vorhanden, übersendete Routenparameter gespeichert. Routenparameter sind in den meisten Routingbeispielen als {slug} gekennzeichnet.

Um zum Beispiel an einen $_GET-Parameter zu gelangen, verwendet man:

$bar = $request->query->get('foo', 'default');

Exisitert ein ?foo=irgendwas in der Adresszeile, ist $bar = 'irgendwas'. Anderenfalls $bar = 'default'. Der Wert für „default“ ist beliebig, kann also auch durch ein Objekt repräsentiert werden.

Die Session

Was normalerweise in $_SESSION['foo'] gespeichert wurde, findet nun in einem Session-Objekt von Symfony Platz. Auch dieses Objekt ist eine Art persistenter Speicher bis zum Schließen des Browsers. Die Standard-Session kann nach belieben erweitert werden und befindet sich innerhalb des Requests. Außerdem können eigene Session Variablen hinzugefügt werden.

In der Session befinden sich bereits die Flash Messages. Das sind Nachrichten, die beim nächsten Aufruf einer Seite einmalig abgerufen werden. So lassen sich z.B. Erfolgs- und Fehlermeldungen nach einer Aktion managen.

Um an die Session heranzukommen verwendet man einfach $request->getSession() in PHP und {{ app.request.session }} in Twig.

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.