If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
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.
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.
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!
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/
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.
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
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!
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!
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!
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 :)
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
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.
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!
// 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
}
}
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!!!