Allgemein Beitragsreihen PHP Tipps und Tricks Tutorials

Symfony 2.8 Teil 3 – Formulare

Willkommen zum nächsten Teil der Serie.

In diesem Teil lernst du, wie man Formulare einsetzt und die Daten verarbeitet – aufgrund des Umfangs etwas verspätet 🙂
Du wirst heute das Formular für das anlegen von einem Film sowie das bearbeiten von einem Film anlegen.
Außerdem wirst du die Links im Template dynamisch aufbauen, sodass keine Hart-Kodierten URLs im Template hinterlegt sind.

Rückblick

Im letzten Tutorial hast du das Basis-Template erstellt, dein erstes Bundle, den ersten Controller und deine erste Entity erstellt.
Bei der Entity habe ich einen kleinen Fehler gemacht den wir aber jetzt schnell korrigieren werden.
Öffne die DVD-Entity (src/DHDvdDbBundle/Entity/Dvd.php). Gehe nun in die Zeile in welcher die Eigenschaft imdb_id definiert wird. Ändere nun den Typ auf string und gib die Feldlänge mit 15 an. Nun sollte folgendes bei dir stehen:

    /**
     * @var string
     *
     * @ORM\Column(name="imdb_id", type="string", length=15)
     */
    private $imdbId;

Hintergrund: ich habe vergessen dass die IMDB ID mit tt beginnt und somit integer als Feld-Typ nicht passt.

Damit das Datenbankschema aktualisiert wird, musst du nun noch folgenden Befehl in der Konsole ausführen:
php app/console doctrine:schema:update --force

Das Formular

Formulare sind ein wichtiger Bestandteil von Anwendungen. Symfony bietet mittels dem FormBuilder einen sehr einfachen Weg diese zu erstellen und zu vearbeiten.

JavaScript-Datei für unsere Scripte anlegen

Damit du die JavaScripts nicht in das Template schreiben musst, solltest du dir eine Script-Datei (script.js) im Verzeichnis web/bundles/dvd-db/js/ anlegen.
Wie man diese nun ins Template einbindet erfährst du im nächsten Schritt.

Fülle die script.js nun mit folgendem Inhalt. Dieser dient später dazu, dass neue Schauspieler einem Film zugeordnet werden können.

$(document).ready(function(){
 
	$collectionHolder = $('div.acts');
 
	$collectionHolder.data('index', $collectionHolder.find(':input').length);
	$('#addActor').on('click', function(e){
		e.preventDefault();
		addActForm();
	})
 
	function addActForm() {
	    // Hole den Prototyp des Actor Feldes
	    var prototype = $collectionHolder.data('prototype');
 
	    // Hole den nächsten Index
	    var index = $collectionHolder.data('index');
 
	    // Ersetze __name__ durch den Index im Prototype
	    var newForm = prototype.replace(/__name__/g, index);
 
	    // Erhöhe den Index
	    $collectionHolder.data('index', index + 1);
 
	    // Füge das Textfeld nun dem Quelltext hinzu
	    var $newFormLi = $('<div class="form-group">').append(newForm);
	    $('div.acts').append($newFormLi);
	}
})

Template erweitern

views/base.html.twig

Damit du später auch auf das Formular zum erstellen eines Films kommst, musst du die Verlinkung im Template hinzufügen. Öffne hierzu die Datei base.html.twig welche wir im ersten Teil der Serie erstellt haben. Gehe nun in Zeile 31 in der du folgendes vorfinden solltest.

<li><a href="#">Film hinzufügen</a></li>

Ersetze die Raute durch folgenden Code:

{{ path('new_movie') }}

Damit du die Links innerhalb deines Systems nicht hart hinterlegen musst (/foo/bar/xyz.html), bietet TWIG eine Pfadauflösung welche die Pfade, welche in den Annotations per @Route("/foo/bar/", name="xyz") angegeben werden, auflöst.

Füge außerdem im block javascripts deine Scriptdatei hinzu:

<script src="{{ asset('bundles/dvd-db/js/script.js') }}" type="text/javascript"></script>

views/Movie/index.html.twig

Wie bereits im vorherigen Part erwähnt, soll auf der Startseite die Auflistung der hinterlegten Filme angezeigt werden. Aktuell ist es so dass immer der Text „Noch keine Filme vorhanden“ angezeigt wird. Das ist natürlich falsch, der Text soll nur angezeigt werden wenn tatsächlich keine Filme vorhanden sind. Daher musst du vor den DIV-Container mit der Klasse panel-body eine If-Abfrage hinzufügen, über der Table mit der Klasse table ein else und nach der table ein End if. Außerdem brauchen wir eine weitere Spalte Optionen:

