> Symfony 2 >

Course Overview

Login to bookmark this course

Starting in Symfony2: Course 3 (2.4+)

Unlock the full potential of Symfony: understand services, Doctrine associations, lifecycle callbacks, and even more!

  • 3118 students
  • EN Captions
  • EN Script
  • Certificate of Completion

Your Guides

About this course

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
        "ircmaxell/password-compat": "~1.0.3", // 1.0.3
        "phpunit/phpunit": "~4.1", // 4.1.0
        "stof/doctrine-extensions-bundle": "~1.1.0" // v1.1.0
    }
}

Welcome back for part 3 of our Starting in Symfony2 series! In part 3 of this series, we're going to discover even more about Symfony and begin to learn more about how Symfony really works under the hood. Over the next hour or so, we're going to learn what a service is, find out more about the core Symfony services, and create a few of our own. In Doctrine, we'll create some Doctrine associations, including ManyToOne and ManyToMany relationships. We'll also talk about lifecycle callbacks and event listeners. Plus a ton more!

Highlights:

  • Doctrine relations, metadata, and the JoinColumn
  • ManyToMany relationship that's bidirectional, owning versus inverse side and why that matters
  • Loading fixtures with shared data
  • Creating shortcuts with your very own base controller!
  • Doctrine extensions: timestampable, sluggable
  • Ajax: returning Json from a controller, setting the content type and the _format parameter
  • Customizing error pages at the template and controller level
  • Overriding bundle templates
  • Embedding other controllers with Twig's render tag
  • Services! What they are, where to find and use the core services, and a step-by-step on how to create your own. Also, demystifying dependency injection.
  • Creating your very own Twig extension
  • Doctrine lifecycle callbacks and how to create and leverage your own doctrine event listener service

Next courses in the Symfony 2: Starting in Symfony 2 section of the Symfony 2 Track!

93 Comments

Sort By
Login or Register to join the conversation
Default user avatar lennondtps 6 years ago

Hello, I think that you have a typo here

// src/Yoda/EventBundle/Entity/Event.php
// ...

use Doctrine\Common\Collections\ArrayCollection;

public function __construct()
{
$this->events = new ArrayCollection();
}

----------------------------
$this->attendees = new ArrayCollection(); is the correct right?

22 | Reply |

Hey lennondtps!

Yes, you're right - thanks for the report! I've fixed it at https://github.com/knpunive... (and it's correct in the video).

Cheers!

| Reply |

These tutorials really do desperately need an update. Not much has changed, but if your brand new to this kind of work, a small change can cause problems that can take hours to figure out. And then, there's an additional problem. When something doesn't work as expected, there's no easy way to know if you've made a mistake or if the trouble is due to a small change in the framework.

And that can be exceptionally frustrating...

2 | Reply |

Hey there!

We're working on the update for this tutorial right now, but I'm definitely sorry you're having problems. But definitely feel free to push issues here and I'll see if I can help. And regardless, we're already part way through re-recording this episode to be nice and fresh :).

Cheers!

2 | Reply |

Thanks for the quick response! These tutorials are really well done in a great many respects. They're wonderfully put together. I'm really looking forward to the updates. :)

| Reply |
Default user avatar Guest 6 years ago

Weird, after going through this a few times, I just noticed that the Delete option in Edit mode no longer works when using (cascade={"remove"}), and (onDelete="CASCADE") for the $owner annotations in Event.php. I get the following error:

The identifier id is missing for a query of KK\UserBundle\Entity\User

When I remove the cascade={"remove"} and the @ORM\JoinColumn(onDelete="CASCADE") lines from the $owner annotations, the delete function works.

After checking the db, the user is indeed deleted along with all of the events instead of just the selected event. Any idea what's causing this?

1 | Reply |

Hey!

Yes, I can repeat this. So, there are 2 interesting things:

A) In the update of this screencast, we're going to remove the `cascade={"remove"}`. It was originally explained incorrectly and isn't actually the behavior we want. Se https://github.com/knpunive.... Basically, saying this on the $owner property says "when this event is deleted, delete the user". That's obviously not a good thing.

