Allgemein

Symfony 2.8 Teil 4 – TWIG

TWIG Logo

Hallo 🙂
nachdem nun der Weihnachts- und Silvesterstress vorüber ist, hier der nächste Teil der Serie.

Heute erzähle ich dir mehr über TWIG und wir reflektieren die funktionalitäten in den erstellten Templates der DVD-Datenbank.

Was ist TWIG?

Wie bereits am Anfang der Serie erwähnt, handelt es sich bei TWIG um die Template-Engine aus dem Hause SensioLabs. TWIG ist Standartmäßig in Symfony enthalten, kann jedoch aber auch Standalone heruntergeladen und genutzt werden (Projektseite).

Was sind die Aufgaben einer Template Engine?

Eine Template Engine trägt erheblich zu der Atomisierung der Software bei. Hierbei wird die eigentliche HTML-Ausgabe in separate Dateien (=Templates) ausgelagert und mit Platzhalter versehen.
Dies hat den Vorteil dass die eigentliche Logik in Controllern geschieht und man sich bei Bugs/Erweiterungen nicht durch den HTML-PHP-Misch-masch wühlen muss.

Als Beispiel:
Pures PHP mit HTML:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
    echo '<!DOCTYPE HTML>';
    echo '<html>';
    echo '  <head>';
    echo '      <title>'.getTitle().' - Mein Projekt</title>';
    echo '  </head>';
    echo '  <body>';
    echo '      <h1>Mein Projekt</h1>';
    echo '      <p>Hallo '.$user->getUsername().'</p>';
    echo '  </body>';
    echo '</head>';
?>

Mit Twig würde das folgendermaßen aussehen

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE HTML>
<html>
    <head>
        <title>{% block title %}Mein Projekt{% endblock %}</title>
    </head>
    <body>
        {% block body %}
            <h1>Mein Projekt</h1>
            <p>Hallo {{ user.username }}</p>
        {% endblock %}
    </body>
</html>

Wie funktioniert TWIG?

Im Grunde gibt es zwei logische Begrenzer: {{ ... }} und {% ... %}
{{ ... }} dient zum ausgeben von Variablen etc. innerhalb des Templates.
{% ... %} wird zum ausführen von Kontrollstrukturen wie z.B. einer If-Abfrage oder einer For-Schleife verwendet.

