Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Code Generation FTW!

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

I feel like making someone else do some work for awhile, so let's look at Symfony's code generation tools.

The first thing we need our app to do is let users create, view, update and delete events. In other words, we need a CRUD for the Event entity.

Want to use Doctrine to generate a CRUD? Yea, there's a console command for that doctrine:generate:crud:

php app/console doctrine:generate:crud

This inquisitive command first wants to know which entity we need a CRUD for. Answer with that shortcut entity "alias" name we've been seeing: EventBundle:Event.

Say "yes" to the "write" actions, yml for the configuration format, and use the default /event route prefix. Then finish up.

Routing Imports and Organization

Ah crap! Red errors! It's ok, copy the code into the routing.yml of our EventBundle.

# src/Yoda/EventBundle/Resources/config/routing.yml
# ...

# copied in from the commands output
EventBundle_event:
    resource: "@EventBundle/Resources/config/routing/event.yml"
    prefix:   /event

The generation tasks tried to put this in there for us, but we already had something in this file so it panicked. All better now.

We now know this is a routing import, which loads a brand new event.yml file:

# src/Yoda/EventBundle/Resources/config/routing/event.yml
event:
    pattern:  /
    defaults: { _controller: "EventBundle:Event:index" }

event_show:
    pattern:  /{id}/show
    defaults: { _controller: "EventBundle:Event:show" }

# ... more routes

Let's run the router:debug command to make sure these are being loaded:

php app/console router:debug
event               ANY     /event/
event_show          ANY     /event/{id}/show
event_new           ANY     /event/new
event_create        POST    /event/create
event_edit          ANY     /event/{id}/edit
event_update        POST    /event/{id}/update
event_delete        POST    /event/{id}/delete

Check out the main app/config/routing.yml file - it's still only importing the one file from the EventBundle:

# app/config/routing.yml
event:
    resource: "@EventBundle/Resources/config/routing.yml"
    prefix:   /

But once we're in that file, we're of course free to organize routes into even more files and import those. That's what's happening with event.yml: it holds all the routes for the new EventController. Don't go crazy, but when you have a lot of routes, splitting them into multiple files is a good way to keep things sane.

Oh, and when we import another file, the key - like EventBundle_event - is completely meaningless - make it whatever you want. But, the key for an actual route is important: it becomes its internal "name". We'll use it later when we generate links.

Checking out the Generated Code

Enough with routing! Head to the /event page to see this in action. I know we got Apache setup in the last chapter, but I'm going to continue using the built-in PHP web server and access the site at localhost:8000:

http://localhost:8000/app_dev.php/event

Woh, that's ugly. Hmm, but it does work - we can add, view, update and delete events. Easy!

Let's peek at some of the code. The generated controller is like a cheatsheet for how to do common things, like form processing, deleting entities, redirecting and showing a 404 page.

For example, showAction uses the id from its route to query for an Event object. If one isn't found, it sends the user to a 404 page by calling createNotFoundException and throwing the result. This helper function is just a shortcut to create a very specific type of Exception object that causes a 404 page:

// src/Yoda/EventBundle/Controller/Event.php
// ...

public function showAction($id)
{
    $em = $this->getDoctrine()->getManager();
    
    $entity = $em->getRepository('EventBundle:Event')->find($id);

    if (!$entity) {
        throw $this->createNotFoundException('No event with id '.$id);
    }

    // ...
    return $this->render('EventBundle:Event:show.html.twig', array(
        'entity' => $entity,
        'delete_form' => $deleteForm->createView(),
    ));
}

If we do find an Event, it's passed to the template and rendered. Take a few minutes to look through the other parts of the controller. I mean it!

Making the Generated Code Less Ugly

I know this all works, but the ugly is killing me. I created a custom version of each of the CRUD template files while you were looking through the controller. You can find these in the resources directory of the code download for this screencast. I already moved that directory from the code download into my project.