B) With the cascade, when you delete the event, it deletes the user. But since you're logged in as this user, on the next request, Symfony tries to get information about the currently-logged-in user (which you just deleted). This causes the error. If you look at the stack trace for the error, it's coming from a class called ContextListener, which is responsible for "refreshing" the User entity on each request in the security system.

So, that's a fun error to hit as it touches on some interesting topics! But we'll definitely remove that cascade in the future.

Thanks!

1 | Reply |
Default user avatar Jonny 6 years ago

Integrity constraint violation: 1048 Column 'password' cannot be null'

When add the $this->setPassword(null); to setPlainPassword I get the the above error.

1 | Reply |

Ignore this, I'd forgotten to add the service, certain I had but alas I'd not add the prePersist service tag. Cheers great tutorials by the way.

| Reply |

Ha, great! And thank you very much :)

| Reply |
Default user avatar Nate 6 years ago edited

The style for toggling visibility of the attend links was a bit screwy for me in this example.

Setting the visibility via the 'hidden' class wasn't workable because jQuery show/hide adds and removes a 'display: none' style. In the example, after clicking the link, the other sibling would not be displayed.

I modified the twig on the link tags to this and it worked just right:



<a href="{{ path('event_unattend', {'id': entity.id}) }}" class="attend-toggle" {{ entity.hasAttendee(app.user) ? '' : ' style="display: none"' }}>
                                Oh no! I can't go anymore!
                            </a>

                            <a href="{{ path('event_attend', {'id': entity.id}) }}" class="attend-toggle" {{ entity.hasAttendee(app.user) ? ' style="display: none"' : '' }}>
                                I totally want to go!
                            </a>

Other than that, awesome tuts!

1 | Reply |

sorry.... Disqus formatting fail...

here's the code:

<a href="{{ path('event_unattend', {'id': entity.id}) }}" class="attend-toggle" {{ entity.hasAttendee(app.user) ? '' : ' style="display: none"' }}>
Oh no! I can't go anymore!
</a>

<a href="{{ path('event_attend', {'id': entity.id}) }}" class="attend-toggle" {{ entity.hasAttendee(app.user) ? ' style="display: none"' : '' }}>
I totally want to go!
</a>

2 | Reply |
Default user avatar Tapan Kumar Thapa 6 years ago

I think under "table overview" name of

Moar Security should be More Security

| Reply |

Thanks @tapankumarthapa! I've fixed it over at https://github.com/knpunive...

Cheers!

| Reply |
Default user avatar Tapan Kumar Thapa weaverryan 6 years ago

Hello Weaverryan,

Today i have checked the "Defination of Moar" and found it is same as More..So please forgive me for this.

Thanks

| Reply |
Default user avatar Dmitriy 6 years ago

Can you make video about Security Bundle - crating, using and another stuff about security?

| Reply |

Check out the Episode 2 - Ryan actually discusses every single detail about security - while implementing the UserBundle.

There is nothing more you need to know - it's very informative.

| Reply |
Default user avatar Daniele Tieghi 6 years ago

Hey guys, your screencasts are simply awesome!
I hope you come out with the next ones soon!!!
Thank You!

| Reply |
Default user avatar Almog Baku 6 years ago

Hey!,
Why use this fancy "dependency injection" instead of just saying "reference"?? Haha.. you made me confused.

Please keep it "universal".. use the common terms!

| Reply |

Hi Almog!

Yes, dependency injection *is* a fancy term isn't it? :). That's the standard term you hear most, at least around the Symfony world for the idea of injecting one dependency into another. I think the idea is fundamental enough that you can call it by many names. "Dependency Injection" is the most common around Symfony2, but I also think it's a rather *scary* sounding term for a nice, simple concept.

Anyways, glad you got un-confused, and thanks for your comment!

1 | Reply |
Default user avatar dpfkg 6 years ago

I'm a bit confused about the cascade operation. Doesn't "cascade={"remove"}" on the Event side mean, that deleting the Event will remove the User as well?

Over here http://docs.doctrine-projec... the example is using "cascade={"remove"}" on the User side.

| Reply |

Hi dpfkg!

