This tutorial has a new version, check it out!

Easy Edit Form

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

When you get to the form, it's completely blank. How could we add default data to the form?

Well, it turns out answering that question is exactly the same as answering the question

How do we create an edit form?

Let's tackle that.

In GenusAdminController, I'm going to be lazy: copy the entire newAction() and update the URL to /genus/{id}/edit. Give it a different route name: admin_genus_edit and call it editAction():

... lines 1 - 13
class GenusAdminController extends Controller
{
... lines 16 - 55
/**
* @Route("/genus/{id}/edit", name="admin_genus_edit")
*/
public function editAction(Request $request, Genus $genus)
{
... lines 61 - 79
}
}

Our first job should be to query for a Genus object. I'll be lazy again and just type-hint an argument with Genus:

... lines 1 - 4
use AppBundle\Entity\Genus;
... lines 6 - 8
use Symfony\Component\HttpFoundation\Request;
... lines 10 - 13
class GenusAdminController extends Controller
{
... lines 16 - 58
public function editAction(Request $request, Genus $genus)
{
... lines 61 - 79
}
}

Thanks to the param converter from SensioFrameworkExtraBundle, this will automatically query for Genus by using the {id} value.

Passing in Default Data

This form needs to be pre-filled with all of the data from the database. So again, how can I pass default data to a form? It's as simple as this: the second argument to createForm is the default data. Pass it the entire $genus object:

... lines 1 - 13
class GenusAdminController extends Controller
{
... lines 16 - 58
public function editAction(Request $request, Genus $genus)
{
$form = $this->createForm(GenusFormType::class, $genus);
... lines 62 - 79
}
}

Why the entire object? Because remember: our form is bound to the Genus class:

... lines 1 - 13
class GenusFormType extends AbstractType
{
... lines 16 - 42
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Genus'
]);
}
}

That means that its output will be a Genus object, but its input should also be a Genus object.

Behind the scenes, it will use the getter functions on Genus to pre-fill the form: like getName(). And everything else is exactly the same. Well, I'll tweak the flash message but you get the idea:

... lines 1 - 13
class GenusAdminController extends Controller
{
... lines 16 - 58
public function editAction(Request $request, Genus $genus)
{
... lines 61 - 64
if ($form->isSubmitted() && $form->isValid()) {
... lines 66 - 71
$this->addFlash('success', 'Genus updated!');
... lines 73 - 74
}
... lines 76 - 79
}
}

Rendering the Edit Form

Update the template to edit.html.twig:

... lines 1 - 13
class GenusAdminController extends Controller
{
... lines 16 - 58
public function editAction(Request $request, Genus $genus)
{
... lines 61 - 76
return $this->render('admin/genus/edit.html.twig', [
'genusForm' => $form->createView()
]);
}
}

I'm still feeling lazy, so I'll completely duplicate the new template and update the h1 to say "Edit Genus":

... lines 1 - 22
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Edit Genus</h1>
... lines 28 - 40
</div>
</div>
</div>
{% endblock %}

Don't worry, this duplication is temporary.

Finally, in the admin list template, I already have a spot ready for the edit link. Fill that in with path('admin_genus_edit') and pass it the single wildcard value: id: genus.id:

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
... lines 8 - 13
<table class="table table-striped">
... lines 15 - 19
{% for genus in genuses %}
<tr>
... lines 22 - 23
<td>
<a href="{{ path('admin_genus_edit', {'id': genus.id}) }}" class="btn btn-xs btn-success"><span class="fa fa-pencil"></span></a>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% endblock %}

LOVE it. Open up /admin/genus in your browser.

Ah good, an explosion - I felt like things were going too well today:

Method id for object Genus does not exist in list.html.twig at line 25.

So apparently I do not have a getId() function on Genus. Let's check it out. And indeed, when I created this class, I did not add a getter for ID. I'll use command+N, or the "Code"->"Generate" menu to add it:

... lines 1 - 12
class Genus
{
... lines 15 - 68
public function getId()
{
return $this->id;
}
... lines 73 - 148
}

All right. Let's try it again. Refresh. No errors!

Edit the first genus. Check that out: it completely pre-filled the form for us. In fact, check out this weird "TEST" text inside of funFact. That is left over from an earlier tutorial. I hacked in this TEST string temporarily in getFunFact() so we could play with markdown. This proves that the form is using the getter functions to pre-fill things.

So, that's really interesting but let's take it out:

... lines 1 - 12
class Genus
{
... lines 15 - 106
public function getFunFact()
{
return $this->funFact;
}
... lines 111 - 148
}

Refresh. Change the "Fun fact" to be even more exciting, hit enter, and there it is:

Genus updated - you are amazing!

Edit that Genus again: there's the new fun fact. This is a really cool thing about the form framework: the new and edit endpoints are identical. The only difference is that one is passed default data.

So this is great! Except for the template duplication. That's not great still.

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