cp resources/Event/* src/Yoda/EventBundle/Resources/views/Event/

The 3-template Inheritance System

If you think that the new template files probably extend a layout file, gold star! But I can't make it that easy. Instead of extending the ::base.html.twig file we're familiar with, each extends EventBundle::layout.html.twig:

{# src/Yoda/EventBundle/Resources/views/Event/index.html.twig #}
{% extends 'EventBundle::layout.html.twig' %}

...

Let's create this template. The middle piece of the 3-part template syntax is missing, which tells us that this will live directly in the Resources/views directory of our bundle, and not in a sub-directory:

{# src/Yoda/EventBundle/Resources/views/layout.html.twig #}

Create this file... but nothing here yet...

Inside the new template, simply extend ::base.html.twig:

{# src/Yoda/EventBundle/Resources/views/layout.html.twig #}
{% extends '::base.html.twig' %}

Now we have a template hierarchy - index.html.twig extends layout.html.twig, which extends base.html.twig.

Tip

If you try the new templates out, and your browser shows the old ones, try clearing out your cache (php app/console cache:clear) - this could be a rare time when Symfony doesn't rebuild the cache correctly.

This is awesome because all the new templates extend layout.html.twig. So if we want to override a block for all of our event pages, we can do that right here.

Let's try it: set the title block to "Events":

{# src/Yoda/EventBundle/Resources/views/layout.html.twig #}
{% extends '::base.html.twig' %}

{% block title 'Events' %}

Now we have a better default page title for every event page. Of course, we can still override the title block in any child template. Template inheritance, you're awesome.

This 3-level inheritance is definitely not required, keep things simple if you can. But if you have many slightly different sections on your site, it might be perfect.

Route Prefix

Look back at the routing.yml file in our bundle. You're smart, so you probably already saw the prefix key and guessed that this prefixes all the imported route URLs with /event:

{# src/Yoda/EventBundle/Resources/config/routing.yml #}
{# ... #}

EventBundle_event:
    resource: "@EventBundle/Resources/config/routing/event.yml"
    prefix:   /event

This is a nice little feature. Now kill it!

{# src/Yoda/EventBundle/Resources/config/routing.yml #}
{# ... #}

EventBundle_event:
    resource: "@EventBundle/Resources/config/routing/event.yml"
    prefix:   /

With this gone, the events will show up on the homepage. Remove the /event from the URL in your browser to see it:

http://localhost:8000/app_dev.php

Leave a comment!

8
Login or Register to join the conversation
Default user avatar

Great tutorial! Thank You!
I have two problems.

This is a code from this lesson:

// src/Yoda/EventBundle/Controller/Event.php
// ...

public function showAction($id)
{
$em = $this->getDoctrine()->getManager();

$entity = $em->getRepository('EventBundle:Event')->find($id);

if (!$entity) {
throw $this->createNotFoundException('No event with id '.$id);
}

// ...
return $this->render('EventBundle:Event:index.html.twig', array(
'event' => $event,
'delete_form' => $deleteForm->createView(),
));
}

In your code, EventController actions don't send $entities in its responses. Instead they send $event. But there are no $event in the views. Is this a mistake or I don't understand something? Because the code from this lesson doesn't work for me.
I've changed $event in the controller to $entity and everything started to work well except google maps. Instead google map I see only broken image. This is second problem.
Please help me to fix this.
Thank You!!!

Reply

Hey Alex,

Thanks for reporting this! You're right, it should be $entity instead of $event. But what about broken image instead of google map - I can't reproduce this bug... Did you have an active internet connection? Have you tried to run the downloaded code from the "finish/" directory? Is there google map image broken for you too? Could you debug a bit more and send me the URL of google map image which is broken for you?

Cheers!

Reply
Default user avatar
Default user avatar Dan Costinel | posted 5 years ago

Hi KNP!

It is possible to generate CRUD based on a field other than id? Because I'd like to have URLs like /blog/about-symfony, /blog/about-php, etc (slugs), instead of /blog/1, /blog/2, etc. And, is this a correct approach or is considered a hack?

Thank you!

Reply

Hi Dan,

I suppose you aware about generate:doctrine:crud command, but it's impossible with it. Probably there's could be a bundle for what you want already, but I don't know this exactly. However you can use this command to generate CRUD based on IDs and then tweak generated code with slugs - actually, it's not a big deal. ;)

Cheers!

Reply

Hi Lucas!

If you own or have a subscription, you'll find a download button on the upper right of this page. But if you don't, you can find the "starting" version of the code (which includes the resources/ directory with the CSS stuff) here: https://github.com/knpunive...

Let me know if that gives you what you need :)

Reply
Default user avatar

Hey Ryan,

i copied the tutorial code but nothing changed. The template's looks like before. Do you know why? I added the Rendering Call Graph maybe it helps.

main 5.87ms/100%

└ EventBundle:Event:show.html.twig 4.89ms/83%

│ └ ::base.html.twig 4.84ms/82%

│ └ EventBundle:Event:show.html.twig::block(body) 4.75ms/81%

│ └ form_div_layout.html.twig::block(form) 1.01ms/17%

│ └ form_div_layout.html.twig::block(form_start)

│ └ form_div_layout.html.twig::block(form_widget)

│ │ └ form_div_layout.html.twig::block(form_widget_compound)

│ │ └ form_div_layout.html.twig::block(widget_container_attributes)

│ │ └ form_div_layout.html.twig::block(form_errors)

│ │ └ form_div_layout.html.twig::block(form_rows)

│ │ │ └ form_div_layout.html.twig::block(button_row)

│ │ │ │ └ form_div_layout.html.twig::block(submit_widget)

│ │ │ │ └ form_div_layout.html.twig::block(button_widget)

│ │ │ │ └ form_div_layout.html.twig::block(button_attributes)

│ │ │ └ form_div_layout.html.twig::block(hidden_row)

│ │ │ └ form_div_layout.html.twig::block(hidden_widget)

│ │ │ └ form_div_layout.html.twig::block(form_widget_simple)

│ │ │ └ form_div_layout.html.twig::block(widget_attributes)

│ │ └ form_div_layout.html.twig::block(form_rest)

│ └ form_div_layout.html.twig::block(form_end)

│ └ form_div_layout.html.twig::block(form_rest)

└ @WebProfiler/Profiler/toolbar_js.html.twig

└ @WebProfiler/Profiler/base_js.html.twig

Thank you

Reply
Default user avatar

Hey Ryan. It think i solved it now. I added a control line eg <h1>new</h1> at the top of the {%block body%} in the create template and it works after that. This is a little bit strange i swear i don't changed a pice of the logic and now it works :) But why does it work after adding this little bit of code.

Reply

Hey Florian!

Ah, I'm glad it works! You know, *sometimes* (and it is very rare) Symfony will incorrectly *not* reload cached files (like Twig templates). It's possible that Symfony was still using your old cached templates until you finally modified the file again, which triggered the template's "modified date" to change (which is how Symfony knows it should reload this file fresh). So, I think you're probably not crazy - just a weird cache thing ;).

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "~2.4", // v2.4.2
        "doctrine/orm": "~2.2,>=2.2.3", // v2.4.2
        "doctrine/doctrine-bundle": "~1.2", // v1.2.0
        "twig/extensions": "~1.0", // v1.0.1
        "symfony/assetic-bundle": "~2.3", // v2.3.0
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.5
        "symfony/monolog-bundle": "~2.4", // v2.5.0
        "sensio/distribution-bundle": "~2.3", // v2.3.4
        "sensio/framework-extra-bundle": "~3.0", // v3.0.0
        "sensio/generator-bundle": "~2.3", // v2.3.4
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "doctrine/doctrine-fixtures-bundle": "~2.2.0" // v2.2.0
    }
}
userVoice