{% 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>
				<th>Optionen</th>
			</tr>
		</thead>
	</table>
{% endif %}

Damit die Filme, sofern vorhanden, auch aufgelistet werden, muss nach dem schließenden thead-Tag ein tbody mit einer Schleife hinzugefügt werden:

<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>

views/Movie/new.html.twig

Erstelle nun für das Formular „Film hinzufügen“ die Datei views/Movie/new.html.twig mit folgendem Inhalt:

{% 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 %}

views/Movie/edit.html.twig

Damit der Film auch bearbeitet werden kann, brauchst du noch das Template views/Movie/edit.html.twig mit diesem Inhalt:

{% extends 'DHDvdDbBundle::base.html.twig' %}
{% block body %}
	<h2>Film bearbeiten</h2>
	{{ form_start(form) }}
		<div class="form-group">
			<label for="dvd_name">Name</label>
			{{ form_widget(form.name, { 'attr': {'class': 'form-control'} }) }}
		</div>
		<div class="form-group">
			<label for="dvd_language">Sprache</label>
			{{ form_widget(form.language, { 'attr': {'class': 'form-control'} }) }}
		</div>
		<div class="form-group">
			<label for="dvd_year">Jahr</label>
			{{ form_widget(form.year, { 'attr': {'class': 'form-control'} }) }}
		</div>
		<div class="form-group">
			<label for="dvd_imdbId">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 }}">
			{% for actor in form.actors %} 
				<div class="form-group">
					{{ form_widget(actor, { 'attr': {'class': 'form-control'} }) }}
				</div>			
			{% endfor %}
		</div>
		<button type="submit" class="btn btn-default">Speichern</button>
	{{ form_end(form) }}	
{% endblock %}

Keine Sorge, ich gehe im nächsten Part auf TWIG ein :).
20

Coden

DvdType erstellen

Damit ein Formular erstellt werden kann, benötigt man den FormBuilder. Dieser wird üblicherweise im EntityType für das Formular benutzt.
Um den DvdType zu erstellen, gibst du in der Console folgenden Befehl ein:
php app/console doctrine:generate:form DHDvdDbBundle:Dvd.

Als Bestätigung erhälst du die Nachricht „The new DvdType.php class file has been created under […] \dvd-db\src\DHDvdDbBundle/Form/DvdType.php.„. Öffne nun die Datei und ersetze in der Methode buildForm die Zeile ->add('actors') durch

->add('actors', CollectionType::class, array(
		'entry_type' => TextType::class,
		'allow_add' => true,
		'allow_delete' => true
))

und füge die Namespace-Abhängigkeiten hinzu sodass die Datei nun folgendermaßen aussieht:

<?php
 
namespace DHDvdDbBundle\Form;
 
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
 
class DvdType extends AbstractType
{
	/**
	 * @param FormBuilderInterface $builder
	 * @param array $options
	 */
	public function buildForm(FormBuilderInterface $builder, array $options)
	{
		$builder
			->add('name')
			->add('language')
			->add('actors', CollectionType::class, array(
			 		'entry_type' => TextType::class,
			 		'allow_add' => true,
			 		'allow_delete' => true
			 ))
			->add('year')
			->add('imdbId')
		;
	}
 
	/**
	 * @param OptionsResolver $resolver
	 */
	public function configureOptions(OptionsResolver $resolver)
	{
		$resolver->setDefaults(array(
			'data_class' => 'DHDvdDbBundle\Entity\Dvd'
		));
	}
}

Controller erweitern

So, da du nun den Type erstellt hast, kannst du nun mit der eigentlichen Logik anfangen.
Als erstes erstellst du im MovieController die Actions newAction und editAction mit den ersichtlichen Annotations:

 
    /**
     * @Route("/movie/add", name="new_movie")
     * @Template()
     */
    public function newAction(Request $request) {
 
    	return array();
    }
 
    /**
     * @Route("/movie/edit/{movie}", name="edit_movie")
     * @Template()
     */
    public function editAction(Request $request, $movie) {
 
    	return array();
    }

indexAction

Die indexAction kannst du nun mit folgendem Code füllen und die Annotations aktualisieren:

    /**
     * @Route("/", name="home")
     * @Template()
     */
    public function indexAction()
    {
    	$em = $this->getDoctrine()->getManager()->getRepository('DHDvdDbBundle:Dvd');
    	$movies = $em->findAll();
 
        return array('movies' => $movies);
    }

Dieser Code dient dazu dass uns später die Filme im Template zur Verfügung stehen.