Desweiteren gibt es noch {# ... #} für Kommentare.

Filter

Bei der Verwendung der Begrenzer kann man so genannte Filter benutzen, welche das Verhalten beinflussen. Diese Filter werden hinter der Variable mit | angegeben.
Als Beispiel:
TWIG gibt standardmäßig alle Variablen escaped aus. Soll so zum Beispiel HTML ausgegeben werden, würde lediglich der HTML-Code angezeigt werden. Damit dies nicht passiert, verwendet man den raw-Filter:
{{ page.content | raw }}

Rückblick auf die Templates

Wie erwähnt, wollen wir uns heute ansehen, was wir in unseren Template gemacht haben, los gehts.

views/base.html.twig

Deine base.html.twig sollte aktuell folgenden Inhalt aufweisen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset={{ _charset }}" />
    <meta name="robots" content="noindex,nofollow" />
    <title>{% block title %}DVD-Datenbank{% endblock %}</title>
    <link rel="stylesheet" href="{{ asset('bundles/twitter/bootstrap/css/bootstrap.min.css') }}" />
    <link rel="stylesheet" href="{{ asset('bundles/twitter/bootstrap/css/bootstrap-theme.min.css') }}" />
    <link rel="stylesheet" href="{{ asset('bundles/dvd-db/css/main.css') }}" />
    {% block head %}{% endblock %}
</head>
<body role="document">
 
<!-- navbar -->
<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed"
                    data-toggle="collapse" data-target="#menu-toggle">
                <span class="sr-only"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">DVD-Datenbank</a>
        </div>
 
        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="menu-toggle">
            <ul class="nav navbar-nav">
                <li><a href="{{ path('new_movie') }}">Film hinzufügen</a></li>
            </ul>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container-fluid -->
</nav>
<!-- /navbar -->
 
<div class='container'>
    {% block body %}{% endblock %}
</div>
 
{% block javascripts %}
    <script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
    <script src="{{ asset('bundles/twitter/bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script>
    <script src="{{ asset('bundles/dvd-db/js/script.js') }}" type="text/javascript"></script>
{% endblock %}
 
</body>
 
</html>

Zur Übersichtlichkeit habe ich die Zeilen farbig hervorgehoben welche ich nun erläutere.

Zeile 4 – {{ _charset }}

In _charset schreibt Symfony das verwendete Zeichenencoding welches von der Methode getCharset() aus dem App-Kernel kommt.

Zeile 6 – {% block title %}DVD-Datenbank{% endblock %}

Blocks sind Bereiche welche von Child-Templates (dazu komme ich gleich) überschrieben/erweitert werden können. DVD-Datenbank ist hierbei der Standartwert falls dieser Block nicht überschrieben wird.

Zeile 7-9 – {{ asset('...') }}

Mit dem asset-Befehl werden Pfade innerhalb des Web-Verzeichnis aufgelöst. Dies ist bspw. für CSS- und JS-Dateien notwendig da diese nicht von Symfony geroutet werden.

Zeile 10 – {% block head %}{% endblock %}

Mit diesem Block kann der Head-Bereich später, falls notwendig, erweitert werden.

Zeile 31 – {{ path('...') }}

Mittels der path-Funktion können die Routen für die Actions automatisch ausgelesen werden. Hilfreich ist dies besonders dann, wenn sich eine Route ändert. Denn dann muss mann tatsächlich nur die Route und nichts in den Templates verändern. Als Parameter wird der Name der Route erwartet, welcher in unserem Fall in den Annotations festgelegt ist. (@Route("...", name="new_movie"))

Zeile 41 – {% block body %}{% endblock %}

Dieser Block wird von den Child-Templates überschrieben. Hierbei handelt es sich um den eigentlichen Seiteninhalt da sich dieser in der Regel pro Seite unterscheiden kann.

Zeile 44-48

In diesem Block werden die JavaScripts angegeben.

views/Movie/index.html.twig

Die Datei index.html.twig sollte bei euch wie folgt aussehen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{% extends 'DHDvdDbBundle::base.html.twig' %}
{% block body %}
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-default">
                <div class="panel-heading">Suche</div>
                <div class="panel-body">
                    <div class="input-group">
                        <input type="text" class="form-control">
                        <div class="input-group-btn">
                            <button class="btn btn-default" type="button">Go!</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-default">
                <div class="panel-heading">Filme</div>
                {% if movies == false %}
                    <div class="panel-body">
                        <p>Noch keine Filme vorhanden</p>
                    </div>
                {% else %}
                    <table class="table">
                        <thead>
                        <tr>
                            <th>Name</th>
                            <th>Sprache</th>
                            <th>Schauspieler</th>
                            <th>Jahr</th>
                            <th>IMDB ID</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for movie in movies %}
                            <tr>
                                <td>{{ movie.name }}</td>
                                <td>{{ movie.language }}</td>
                                <td>{{ movie.actors | join(', ') }}</td>
                                <td>{{ movie.year | default('-')}}</td>
                                <td>{{ '<a target="_BLANK" href="http://www.imdb.com/title/%s/">%s</a>'|format(movie.imdbid, movie.imdbid) | raw }}</td>
                                <td>
                                    <a href="{{ path('edit_movie', {'movie': movie.id}) }}"><i class="glyphicon glyphicon-pencil"></i></a>
                                </td>
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                {% endif %}
            </div>
        </div>
    </div>
{% endblock %}

Zeile 1 – {% extends 'DHDvdDbBundle::base.html.twig' %}

Diese Zeile sagt dem Template dass es sich um ein Child-Template handelt, und als Parent-Template die Datei base.html.twig im DHDvdDbBundle aus dem resources/views/ Verzeichnis verwendet werden soll.

Zeile 2 – {% block body %}

Da dieser Block im Parent-Template vorhanden ist, sagen wir hiermit TWIG das der Block body aus dem Parent Template überschrieben werden soll.
Wichtig: Die Blockbezeichnungen dürfen pro Template-Datei nur einmal vergeben werden!

Zeile 22, 26 und 52

Mit {% if movies == false %} wird geprüft ob noch keine Filme vorhanden sind und zeigt dann entsprechend den folgenden Quellcode bis zum else bzw. ohne else bis zum endif.
Wenn bereits Filme vorhanden sind, tritt der {% else %}-Fall ein, und somit wird die Filmliste angezeigt.
Mit {% endif %} beenden wir die Kontrollstruktur.

Zeile 38-49

In den Zeilen 38-49 wird die Filmliste aufgebaut. Mittels {% for movie in movies %} fangen wir an alle Filme durchzugehen.
{{ movie.* }} liefert uns die jeweilige Eigenschaft der Dvd-Entity des aktuellen Durchgangs. Mit dem join()-Filter in {{ movie.actors | join(', ') }} sagen wir TWIG dass die Darsteller Komma getrennt ausgegeben werden sollen, da es sich bei dieser Variable um ein Array handelt. Der default()-Filter ermöglicht das definieren eines Standart-Wertes falls dieser leer ist. Die Funktionsweise des format(…)-Filters ist mit der PHP-Funktion sprintf() zu vergleichen.

Path mit Parametern: Für die Bearbeiten-Route benötigen wir einen Parameter. Daher wird dieser in Path mit übergeben: {{ path('edit_movie', {'movie': movie.id}) }}. Die Reihenfolge der Parameter spielt keine Rolle da diese anhand des Namens abgeglichen werden.

Mit dem abschließenden {% endfor %} beenden wir unsere Schleife.

views/Movie/new.html.twig