Actually, you're right - I just re-checked and the explanation about the "cascade={"remove"}" part was reversed. In other words, because the cascade is on the Event, it means that deleting the Event will delete the User. That's actually not the behavior we want, so we'll fix that in an upcoming update. But I have changed that paragraph to accurately explain how the cascade works - see https://github.com/knpunive...

Thanks for pointing that out!

| Reply |
Default user avatar Enkhbilguun E. weaverryan 6 years ago

Hi Ryan,

When would you release updated?

| Reply |

Hi Enkhbilguun E.!

I've already updated the script on the website! Check it out here: http://knpuniversity.com/sc.... With that update, everything should be good - this was a very small detail that we mentioned anyways and doesn't affect anything else :).

Thanks!

| Reply |
Default user avatar Guest 6 years ago

If using Symfony 2.4 as I am you'll get an error in this code:
{# app/Resources/TwigBundle/views/Exception/error404.html.twig #}
{# ... #}

{% block body %}
{# ... #}

<section class="events">
{% render 'EventBundle:Event:_upcomingEvents' %}
</section>
{% endblock %}

Using {% render controller('EventBundle:Event:_upcomingEvents' %} works perfectly. Already submitted a pull request among others.

| Reply |

Pull request merged - thanks!

| Reply |
Default user avatar Guest 6 years ago

Missing an '=' for public function updatedEventAction() in the ReportController, also made a pull request for this.

$rows[] = implode(',', $data);

| Reply |

Pull request for this one merged too - good typo catch :).

Cheers!

| Reply |

One more instance of missing a '=' in the public function getRecentlyUpdatedReport(), opened a pull request as well as your recommendation on the type hinting for the deprecation of getRequest().

| Reply |
Default user avatar frimousse 6 years ago

Hi, I did everyhting as in the video and after dropping the schema, creating the schema, running the fixtures again and querying the table, the slugs do not appear. Where should I look to see the source of the problem? (I obviously already looked into Event and config.yml since those were the two files modified to accept the slugs. Sorry my mistake! Everything works fine now. If I could I would have deleted this post but there is no such option.

| Reply |

Hey frimousse!

No problem at all :). You can also always open up an issue on our GitHub (https://github.com/knpunive..., even if you think it might be your fault ;).

Cheers!

| Reply |
Default user avatar Andrey 6 years ago

The part "Customizing error pages at the template and controller level" seems to be outdated in everything that is describing overriding ExceptionController class, since there seem to be a Twig_Environment in constructor now:

http://api.symfony.com/2.2/...

I know it's sh*tload of work, and I really appreciate everything you do!!! But would it be possible to update this part, if only in code - not video - that would be awesome!!!

Thank you in advance!

Another happy student/customer, Andrey.

| Reply |

Hi Andrey!

We're in the process of updating the screencasts for Symfony 2.2 (we're done with episodes 1 and 2 already!). Like you mentioned, for now, we're just updating the code, script and adding notes in videos to point out where things are a bit different - because not too much has changed fortunately. We'll update this episode sometime within the next month (with some new features too, like in-browser video playing and an online-viewable script with code blocks to make finding things easier).

In the mean time, here's the trick! Before Symfony 2.2, the error controller was just a normal controller (no constructor arguments) that used the FrameworkBundle:Exception:show syntax.

But in Symfony 2.2, it's now a service and has - as you saw - a constructor argument. So, here's what you'll need to do now:

1) Override the class just as before

2) Register your controller as a service. This works just like registering any other service. Make sure your service takes a single argument, which is the "twig" service (or, even better, you can set the "parent" of your service to "twig.controller.exception", which is the service for the normal error controller: http://symfony.com/doc/current/components/dependency_injection/parentservices.html)

3) Set the twig.exception_controller setting just like before, except now use the "controller as a service" syntax: http://symfony.com/doc/current/cookbook/controller/service.html. For example, assuming your new service is called "main.error_controller", then you'd have:


twig:
    exception_controller: main.error_controller:showAction

I hope that helps - and thanks for your support!

Cheers!

| Reply |
Default user avatar Thomas 6 years ago


/**
* @ORM\ManyToMany(targetEntity="Yoda\UserBundle\Entity\Member")
* @ORM\JoinTable(
* joinColumns={@ORM\JoinColumn(onDelete="CASCADE")},
* inverseJoinColumns={@ORM\JoinColumn(onDelete="NO ACTION")}
* )
**/

This does not work on MSSQL Server 2012. I get

Introducing FOREIGN KEY constraint 'FK_427D8D2A7597D3FE' on table 'event_member' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints."

Note: I am using "member" instead of "user" for the "user" table because when I tried to create that entity it says that user is a reserved word.

| Reply |

Hey @tvle83!

I've got a question/answer over to you about the User->Member thing on episode 2, but it shouldn't be an issue. And what's nice, even if you *do* need to change the name of your table, you can do it without renaming the entity.

About the error here, it's new to me! It looks like you need to be a bit more careful with your CASCADE paths in MSSQL (I don't happen to use this, but we do want code that works on it).

