This tutorial has a new version, check it out!

Sharing Form Templates with include()

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Adding edit was quick! But the entire template is now duplicated. This includes the code to render the form and the other blocks that include the needed CSS and JS files.

First, copy the form rendering code and move that into a new file: _form.html.twig:

{{ form_start(genusForm) }}
{{ form_row(genusForm.name) }}
{{ form_row(genusForm.subFamily) }}
{{ form_row(genusForm.speciesCount, {
'label': 'Number of Species'
}) }}
{{ form_row(genusForm.funFact) }}
{{ form_row(genusForm.isPublished) }}
{{ form_row(genusForm.firstDiscoveredAt) }}
<button type="submit" class="btn btn-primary" formnovalidate>Save</button>
{{ form_end(genusForm) }}

Paste it here.

In edit, just include that template: include('admin/genus/_form.html.twig'):

... lines 1 - 22
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Edit Genus</h1>
{{ include('admin/genus/_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}

Copy that, open new.html.twig, and paste it there:

... lines 1 - 22
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>New Genus</h1>
{{ include('admin/genus/_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}

Ok, I'm feeling better. Refresh now: everything still looks good.

And by the way, if there were any customizations you needed to make between new and edit, I would pass a variable in through the second argument of the include function and use that to control the differences.

Using a Form Layout

So let's fix the last problem: the duplicated block overrides.

To solve this, we'll need a shared layout between these two templates. Create a new file called formLayout.html.twig. This will just be used by these two templates.

Copy the extends code all the way through the javascripts block and delete it from edit.html.twig:

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Edit Genus</h1>
{{ include('admin/genus/_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}

Paste it in formLayout.html.twig:

{% extends 'base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.0/css/bootstrap-datepicker.css">
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.0/js/bootstrap-datepicker.min.js"></script>
<script>
jQuery(document).ready(function() {
$('.js-datepicker').datepicker({
format: 'yyyy-mm-dd'
});
});
</script>
{% endblock %}

So this template itself will extend base.html.twig, but not before adding some stylesheets and some JavaScripts. In edit, re-add the extends to use this template: admin/genus/formLayout.html.twig:

{% extends 'admin/genus/formLayout.html.twig' %}
... lines 2 - 14

Copy that, open new.html.twig and repeat: delete the javascripts and stylesheets and paste in the new extends:

{% extends 'admin/genus/formLayout.html.twig' %}
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>New Genus</h1>
{{ include('admin/genus/_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}

Try it! Cool! We're using our Twig tools to get rid of duplication!

A Word of Caution

Congrats team - that's it for our first form episode. You should feel dangerous. Most of the time, forms are easy, and amazing! They do a lot of work for you.

Let me give you one last word of warning: because this is how I see people get into trouble.

Right now, our form is bound to our entity and that makes this form super easy to use. But eventually, you'll need to build a form that does not look exactly like your entity: perhaps it has a few extra fields or is a combination of fields from several entities.

When you run into this: here's what I want you to do. Don't bind your form to your entity class. Instead, create a brand new class: I usually put these classes inside my Form directory. For example, GenusModel. This class will have the exact properties that your form needs.

Bind this class to your form and add all your validation rules like normal. After you submit your form, $form->getData() will return this other object. Then, it'll be your job to write a little bit of extra code that reads this data, updates your entities - or whatever else you need that data for - and saves things.

If you have questions, let me know in the comments.

There's certainly more to learn, but don't wait! Get out there and build something crazy cool!

Seeya guys next time!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}