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
- Symfony installieren (Win + MAC + Linux)
- Symfony in PhpStorm einrichten
- Composer hinzufügen und Bundles installieren
- Das Dateisystem und Namenskonventionen
- Die Environments: dev, prod und test
- Die Konsole nutzen
- Dependencies verwalten: Windows vs. Unix
- Die Profiler-Debugging-Leiste
- Routing
- Formulare und Form-Theming
- Requests, Responses und Sessions
Weiteres
- Die Dokumentation
- Einen Twig-Service erstellen
- Einen eigenen Konsolenbefehl erstellen
- Symfony auf einen simplen FTP deployen
Symfony installieren
Voraussetzungen
- Windows, Mac oder eine Linux-Distribution als Betriebssystem
- PHP-CLI und ein Datenbankformat installiert (z.B. MySQL)
- Möglichst: PHP-intl Extension und ICU-Extension
- Möglichst: PhpStorm als IDE
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:
- Das Symfony Plugin
- PHP Annotations
- 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:
- Dem Composer das neue Paket hinzufügen: Der befehl folgt dem Schema
composer require "contributor/package-name:version"
und fügt dercomposer.json
die Zeile hinzu. Damit wird das Paket heruntergeladen und in dercomposer.json
als Abhängigkeit aufgeführt. - 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. - 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:
- Twig-Templates werden zuerst unter
/app/Resources/views
, Übersetzung-Dateien unterapp/Resources/translations
gesucht. Werden dort keine Matches gefunden, wird der Reihe nach jedes Bundle in der gleichen Weise unter/{MyBundle}/Resources
durchsucht. - Error-Seiten müssen immer unter
/app/Resources/TwigBundle/views/Exception
abgelegt werden. - 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.
Je nach Konfiguration können zusätzlich Informationen zu Übersetzung, angemeldete Benutzer oder Datenbankstatistiken angezeigt werden.
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.
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.
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:
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:
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.