Did the above change (with NO ACTION) fix things for you or is this still an issue? I don't have access to MSSQL, but if you have a working version, we can tweak things or add a note.

Thanks!

| Reply |

Sorry, that annotation was supposed to be both CASCADE like in the video.

Yes this one does seem to work. I need to write a functional (or unit? (one thing I hope to gain from these is being able to test well. I purchased the behat/mink tutorial too) test to see if deleting the user deletes the events (the desired result, right?)

I did some research and apparently MS SQL takes the super safe approach and doesnt allow cyclical relations with both CASCADE whereas other SQL products ignore the possible issue and let the user deal with it, in effect.

I can do some more testing and let you know! it will give me good practice in testing. I basically just have to delete the user and make sure that the event that he created is gone too. if so then the cascading works. right ? :)

| Reply |

Hi Thomas!

Actually, the desired behavior is simply that if an Event or User is deleted, all entries in the join table are deleted. It may actually happen that way *without* specifying joinColumns and inverseJoinColumns, I don't remember exactly :). About testing this, it's interesting :). It's not a functional test, which is usually a test where you fake a browser and click things. It's also not a unit test, where you test how a function works, but "mock" out any real-world dependencies (meaning, you test, but "fake" the database). Something like this is often called an integration test: it looks and smells like a unit test, but instead of "mocking dependencies" (something you'll learn as you get into those more), you use the real database connection. I'd worry about getting functional tests first, then unit tests, then these integration tests.

And yes, if you want to do some testing, that would be very helpful! But again, we're actually looking to see that if we delete the User, any user_event entries (the join table) for that user are gone. The same thing should happen if you delete an Event. The intended behavior was *not* that if we delete an Event, it deletes the User. If that's happening, we'll want to tweak the code a little bit here. Mostly, I wanted to show people these joinColumns and inverseJoinColumns.

Thanks!

| Reply |
Default user avatar Simon Cossar 6 years ago

I really liked the format of the first two Symfony videos, where you
have the code and the commentary online underneath each of the video
segments. Is there anything like that for parts 3 and 4? I've been
looking everywhere, but maybe I'm missing something obvious.

| Reply |

Hi Simon Cossar!

Ah, I'm really glad you like the new format! You're not missing anything - we're still working on updating episodes 3 and 4 to the new format. We hope to be finished with at least episode 3 by next week.

Thanks!

1 | Reply |
Default user avatar Thomas 6 years ago

I was having a hell of a time figuring out why render wouldn't work. this new syntax worked like a charm. And clearing the cache because i forgot I was in the prod environment.

with IIS it makes things more difficult because short circuiting the exception function to trick it into thinking we're in prod didn't seem to work. I have to use the URL rewrite module in IIS and just change it to app.php or app_dev.php as needed. and remember to clear the cache. The command doesn't seem to work too well either, i just go and select all in app/cache/prod and delete. It's just quicker. I don't really want to spend too much time figuring out why the command doesn't seem to work right.

I am only using IIS because my client has a windows server so I am forced to, or else I would be on ubuntu or something similar using apache or nginx. I am finding some gotchas with the code and configurations so I am glad more good is coming out of my frustration :)

| Reply |
Default user avatar Nate Evans 6 years ago edited

The syntax for the Twig render function is a bit different in Symfony 2.3