newAction

    /**
     * @Route("/movie/add", name="new_movie")
     * @Template()
     */
    public function newAction(Request $request) {
 
    	// Erstelle "dummy"-Dvd als Referenz
    	$dvd = new Dvd();
 
    	// Erstelle neues Form auf Grundlage des DvdTypes
    	$form = $this->createForm(DvdType::class, $dvd);
 
    	// Verarbeite Request (falls Formular abgesendet wurde)
    	$form->handleRequest($request);
 
    	// Wenn das Formular abgesendet und die Daten gültig sind ...
    	if ($form->isSubmitted() && $form->isValid()) {
 
    		// Hole den EntityManager 
    		$em = $this->getDoctrine()->getManager();
 
    		// Gib die Dvd an den EntityManager
    		$em->persist($dvd);
 
    		// Schreibe Dvd in die Datenbank
    		$em->flush();
 
    		// Und leite auf die Startseite weiter
    		return $this->redirectToRoute('home');
    	}
 
    	return array('form' => $form->createView());
    }

editAction

 
    /**
     * @Route("/movie/edit/{movie}", name="edit_movie")
     * @Template()
     */
    public function editAction(Request $request, $movie) {
 
    	// Hole den EntityManager
    	$em = $this->getDoctrine()->getManager();
 
    	// Hole das DVD Repository
    	$repository = $em->getRepository('DHDvdDbBundle:Dvd');
 
    	// Suche die DVD anhand der übergebenen ID
    	$dvd = $repository->findOneById($movie);
 
    	// Leite auf Startseite wenn die DVD nicht existiert
    	if(!$dvd) {    	
    		return $this->redirectToRoute('home');
    	}
 
    	// Erstelle neues Form auf Grundlage des DvdTypes
    	// Und der gefundenen DVD
    	$form = $this->createForm(DvdType::class, $dvd);
 
    	// Verarbeite Request (falls Formular abgesendet wurde)
    	$form->handleRequest($request);
 
    	// Wenn das Formular abgesendet und die Daten gültig sind ...
    	if ($form->isSubmitted() && $form->isValid()) {
 
    		// Gib die Dvd an den EntityManager
    		$em->persist($dvd);
 
    		// Aktualisere Dvd in der Datenbank
    		$em->flush();
 
    		// Und leite auf die Startseite weiter
    		return $this->redirectToRoute('home');
    	}
 
    	return array('form' => $form->createView());
    }

Abschluss

Wenn du alles richtig gemacht hast, kommst du nun über den Link „Film hinzufügen“ auf folgendes Formular kommen:
Formular Film erstellen

Nach dem speichern solltest du auf der Startseite diesen angezeigt bekommen:
Startseite

Und im Bearbeiten Formular solltest du dies sehen:
Bearbeiten Formular

Damit sind die Basisfunktionalitäten, außer dem löschen von Filmen / Schauspielern, sichergestellt. Im nächsten Part geht es erst einmal um TWIG. In diesem werde ich die bereits verwendeten Codes und die Funktionsweise von TWIG erklären.

Bis dahin, viel spaß beim experementieren 🙂

~Julian

Zurück zu „Teil 2 – Bundles, Controller und Entites“

Weiter zu „Teil 4 – TWIG“


0x gelesen

Print Friendly, PDF & Email

3 Kommentare

  1. Mir gefällt das Tutorial bisher sehr gut. Nur leider bekomme ich beim Klick auf „Film hinzufügen“ folgende Fehlermeldung:
    „Fatal error: Class ‚DHDvdDbBundle\Controller\Dvd‘ not found“

    Ich bin Symfonie-Neuling und kann den Fehler nicht finden. Wie sieht der Kopf-Part ihres MovieController’s aus?

    Vielen Dank im Voraus.

    1. Hallo Sebastian,

      Danke 🙂 Warscheinlich fehlen dir die Use-Statements, mein Kopfbereich sieht so aus:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      <?php
       
      namespace DHDvdDbBundleController;
       
      use SymfonyBundleFrameworkBundleControllerController;
      use SensioBundleFrameworkExtraBundleConfigurationRoute;
      use SensioBundleFrameworkExtraBundleConfigurationTemplate;
      use SymfonyComponentHttpFoundationRequest;
      use DHDvdDbBundleEntityDvd;use DHDvdDbBundleFormDvdType;
      1. Hallo Julian,

        danke Dir. Das hat mein Problem erledigt. Nun mal weiter mit TWIG. 😉
        Gerne mehr Symfony-Tutorials. Sehr gut geschrieben und leicht verständlich. 🙂

Kommentar hinterlassen

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