Buy

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():

81 lines src/AppBundle/Controller/Admin/GenusAdminController.php
... 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:

81 lines src/AppBundle/Controller/Admin/GenusAdminController.php
... 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:

81 lines src/AppBundle/Controller/Admin/GenusAdminController.php
... 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:

50 lines src/AppBundle/Form/GenusFormType.php
... 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:

81 lines src/AppBundle/Controller/Admin/GenusAdminController.php
... 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:

81 lines src/AppBundle/Controller/Admin/GenusAdminController.php
... 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":

45 lines app/Resources/views/admin/genus/edit.html.twig
... 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:

33 lines app/Resources/views/admin/genus/list.html.twig
... 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:

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

Alright. 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:

150 lines src/AppBundle/Entity/Genus.php
... 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!

  • 2016-07-04 weaverryan

    Thanks for the nice words! And yes, we'll definitely do that - hopefully in the next couple of months - along with a more advanced form theming tutorial :)

    Cheers!

  • 2016-07-01 HelgeN

    Hi, i love these tutorials, but i was really hoping to see one about how to embed a collection of forms and dynamically adding and removing fields.

    any chance of getting a tutorial on this topic?

    cheers

  • 2016-06-24 Dominik

    Thank you so much for all the answers!

    Can't wait for another courses!

    Cheers!

  • 2016-06-24 Victor Bocharsky

    Hey, Dominik!

    Yes, you caught it right!

    Well, you can create a private method in the GenusAdminController to reuse it in other methods of this contoller. But! What if you need the same method in another controller? You got the idea? In this case better to move this reusable method to a base controller, and then extend it by whatever controller where you need this. Of course, if you have only one contoller in whole project - it looks pointless, but on practice, you probable have many of them, where you need to reuse some common logic between them.

    Cheers!

  • 2016-06-24 Dominik

    Soo....
    I have to add BaseController which extends Controller (symfony one) and then GenusAdminController extends the BaseController, right?

    But how about this solution:
    create private method inside a GenusAdminController:

    public function generateAdminForm($action = null) {
    //code here
    //$action will take care for addFlash (we got 2 different massage).
    if ($action){
    flash for new

    } else {
    flahs for edit

    }

    }

    Your second solution - it's too high lvl for me right now.

  • 2016-06-24 Victor Bocharsky

    Yes, exactly! We'll close this security hole in the next episode! Keep learning! ;)

    And by the way, that was a good point!

    Cheers!

  • 2016-06-24 Dominik

    Hello!

    Thank you for explain this. I think its necessary because in next course where're going to do some authorization, right?

  • 2016-06-24 weaverryan

    I'm glad you dislike the duplication actually :). There are 2 basic approaches to this:

    A) Create your own BaseController class (that itself extends Symfony's Controller) and add any helper methods that you want inside of there. You can add whatever methods you want to cut-down on the boilerplate of the forms code.

    B) Do the same basic thing, but put the code in some outside service. These are often called "Form handlers" - I don't do this approach, but a lot of people do.

    Cheers!

  • 2016-06-23 Victor Bocharsky

    Hi, Dominik!

    That's because we have a "/admin" prefix for whole GenusAdminController:

    /**
    * @Route("/admin")
    */
    class GenusAdminController extends Controller

    Cheers!

  • 2016-06-23 Dominik

    And i got another question:
    we got duplicated code in controller. Can we do sth, about it? It's totally awful.
    In next chapter we learn about do sth about this in .twig, not in php files.

  • 2016-06-23 Dominik

    Hello!
    "LOVE it. Open up /admin/genus in your browser."
    U mean /genus probably, coz in GenusAdminController there is

    * @Route("/genus", name="admin_genus_list")