{% render(controller('EventBundle:Event:_upcomingEvents')) %}
| Reply |

Thanks Nate! And actually, render is now a function (the tag still exists, but the render function is more "preferred"), in part because it just makes more "sense". Render returns a string, so we echo it:

{{ render(controller('EventBundle:Event:_upcomingEvents')) }}

Anyways, thanks for the comments!

| Reply |

I've just tried to use the slug, but instead of using Annotations (which I don't really like as I see them more as comments than code) I've used Yaml to create my entity. The only issue is that the slug fields are always null when I try and run my fixtures.

Currently this is what my entity looks like: https://github.com/JohnRCra... and the Yaml config for it is: https://github.com/JohnRCra...

Am I missing something?

| Reply |

Hey John!

Hmm, it looks good to me - I was checking it against the gedmo docs: https://github.com/l3pp4rd/....

But, remove the annotations in your entity so they don't confuse things. Also, double-check to make sure you have the sluggable listener activated (https://github.com/stof/Sto.... The null slug in the fixtures definitely means that the slug behavior just isn't working at all.

Let me know if these 2 things above work or not and we'll go from there!

Cheers!

| Reply |

Hi Ryan, I removed the annotations (I actually didn't originally have them there in the first place... I just put them there as I thought it was really needed). I also double checked that I had the sluggable listener activated, and it is (https://github.com/JohnRCra... ) However when I try and run my fixture (https://github.com/JohnRCra... ) I still get this error: http://pastebin.com/diYiA296

| Reply |

Hey John!

I've just sent a PR with the fix: https://github.com/JohnRCra...

It was just a simple indentation problem. It's always in the small details - I missed it myself when I looked at your original config!

Cheers!

1 | Reply |

Ha! That was indeed the issue. Thanks for spotting that Ryan.

| Reply |
Default user avatar ricardo 6 years ago

Strange I have the same code in my entity

id;

}

/**

* Set name

*

* @param string $name

* @return Event

*/

public function setName($name)

{

$this->name = $name;

return $this;

}

/**

* Get name

*

* @return string

*/

public function getName()

{

return $this->name;

}

/**

* Set time

*

* @param \DateTime $time

* @return Event

*/

public function setTime($time)

{

$this->time = $time;

return $this;

}

/**

* Get time

*

* @return \DateTime

*/

public function getTime()

{

return $this->time;

}

/**

* Set location

*

* @param string $location

* @return Event

*/

public function setLocation($location)

{

$this->location = $location;

return $this;

}

/**

* Get location

*

* @return string

*/

public function getLocation()

{

return $this->location;

}

/**

* Set details

*

* @param string $details

* @return Event

*/

public function setDetails($details)

{

$this->details = $details;

return $this;

}

/**

* Get details

*

* @return string

*/

public function getDetails()

{

return $this->details;

}

/**

*

* @return User

*/

public function getOwner()

{

return $this->owner;

}

/**

*

* @param User $owner

*/

public function setOwner(User $owner)

{

$this->owner = $owner;

}

}

but when I write the command php app/console doctrine:schema:update --dump-sql only show me

ALTER TABLE event CHANGEW time time DATETIME NOT NULL

not on the new owner attribute, which I can do nothing appears?

| Reply |

Hi Ricardo!

This part of your code looks fine. But do you have an $owner property? When you run "doctrine:schema:update", it calculates the SQL by looking at the properties on your entity (not the getter methods or setter methods). So, make sure you have the following inside Event:



/**
 * @ORM\ManyToOne(targetEntity="Yoda\UserBundle\Entity\User")
 */
protected $owner;

These lines here should cause the doctrine:schema:update to notice the new owner_id column. If it still doesn't work, let me know! It's also possible that you already have the owner_id column in your database, which is why Doctrine is not adding it. Check your database to be sure :).

Cheers!

| Reply |
Default user avatar ricardo 6 years ago

i copy paste the same code in entity event but when i write command

php app/console doctrine:schema:update --dump-sql

php app/console doctrine:schema:update --force

never take the atribute owner only show me this message

ALTER TABLE event CHANGE time time DATETIME NOT NULL

| Reply |

Delete comment?

Share this comment

astronaut with balloons in space

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