Zum Abschluss noch die new.html.twig welche das Formular zum hinzufügen eines Films dient.
Da die edit.html.twig sich nicht viel von der new.html.twig unterscheidet, werde ich diese nicht separat hier auflisten.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{% extends 'DHDvdDbBundle::base.html.twig' %}
{% block body %}
    <h2>Film hinzufügen</h2>
    {{ form_start(form) }}
        <div class="form-group">
            <label for="form_name">Name</label>
            {{ form_widget(form.name, { 'attr': {'class': 'form-control'} }) }}
        </div>
        <div class="form-group">
            <label for="form_language">Sprache</label>
            {{ form_widget(form.language, { 'attr': {'class': 'form-control'} }) }}
        </div>
        <div class="form-group">
            <label for="form_year">Jahr</label>
            {{ form_widget(form.year, { 'attr': {'class': 'form-control'} }) }}
        </div>
        <div class="form-group">
            <label for="form_imdb_id">IMDB ID</label>
            {{ form_widget(form.imdbId, { 'attr': {'class': 'form-control'} }) }}
        </div>
        <div class="form-group">
            <label>Schauspieler</label>
            <a href="javascript:void(0)" class="btn btn-primary pull-right" ID="addActor"><i class="glyphicon glyphicon-plus"></i></a>
        </div>
        <div class="acts" data-prototype="{{ form_widget(form.actors.vars.prototype, { 'attr': {'class': 'form-control'} })|e }}">
            {% if form.actors|length > 0 %}
                {% for actor in form.actors %}
                    <div class="form-group">
                        {{ form_widget(actor, { 'attr': {'class': 'form-control'} }) }}
                    </div>
                {% endfor %}
            {% else %}
                {{ form_widget(form.actors) }}
            {% endif %}
        </div>
        <button type="submit" class="btn btn-default">Speichern</button>
    {{ form_end(form) }}
{% endblock %}

Zeile 3 – {{ form_start(form) }}

Mit dem Befehl form_start(form_name) sagen wir TWIG das ab hier ein Formular beginnt. form_name entspricht hierbei dem Namen des Formulars aus dem jeweiligen Controller, daher bei uns form:

Spoiler Inside: Ausschnitt aus MovieController.phpSelectShow

Zeile 7, 11, 15, 19 – {{ form_widget(...) }}

form_widget(view, variables) dient zum darstellen eines Eingabefeldes. Durch den Datentyp der „view“ wird hier automatisch ein passendes Feld ausgewählt. In „variables“ können verschiedene Eigenschaften des Eingabefeldes wie z.B. die CSS-Klasse manipuliert werden.

Hinweis: Falls nicht alle Felder des Formulars mittels form_widget() integriert wurden, fügt TWIG die fehlenden Felder automatisch hinzu.

Zeile 25 – data-prototype="..."

Da auch die Möglichkeit existiert, für einen Film Schauspieler zu hinterlegen, brauchen wir pro Schauspieler ein Eingabefeld. Dieses wird per JavaScript nach einem klick auf den Plus-Button generiert.
Im data-prototype Attribute steht nichts anderes als HTML-Code für dieses Feld. Durch den e-Filter wird der HTML-Code enkodiert.

Zeile 26-31

In diesen Zeilen prüfen wir ob bereits Schauspieler vorhanden sind und stellen diese dann dar.

Zeile 32-34

Falls noch kein Schauspieler existiert soll das form_widget angezeigt werden damit später das Label für Schauspieler nicht doppelt angezeigt wird.

Zeile 38 – {{ form_end(form) }}

Mit form_end() wird das geöffnete Formular abgeschlossen und nicht integrierte Felder automatisch hinzugefügt.

Schlusswort

So, mal wieder am Ende angekommen 🙂
Heute gab es zwar nur Theorie, diese ist aber auch notwendig wenn man was lernen möchte 🙂
In dieser Tutorial-Serie werde ich nicht auf den kompletten TWIG-Befehlssatz eingehen, daher lohnt es sich in die Dokumentation zu schauen.

Nächster Part: Erstellung der Suchfunktion

Ich hoffe ich habe nichts vergessen und das es euch gefallen hat.
Kommentar + Daumen nicht vergessen 😉

~Julian

Zurück zu „Teil 3 – Formulare“


0x gelesen

Print Friendly, PDF & Email

8 Kommentare

  1. Hi Julian,

    hat alles prima geklappt (habe SF 3. auf debian, musste nur app/console gegen bin/console austauschen und in der composer.json

    autoload“: {
    „psr-4“: {
    „“: „src/“
    },
    „classmap“: [
    „app/AppKernel.php“,
    „app/AppCache.php“
    ]
    },

    setzen.
    Mal sehen ob ich die Suchfunktion selbst hin bekomme.

    Weiterhin viel Erfolg.

  2. Ich finds voll cool für einen ersten Kickstart 🙂 Hilfst mir ungemein (y), kleiner Input meinerseits: In der edit-View hast noch den gleichen Seitentitel wie in der new-View, hatte mich ganz kurz irritiert 😉

Kommentar hinterlassen

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