Starting in Symfony2: Course 2 (2.4+)

UPGRADE! Check out the newest version of this tutorial

Over the next hour, we're going to take you through some of the most difficult areas of Symfony learning all about security, forms, and parts of Doctrine. We'll also see testing and learn more about how Symfony's service container works. When you get to the end, you'll be well on your way to mastering Symfony.

One of the things we'll be creating is a security system that stores users in the database. This is functionality similar to what's offered by the very popular FOSUserBundle. That bundle is really great, but until you understand how the security system works, you're going to run into limitations. Once you do, you'll feel at home using FOSUserBundle or configuring the security system all on your own.

Highlights:

  • Learning and building a form-login security system from scratch
  • Loading users from the database
  • Using AdvancedUserInterface and UserProviderInterface classes to give us more control over login
  • Doctrine Repositories and Querying
  • Building forms: creating fields, the data_class option, form type classes
  • Form field options & HTML5 validation
  • Form rendering
  • Functional Testing!

... and of course, tips, tricks, best practices and other little features along the way!


Your Guides

Ryan Weaver Leanna Pelham

Questions? Conversation?

  • 2016-04-13 weaverryan

    Awesome :). You might be thinking of the checkMX option on the Email validator - it checks that the domain is valid and actually has an MX record setup for it.

  • 2016-04-13 Shairyar Baig

    Ya, I ended up adding custom validation and it worked out fine. I thought i read somewhere about symfony offering this validation but may be it was something else that i read and i am confusing it :)

    Thanks

  • 2016-04-13 weaverryan

    Hi Shairyar!

    Your best option is to add custom validation to the email field on your class. You could use the Regex constraint, but I'd recommend (unless your rules are really simple) to use the Callback constraint (http://symfony.com/doc/current... - you can easily write the exact validation code you need :).

    Cheers!

  • 2016-04-06 Shairyar Baig

    Hi,

    Does symfony by default has an option which allows us to restrict users from registering if the email id they provided does not belong to a specific host. For example if i only want people to register using yahoo.com? Or to achieve this custom validation needs to be created?

  • 2016-03-29 weaverryan

    Awesome, thank you! If you're a subscriber (or own the video), you can visit any screencast page in this tutorial and you'll see a "Download" button in the upper right corner - one of the options is for the code. If you're not a subscriber, we include all the code blocks in each of the tutorials :).

    Cheers!

  • 2016-03-27 Ayush agrawal

    Hey, awesome tutorials.. You have simplified learning Symfony and whole lot. Can you please point to the Code downloads for the tutorial because Github dont have files for this episode. Thanks

  • 2016-02-22 Dan Costinel

    Yes, it's really wired. First, I used your method with your code. It didn't worked, and it generated the error I've mentioned. I changed the code as in the second method. Then, after some minutes, I needed a refresh, and another error came out. Wired no.2 :) .I changed back to your code, and I've followed the next videos, and, wired no.3, the code is working. I'll let you know if anything bad will happen, if is the case. Thanks for reply!

  • 2016-02-22 weaverryan

    Hey Dan!

    Hmm, so that *is* weird. So here's what happened. When a newer version of PHP came out, it "broke" how Doctrine unserializes objects (it unserializes objects in order to "instantiate" them when you query for them). Incidentally, this has nothing to do with the serialize/unserialize stuff in your User object (unless I'm missing the cause entirely). The fix *is* in fact to upgrade your Doctrine version - they fixed this problem in later versions. Actually, I think they even fixed it in Doctrine 2.3 - if you're using that version, you just need to upgrade to the *latest* 2.3.* (of doctrine/orm).

    I'm not sure why your change "fixed" the issue. It looks a little weird - I'm not sure why your User object would have a $this->user property, or what some of the other properties are that are being referenced. So, I think it just *happens* that this works :). Your serialize and unserialize functions should look like opposites of each other: serialize "id", replace $this->id in unserialize, serialize $username, do the same in unserialize, etc.

    Let me know if that helps!

  • 2016-02-20 Dan Costinel

    I don't know if this is a fix, but the error is going away if I use this instead of what the video says:

    This function from the video

    public function unserialize($serialized)
    {
    list(
    $this->id,
    $this->username,
    $this->password
    ) = unserialize($serialized);
    }

    I've replaced it with:

    public function unserialize($serialized)
    {
    list(
    $this->user,
    $this->authenticated,
    $this->roles,
    $this->attributes
    ) = unserialize($serialized);
    }

    This is the way to do it, or is just happens to work?

  • 2016-02-20 Dan Costinel

    Hi!
    I have a problem ( in fact I receive an error), for the 15th video (User serialization). I must mention that all the previous stuff works like a charm! (Good work, btw). The error is this:

    Warning: Erroneous data format for unserializing 'Yoda\UserBundle\Entity\User'

    500 Internal Server Error - ContextErrorException

    I've searched google for the error ( https://github.com/symfony/sym... ), and some guy ( anteriovieira ) says I should modify the doctrine version for my project. I use Symfony 2.7.5, and in my composer.json I have: "doctrine/orm": "^2.4.8".

    What should I do to get rid of the error?
    Thanks,
    Dan.

  • 2015-12-31 weaverryan

    Hi there!

    I think it's not such a stupid question :). The *only* thing that Symfony cares about is that your User class has a getRoles() method that returns an array of roles (e.g. ROLE_BEGINNER). Symfony doesn't know or care *how* this method works. For example, you could simply return a hardcoded array('ROLE_USER') from that method. Or, you could read some boolean property on your user - like isAdmin - and return an extra ROLE_ADMIN entry from this method if that's true.

    It's pretty common, however, to want to be able to give any user any role by storing the roles in the database. Usually, you'll make the column an "array" type (or json_array - same idea, but I like this a little better) - http://doctrine-orm.readthedoc.... In this case, your roles property is an array, but in MySQL, Doctrine creates a string column and serializes (or json_encode's) the array into a string. In PHP, you never need to worry about this - Doctrine takes care of serializing and unserializing the value to and from the database. If you do this, then getRoles() simply returns the "roles" property. This also means that you can give each user multiple roles.

    But, if each user simply needs only 1 role, you could simplify things. Something like this:


    /**
    * @ORM\Column(type="string")
    */
    private $roleName;


    public function setRoleName($role)
    {
    $this->roleName = $role;
    }


    public function getRoles()
    {
    return array($this->roleName);
    }

    tl;dr; The only requirement is that your User has a method called getRoles() that returns an array. You can create this array however you want, by storing role information in any way. But, commonly, you'll use the array (or json_array) type, because it allows you to store an array of roles in a single string column.

    I hope that helps!

  • 2015-12-31 Bettinz

    Ok so, a stupid question. I can't understand how to use roles with database: i've read the guide and view tutorials, but my simple question is: when I create an entity with doctrine, do I need roles column? If yes (I presume) what kind of column is it? A string?
    Then, i can write ROLE_BEGINNER and ROLE_ADVANCED in this column? How getRoles() read that column and understand that? Everywhere I see static routes with static files .yml but I can't find a tutorial that explain different roles, with different users, using doctrine.
    Thank you

  • 2015-08-09 Shairyar Baig

    A good refreshing tutorial. Can't wait to go through the next part.

  • 2015-04-22 weaverryan

    Hi Robert!

    Sorry for the late reply, but your solution *does* make sense - we only want to clear out the password if a new one *has* been sent. The only part that doesn't make sense in your setup is *why* setPlainPassword() is being called at all - it looks like your form only has firstName and lastName fields (so the form *won't* call setPlainPassword). So, keep an eye out for some code that might be setting that. But either way, with your above change, the code is even better and safer than before.

    Cheers!

  • 2015-04-21 Robert

    Hi again,

    It looks like $this->setPassword(null); in setPassword() makes the difference.

    I made following changes (set password null only if plain exists):

    public function setPlainPassword($plainPassword)
    {
    $this->plainPassword = $plainPassword;
    if($plainPassword){
    $this->setPassword(null);
    }
    return $this;
    }

  • 2015-04-21 Robert

    Hi,

    First of all, great tutorials. Just followed this one and made an simple user panel to let logged in users change their data. Unfortunately, when I want to persist new data, symfony2 always asks for password. Let me show you my code:

    controller:

    public function settingsAction(Request $request)
    {
    $loggedUser = $this->container->get('security.context')->getToken()->getUser();
    $user = $this->getDoctrine()->getRepository('AppBundle:User')->find($loggedUser->getId());
    $form = $this->createForm(new UserDataFormType(), $user);
    $form->handleRequest($request);
    if($form->isValid()){
    $user->setPassword($user->getPassword());
    $em = $this->getDoctrine()->getManager();
    $em->persist($user);
    $em->flush();
    $request->getSession()->getFlashbag()
    ->add('success', 'Your data has been updated!');
    }
    return $this->render('AppBundle:Panel:settings.html.twig', array(
    'form' => $form->createView()
    ));
    }

    form:

    class UserDataFormType extends AbstractType
    {

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder->add('firstname', 'text', array(
    'required' => false,
    'label' => 'Name'
    ))
    ->add('lastname', 'text', array(
    'required' => false,
    'label' => 'Last name'
    ))
    ->add('submit', 'submit', array(
    'attr' => array('class' => 'waves-effect waves-light btn'),
    'label' => 'Zmień'
    ));
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array(
    'data_class' => '\AppBundle\Entity\User',
    ));
    }

    public function getName()

    {
    return 'user_data';
    }

    Could you please help and say what to do to get rid of "Not null violation: 7 ERROR: null value in column "password" violates not-null constraint" everytime I want to persist logged in user entity?

  • 2014-10-06 Edison

    YESSSSSSSSSSSSSSSSS! You are the Best! Haha wow. :)

    1st. Thanks for your rocket answear, you always do that!
    2nd. You solved my problem, you were right, in my <form> tag was the solution!

    Cheers!

  • 2014-10-06 weaverryan

    Hi Edison!

    So glad to hear you're going through all the tutorials - you won't regret it :).

    A few things about your situation:

    1) Most of the time, routes match only on URL. If you have a route whose path is /create, then if you go to /create, it will match. Good work on using router:debug to investigate this.

    2) Sometimes - and this is totally optional - you might decide to say "Hey, I'd like /create to only match on POST requests" (e.g. form submits). That is what the "_method: post" thing in the route is doing. This means that if you go directly (e.g. by just putting /create in your URL and hitting enter) to the URL, you will get this "Method not allowed" exception. You have matched the URL, but this URL only allows POST requests, and you are using a GET request.

    3) Why is this happening in your case? Well, since this is a form submit, you probably *do* want to respond to only POST requests (though in practice, if you also allowed the route to respond to GET requests, it would simply look like the user filled out the form blank and submitted - so they would get validation errors and the form would re-render, so no big deal). My guess is that you're missing the method="POST" part of your <form> tag, which is causing the form to submit via GET, causing this error.

    I hope this helps you discover discover your issue.

    Cheers!

  • 2014-10-06 Edison

    Hello Big Guys!

    First of all thanks for the good tuts!

    I'm currently on part 3 of Starting in Symfony lessons, (Proud of my
    decission on following ALL your tutorials!!) , everything goes smooth,
    since i am coding along with you, but since the second round I noticed that everytime I tried to save an
    event on my project ( Route: http://127.0.0.1:8000/new), let me explain you....I fill out
    all the fields and click save event button...and I got this ugly exception:

    "No route found for "GET /create": Method Not Allowed (Allow: POST)"
    405 Method Not Allowed - MethodNotAllowedHttpException
    1 linked Exception:

    MethodNotAllowedException »

    I looked back through all the possible causes and files, I've challenged myself
    to find the answear, googling, discarding, I compared them with
    yours....I made some test changing things on my event.yml file, but the
    problem persist.

    ( HERE IS THE PART OF MY CODE I THINK IS THE RESPONSIBLE OF THE ISSUE:

    event_create:
    path: /create
    defaults: { _controller: "EventBundle:Event:create" }
    requirements: { _method: post }

    )
    Apparently is ok!!
    On php app/console router:debug all routes looks fine........

    Any hints or recommendations? I appreciate your time on giving me a hand!

    Thanks!
    Edison

  • 2014-09-11 weaverryan

    Hi Tommie!

    We definitely go over all the big pieces of Symfony's authentication system so you (ideally) understand what's going on. We *don't* cover doing something as custom as what you're asking, but something similar *is* covered here: http://symfony.com/doc/current.... You'd need to follow this (roughly), but have your very own Token class where you use the $request argument in createToken() to populate your custom Token class with whatever this third piece of information is. Then, in authenticateToken, you can use the username, password and this third thing from your custom token class to do whatever you want. It's both a bit complicated, and also quite easy (not a lot of code, but tough concepts).

    This tutorial will prep you for understanding how a normal form authentication will work, which will ideally help you understand and implement that cookbook article how you want. If you're not sure, remember that the tutorial text is free and public, so you can browse around and read things to see what's there :).

    Cheers and good luck!

  • 2014-09-10 Tommie

    Hello,

    Is it possible to make a 3 field login with this tutorial.
    For a project i am working on i need to make this but i would like to try it in symfony!
    If i buy this tutorial would it be discussed ?

  • 2014-08-17 Mihai D

    Ok, got it - needed to add "use Symfony\Component\Security\Core\SecurityContextInterface;"

  • 2014-08-17 Mihai D

    Hi, seems I have a bit of a problem with creating the Security Controller.

    I've pretty much done as instructed above but when creating the Login Form, by copy-pasting the code from symphony, I get a blank screen in the "/login" area.

    I've checked the php_error_log and get this:

    [17-Aug-2014 13:05:36 Europe/Berlin] PHP Fatal error: Class 'Yoda\UserBundle\Controller\SecurityContextInterface' not found in E:\xampp\htdocs\symfony\src\Yoda\UserBundle\Controller\SecurityController.php on line 34

    [17-Aug-2014 13:05:40 Europe/Berlin] PHP Fatal error: Cannot redeclare class Symfony\Component\Security\Core\SecurityContextInterface in E:\xampp\htdocs\symfony\vendor\symfony\symfony\src\Symfony\Component\Security\Core\SecurityContextInterface.php on line 21

    The code is:

    getSession();

    // get the login error if there is one

    if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {

    $error = $request->attributes->get(

    SecurityContext::AUTHENTICATION_ERROR

    );

    } else {

    $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);

    $session->remove(SecurityContext::AUTHENTICATION_ERROR);

    }

    // last username entered by the user

    $lastUsername = (null === $session) ? '' : $session->get(SecurityContextInterface::LAST_USERNAME);

    return array(

    // last username entered by the user

    'last_username' => $lastUsername,

    'error' => $error,

    );

    }

    /**

    * @Route ("/login_check", name="login_check")

    */

    public function loginCheckAction ()

    {}

    }

  • 2014-08-13 weaverryan

    Hey!

    We actually have a note on that code block to "also change ROLE_USER to ROLE_EVENT_CREATE in newAction", but it's not very visible at all. So yea, +1 for explicitly adding the line to newAction (like we do in the video).

    Thanks!

  • 2014-08-09 Guest

    Noticed in the section 'Role Hierarchies' that when using:

    $this->enforceUserSecurity('ROLE_EVENT_CREATE');

    in the createAction, the form will still come up and only after filling in the form and attempting to submit it will the 'no access' error message appear.

    However if you use this on newAction, the user without access is blocked immediately as in the video.

    Wanted to confirm this before submitting a change.

  • 2014-08-06 weaverryan

    Hi there!

    Hmm, that's interesting - thanks for sharing it! Yes, your description of "it persists, but doesn't update" is very consistent with the situation where Doctrine thinks that your entity is unchanged (and so does nothing). So, you nailed it! I had never hit this issue before, because normally you are indeed doing things like $entity->setPublishedAt(new \DateTime());, where you're replacing the entire object.

    I'm glad you got it working! Sorry I couldn't respond before now :).

    Cheers!

  • 2014-08-06 Bezruki

    Sorry to spam :) but I think I've finally figured out what is going on and wanted to share the solution. Doctrine api clearly states that DateTime objects are compared by reference, and not value! To my understanding, this means that if you have a DateTime property in your ORM'd class, simply updating it (like changing hours or minutes) is not enough, as Doctrine will think this property has not changed and will not update the actual column in DB. I added a line of code that creates a new DateTime() object and updates the property (basically giving it a new reference), and my code is now working. I believe this is what happens in the symfony's form api as well - I recall seeing something about Doctrine's handling of DateTime objects.

  • 2014-08-05 joe joe

    I've taken your advice and added a thread on StackOverflow: http://stackoverflow.com/quest...

    Still foggy on:
    a) how to setup the security in the controller to limit access to each user per category they have access to

    b) what needs to go in access_control now

    c) how to hide the create/edit/delete links in Twig.

  • 2014-08-05 Bezruki

    By the way, the amPm field is field type of its own - I created a basic 'choice' type with two options - am/pm; so in practice, this is like a 3 level form :)

  • 2014-08-05 Bezruki

    Ryan... thank you so much for such quick reply! I was getting really desperate :)

    So... It took me a bit of time to figure out your suggestion, but I think I got it.

    - I created DateTimeFields class with hours/mins/ampm (plus getters/setters)

    - then I created DateTimeFieldsType form type with necessary fields (hours/mins/ampm)

    - I have Event class which contains this publishedAt date. Here in the Event class I added those getPublishedAtDateTimeFields & setPublishedAtDateTimeFields methods. First takes this event's publishedAt (DateTime), converts it to DateTimeFields and returns it, Second takes DateTimeFields and converts it into DateTime and saves into Event's publishedAt date.

    - In the Event class, I also added a dummy 'middleman' publishedAtDateTime property that is NOT mapped via ORM

    - Next I created the crud forms for Event. Modified the EventType form: removed the publishedAt DateTime form field and replaced it with publishedAtDateTime: ->add('publishedAtDateTimeFields', new DateTimeFieldsType())

    And here's where things stopped working :)

    When I save a new Event entity, it persists without any problems! Works like a charm. But when I tried to update it, nothing happens. Did I forget to do something? I did a var_dump from inside the form, and the DateTime publishedAt looks updated; however, it is not saved to DB... Any ideas? I am SO CLOSE!

    Again, thank you for these great videos and for helping out. This is amazing.

  • 2014-08-04 weaverryan

    Hi there!

    Yes, I know *exactly* the issue you're talking about - I just ran into it myself a few weeks ago. And you're right, it's not possible currently with the datetime type unfortunately :/. And yes, creating custom complex fields with data transformers is not necessarily any easy thing.

    If this is an isolated situation (meaning, you don't need this all over your site, just one or two forms), I might recommend solving it in a simpler way. The custom field is the cleanest, but this might get you moving:

    1) Create a new simple PHP class (DateTimeFields) with 3 properties: hours, minutes, amPm
    2) Create a new form type for this class, which will of course have those 3 fields on it.
    3) Assuming you have a "publishedAt" field on an entity, which is what you ultimately want to set, create getPublishedAtDateTimeFields and a setPublishedAtDateTimeFields methods. Make the first transform your publishedAt DateTime into a new DateTimeFields object. Make the second do the opposite: turn the DateTimeFields into a DateTime and set it on the publishedAt field.
    4) Instead of having a publishedAt field on your main form, have a publishedAtDateTimeFields field, which is a new instance of the form type you created in step 2.

    As you can see, it's a workaround and takes a bit more work. But maybe it will inspire you if creating the custom form field type is proving to be a huge pain. I hope it helps a bit :).

    Cheers!

  • 2014-08-04 bezruki

    Hello Knp Team! Thank you for these tutorials, they are very well done and make learning Symfony definitely easier.
    I've ran into an interesting problem. I've created a form with a DateTime field and can't seem to find a way to display it in 12 hour format (with am/pm). I've been searching for a solution for several days now without any luck :)
    It looks like this was a suggested feature for future release, but has not been implemented yet. I've tried to create a custom form type, but the SF2 cookbook only covers creating simple fields. I need to create a form that has a special field which contains 3 'children' that are of 'choice' type (hours, minutes, am_pm). I've tried looking into the TimeType.php and DateTimeType.php (Form component) but that code is quite a bit complicated - can't understand half of it. Any suggestions (jQuery/js is my last choice, but I'm trying to do this in PHP).

    Thank you!

  • 2014-08-04 weaverryan

    Hey Joe!

    Yep, that's exactly what I was talking about. Re-work things and I bet it'll all feel a bit more natural to get the security working :).

    Cheers!

  • 2014-08-04 joe joe

    Well "job1" and "job2" are the category titles. I just have one Category entity. Dunno if that's what you meant.

    But yes, as the code grows, I would end up adding more (/job1/new, /job2/new, /job3/new, job4/new and job5/new etc.) paths to the access control and adding the corresponding category title. (i.e., /job1 would be "job1" title in the category entity etc.)

    It does work, but you're right it's probably not the best way to go about things.

    Will rework the code based on your suggestion.

  • 2014-08-04 weaverryan

    Hey Joe!

    Hmm, so I think your setup needs just a little bit of work. If I understand it correctly, the URLs like /job1/new and /job2/new are dynamic - meaning that "job1" and "job2" are the categories in your database, they are not "hardcoded". In that case, you should *not* include all of these paths in your access_control. It just doesn't make sense, because if you create a new Category ("Job4") in the database, then you'll need to actually change your code to make this work.

    Instead, I would apply security for these pages in your controller. I would also make some sort of database relationship between User and Category that defines which Category(s) a User has access to (probably a ManyToMany between User and Category). Then, in your controller, you'll query for the Category based on its slug, then use if logic to see if the current User is related to this Category in the database. If he isn't, access denied!

    Now, once you have this User to Category relationship, you'll use my builder code and query for all of the categories that the current User object is related to.

    Hopefully this makes *some* sense. If it doesn't make total sense (it probably won't yet), go ahead and post on Stack Overflow and then paste the link here. Then me *and* other people can help, which will probably be faster anyways :).

    Cheers!

  • 2014-08-02 joe joe

    Hi Ryan,

    That looks like a great way of handing it, but unfortunately I am not using Role in the Category or the Post entity. (probably not a good idea)

    I'm only using id, title, posts (connection with Post entity) and catslug in Category and id, title, author, body, categories (connection with Category entity) and slug in Post.

    I do however have the Role field in the User entity and am setting it by the following:

    User Fixture:

    $user1 = new User();
    $user1->setUsername('user1');
    $user1->setPassword($this->encodePassword($user1, 'password'));
    $user1->setEmail('user1@user1.com');
    $user1->setRoles(array('ROLE_USER1'));

    And in the security.yml I am using the access_control to restrict access to each ROLE_USER with the following example: (i.e., the path /job1 will lead to a job1 blog page with CRUD privileges to user with that role, same with /job2, /job3 etc. all access assigned out by the type of ROLE_USER they are)

    access_control:
    - { path: ^/job1/new, roles: [ROLE_USER1, ROLE_ADMIN] }
    - { path: ^/job2/new, roles: [ROLE_USER2, ROLE_ADMIN] }
    - { path: ^/job3/new, roles: [ROLE_USER3, ROLE_ADMIN] }
    - { path: ^/job1/create, roles: [ROLE_USER1, ROLE_ADMIN] }
    - { path: ^/job2/create, roles: [ROLE_USER2, ROLE_ADMIN] }
    - { path: ^/job3/create, roles: [ROLE_USER3, ROLE_ADMIN] }

    Then the user with the appropriate role can CRUD on the blog etc. which is where the potential category problem came up. If they selected on the wrong one, say they have access to job1 and selected category job2 on post creation then they wouldn't have access to change this back as it would appear on the blog page job2 lacking the proper access there.

    How can I modify your builder code to achieve the same or similar outcome with my setup or am I doing it in such way it's completely nuts and strongly discouraged?

  • 2014-08-01 weaverryan

    Hey Joe!

    Great question. So it doesn't really have much to do with security, it more relates to *how* you query for the object. Your "category" field is using the "entity" type (http://symfony.com/doc/current..., even though it doesn't look like it (it's being guessed as entity, since the second argument to add() is empty). This type has a "query_builder" option, which lets you build a custom query for returning those category objects. I don't know exactly how you're storing which user role maps to which categories, but it'll look something like this. I assumed (naively), that you have Category.role and User.role properties. That's almost certainly an over-simplification, but hopefully you get the idea :).


    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $user = $options['user'];

    $builder
    ->add('category', 'entity', array(
    'class' => 'AcmeHelloBundle:Category',
    'query_builder' => function(EntityRepository $er) use ($user) {
    return $er->createQueryBuilder('c')
    ->orderBy('c.role = :userRole')
    ->setParameter('userRole', $user->getRole())
    },
    );
    }


    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array('user' => null));
    }

    The key is that I'm passing in the "user" object into the form, so we can access it to build our query:


    public function newAction()
    {
    $form = $this->createForm(new PostType(), $post, array(
    'user' => $this->getUser()
    ));


    // ...
    }

    Let me know how it works out! Cheers!

  • 2014-07-31 joe joe

    Hi Ryan,

    Quick question on forms and user roles access restrictions, I have a form, and each user has a specific USER_ROLE that gives them access to specific categories they can post/edit to.

    Problem is that I am using a category drop down menu selection (category entity relationship to post entity (OneToMany/ManyToOne)) in the form that shows all of the categories even if they do not have access to it.

    Thus, the user can inadvertently select the wrong category which will create the post in the category they will not have access to change back.

    To avoid this potential headache. how can I set/restrict the user access so they can only see the category they have access to?

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder
    ->add('title')
    ->add('body')
    ->add('author')
    ->add('category')
    ->add('file', 'file', array(
    'label' => 'Image',
    'required' => false
    ))
    ->add('created');
    }

  • 2014-07-30 joe joe

    Ryan,

    Thank you so much for the in-depth explanation on how to get to this to work. It definitely makes more sense now after your thorough walk-thru.

    And keep up the fantastic work, by far the best learning materials on Symfony2 out there.

  • 2014-07-30 weaverryan

    Hi Joe!

    I'm glad you like it, and *such* a good question!

    So, let's look at what encodePassword is actually doing:

    1) It accesses Symfony's container object (that one magic object that holds all other important objects in the system (check out: http://knpuniversity.com/scree...

    2) It uses this container object and asks it for the "security.encoder_factory" service. This returns the object that's known by this name.

    3) We use the "security.encoder_factory" object

    So, for (1), we need to know where this all-important container object lives. Symfony is made so that in a controller, you can just say $this->container. But that *only* works in a controller. But, in this script - if you look near the middle of the file (before we start coding), you'll see that we've booted Symfony and I've grabbed the container and set it as a $container variable. So, if you just remove the encodePassword function and write everything inline with your user-creation, all you need is:


    $user = new User();

    // ...
    $encoder = $container->get('security.encoder_factory')
    ->getEncoder($user);

    $encodedPassword = $encoder->encodePassword('password', $user->getSalt());
    $user->setPassword($encodedPassword);

    So the trick is always to figure out where this container object is. If you can find it, you can get any object you want.

    Cheers!

  • 2014-07-30 joe joe

    Hi Ryan,

    I like the idea of the play script for quickly testing or adding to the database. I was trying to use this to enter in a user quickly for testing purposes, but ran into an error when it came to the encode part.

    How do I pass in the encodePassword function from fixtures into this script?

    Thanks!

    Error:
    PHP Fatal error: Call to undefined method Acme\DemoBundle\Entity\User::encodePassword() in /.../play.php on line 24

    play.php:
    use Acme\DemoBundle\Entity\User;

    $user = new User();
    $user->setUsername('user4');
    $user->setPassword($user->encodePassword($user, 'password'));
    $user->setEmail('user4@user4.com');
    $user->setRoles(array('ROLE_USER2'));

    $em = $container->get('doctrine')->getManager();
    $em->persist($event);
    $em->flush();

    function encodePassword(User $user, $plainPassword)
    {
    $encoder = $this->container->get('security.encoder_factory')
    ->getEncoder($user);

    return $encoder->encodePassword($plainPassword, $user->getSalt());
    }

  • 2014-07-26 weaverryan

    Nice detective work on the route ordering!!! Woot! router:debug is your friend with this - since it lists routes in the order that Symfony will parse through them.

    Cheers!

  • 2014-07-26 Guest

    You're right, it is related to the showAction, I changed the showAction to /show/{id}—and of course it works. And I've tried creating the doctrine crud with the /event prefix, then again with the / prefix and both times going to /new works until I add in the access control.

    Come to think of it, I am thinking maybe this is related to the route order as I am using annotations? Since showAction is in the EventController-showAction the '/{id}' prefix comes before the SecurityController-showAction '/login', therefore it's trying to hit the showAction from EventController instead of '/login'?

    Ok, just changed the loading sequence in routing.yml so that UserBundle is loaded before EventBundle, and it works perfectly without having to add in '/show' to the showAction in the EventController. +1 on the assist, you're a rockstar!

    jfc_user:
    resource: "@UserBundle/Controller/"
    type: annotation

    jfc_event:
    resource: "@JFCEventBundle/Controller/"
    type: annotation
    prefix: /

  • 2014-07-25 weaverryan

    Hey Keefe!

    Thanks :). And Ep3 and Ep4 are coming soon, like probably next week!

    Hmm. So the "Unable to find Event entity" is coming from `showAction` in EventController. So this tells me that when you're going to "/new", it's *not* matching our new page, and is instead hitting the show action. This is probably because - for one reason or another - the route for showAction is /{id} (no event prefix).

    The confusing part is that the access_control stuff has *nothing* to do with whether your route is matched or your URLs. You should be able to totally mess that part up, and at worst, your URLs just won't have security applied to them.

    If you can, check back and try this all again. Use the "/" prefix first, and make sure "/new" goes to that page (use router:debug if anything weird happens). Then re-add the access_control entries. It *should* work - you're doing everything right.

    Let me know!

  • 2014-07-25 Guest

    Authorization Access Control:

    I've been going this again for practice as it's updated now. (Job well done!) Instead of using YAML for the CRUD controller (EventController.php) I opted with using Annotations.

    I noticed some strange behavior when I go to the access control section, I am using @Route("/") in my Event Controller so everything loads without the /event prefix. This works as intended (the routes work and create entity works etc.), however when I put in the access_control: ^/new or ^/create I get a 404 error ("Unable to find Event entity").

    Now, when I go back in and put in @Route("/event") in the event controller and update the access_control to reflect this (i.e., ^/event/new, ^/event/create) it works perfectly and redirects to the login page.

    Any idea what is causing this? My app/config/routing.yml is as follows below. I've commented the prefix part just to experiment and the same results as described above occurs. Is there another setting somewhere I'm missing when using Annotations for CRUD Controller creation?

    jfc_event:
    resource: "@JFCEventBundle/Controller/"
    type: annotation
    prefix: /

  • 2014-06-30 somecallmetim27

    By the way, people seem to tend to use comments when things go wrong. I just want to take a second to say that these tutorials really are phenomenal. Honestly, terrific way to learn the Symfony framework. I'd highly recommend this to anyone who has a bit of programming background and wants to get into web development.

  • 2014-06-30 somecallmetim27

    Actually if you take the login.css file and copy the text into a register.css file and replace .login with .main-block you end up with something that doesn't look entirely terrible. Particularly if you just want something that will get you through the tutorial.

  • 2014-06-30 somecallmetim27

    Thanks for commenting on this. I ran into the exact same problem and was wondering the exact same thing. My css skills are still on the new side, so hopefully 2.4+ comes out soon. :P

  • 2014-06-27 weaverryan

    Hey!

    Actually, your security.yml file *may* be empty or may *not* be empty, depending on how you installed it. When you originally install Symfony, it asks you if you want to install the AcmeDemoBundle (this question is new to Symfony 2.5, prior it always installed it). If you choose "no" (the default), then your security.yml file is empty.

    But I'll add a note about this - I think other people may see the same thing. If your security.yml file *is* empty, you'll just need to fill it in to look like ours.

    Thanks for the comment!

  • 2014-06-27 weaverryan

    Hey!

    Both should actually work, but you're right that your first code block (which uses route names instead of URLs) is typically more recommended. The 2.4/2.5 update will use the route names like you have here :).

    Thanks!

  • 2014-06-27 weaverryan

    Hey there!

    That message usually means that you've forgotten the "return" statement in your controller that renders the login page. Try checking for that! Fortunately, this isn't a security thing - just a missing return statement :).

    Cheers!

  • 2014-06-27 somecallmetim27

    Figured out the problem. You need the following under the 'secured_area:' under your 'firewalls:' in security.yml

    form_login:
    check_path: login_check
    login_path: login

    instead of

    form_login:
    check_path: /login/check
    login_path: /login

    This forum doesn't allow tab characters, apparently, so the above may look a little funny compared to what it should look like in your actual file...

  • 2014-06-26 somecallmetim27

    This is the exact message that is continually generated, in case anyone is curious...

    The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?
    500 Internal Server Error - LogicException

  • 2014-06-26 somecallmetim27

    The default security.yml file is nearly empty compared to the one in 2.2. Also, using the instructions contained herein, at least up through the first 11 minutes, anyway, doesn't appear to function. You can get the right/expected results up through the point where you try to login for the first time. Now, there's some code missing from security.yml, but even when you paste that missing code in you get a Symfony 500 internal server error message. I've tinkered with this for a bit and can't get it to work. Granted, I'm brand new to web development, so someone who has worked with other frameworks might be able to figure out where the problem is and jump in and fix it. That being said, this video definitely doesn't work as is.

    Bottom line, if you're brand new to web programming, you might want to wait until the 2.4+ version is released...

  • 2014-06-25 somecallmetim27

    Is this tutorial still relevant? If I'm using 2.5, is this worth picking up?

  • 2014-06-24 weaverryan

    Hi Marco!

    Hmm, make sure your access_control looks exactly like what we have here: http://knpuniversity.com/scree... - including the "roles: ROLE_USER". Also make sure that "/new" and "/create" are *real* functional pages on your site (they should be, but if they are 404 pages for some reason, you won't be asked for credentials).

    Let me know what you're seeing and I'll do my best to help :).

    Cheers!

  • 2014-06-23 Marco

    Hi, I'm trying to follow the lesson but I have a problem with "pattern" parameter in security.yml . Although everything is set like in the video ("pattern: ^/"), when I try to add new paths under "access_control:" using the pattern (like "^/new" or "^/create"), it doesn't ask for login credentials. Can anybody help me please?

  • 2014-05-19 weaverryan

    If you buy 2.2, you'll get the 2.4 one automatically for free. So yes, it's free. But if you've coded along with episode 1, it'll be a bit smoother of at transition if episode 2 is also 2.4.

  • 2014-05-19 SPIRITED

    Just purchased the Episode1, So I will wait to digest it first, then go for Episode 2 next week, except if update/upgrade is free.

  • 2014-05-19 weaverryan

    I've been working on it already! Hopefully next week. Not a lot has really changed, so if you go through things now, you'll be ok :). But if you can wait a week and a half, then we'll have the new guy up there.

    Cheers!

  • 2014-05-19 SPIRITED

    When is v2.4 be ready, or shouldn't be worried about it if you have got v2.2?

  • 2014-05-06 weaverryan

    I don't have an ETA, but this episode will be next and I'm hoping to be working on it this month :). It's definitely high on my list!

  • 2014-05-03 Adam

    Is there anywhere to sign up to be notified when the 2.4 screencasts are released? And do you have an ETA? Looking forward to them!

  • 2014-05-01 weaverryan

    Ah, of course! I should have guessed that you might have a YAML file! I'm glad you figured that out! Yes, we typically use YAML here, annotations there, etc etc - a big mix :). I'm glad you got it figured out, and I think it's more of a challenge (meaning, good for you) to watch it first and code afterwards.

    I'll update the note as you suggested - it's a good idea!

    Cheers!

  • 2014-05-01 Dizzy Bryan High

    well i restarted from scratch and it still created my table as User, totally ignoring the doctrine annotation, so i looked at the file structure, and noticed a doctrine folder in my resources!!! upon looking i saw a User.yml file there, so what i had done is created the entity using YML instead of annotation!!!, all working now i have recreated the entity using annotations.

    I think i made the mistake because the previous section had been dealing with the security.yml file, so i had yml in my head.
    there's no note about what to use for the user entity, might be an idea to append it to the existing tip, for idiots like me!!!

    Tip
    For entity shortcut name, use UserBundle:User. Also, choose "yes" to generating the repository class. (and choose the default annotation format)

    i actually watch the whole screencast first then do the lesson just using the pdf as its easier for me to follow along, than starting/stopping screen casts, i would have probably seen that the default annotations was used, if i had!!!

  • 2014-05-01 weaverryan

    Good plan - it's an odd issue - not one I've heard of before. Good luck!

  • 2014-04-30 Dizzy Bryan High

    Thanks i have the symfony plug-in installed, it was one of the reasons i decided to use PHPstorm as my IDE, It does pick up most things :) though still i needed to put in the annotation for it to see the methods and not show warnings.

  • 2014-04-30 Dizzy Bryan High

    I had tried the cache:clear, but it didn't seem to do anything, i tried removing the User Entity but still the same problem , in fact doctrine did not remove the table, it said:
    [Doctrine\Common\Persistence\Mapping\MappingException]
    Class 'Base5\UserBundle\Entity\User' does not exist

    I will completely remove the User Entity and start from scratch again tomorrow, just pasting back the code still causes the same problem the table created gets called User and not base5_user..
    starting again with fresh head and eyes is the best way forward!!!
    I will let you know if my redo is successful!

  • 2014-04-30 weaverryan

    Yep, that's odd - your annotation looks totally correct to me. You can read the docs directly about the @ORM\Table here (and you'll see you're using it correctly): http://docs.doctrine-project.o... (btw, we add ORM\ in front of the annotation in Symfony, but it makes no difference).

    If you keep having problems, I'd actually try temporarily removing this file completely (copy its contents, then delete it). Once you've done that, try running the doctrine:schema:update - you should see it drop the User table. I'm suggesting this just as a way to make sure that Doctrine is responding to *any* changes we make to this class :). It's also possible (though I've never heard of it), that you need to clear your Symfony cache. It's worth a shot :)

    php app/console cache:clear

    Let me know what you find out!

  • 2014-04-30 weaverryan

    Even better, install the Symfony community plugin for PHPStorm: http://plugins.jetbrains.com/p.... You can install it directly in the settings area. Afterwards, search the settings for "Symfony", because you'll need to enable it on a project-by-project basis. The plugin gives you all kinds of autocomplete that your editor shouldn't be smart enough to give you - I love it :).

    Thanks and good luck!

  • 2014-04-30 Dizzy Bryan High

    Hi i'm having a problem with:
    php app/console doctrine:schema:update --force
    Its adding my user table as 'User' and does not seem to be picking up the annotations in my User class:
    /**
    * User
    *
    * @ORM\Table(name="base5_user")
    * @ORM\Entity(repositoryClass="Base5\UserBundle\Entity\UserRepository")
    */

  • 2014-04-30 Dizzy Bryan High

    Ahh silly me, ive figured it out, just had to put the following annotation on the $container var

    /**
    * @var $container ContainerInterface
    */
    so that PHPstorm could pick it up!!!

  • 2014-04-30 Dizzy Bryan High

    I'm really enjoying the course, excellent work, your making it very easy to understand something very complex, I have a quick question though, which is probably to do with me using symfony 2.4, i am using phpstorm as my editor and in the LoadUser.php its highlighting a few things method not found in class:

    $encoder = $this->container->get('security.encoder_factory') .......

    I have tried googling to see if the get method has changed but cant find anything, worse comes to worse i will wait for the 2.4 update, was wondering if you had any ideas?

  • 2014-04-24 weaverryan

    Hi Jonny!

    I'm glad you're enjoying it :). Can you tell me more about your issue? Do you get an error? Or is something not working as you'd expect?

    Cheers!

  • 2014-04-24 Jonny

    Hi really enjoying the course, this snippet of code doesn't run in loadUserByUsername
    function.

    if (!$user) {

    throw new UsernameNotFoundException('No user found for username '.$username);

    }

  • 2014-04-12 weaverryan

    We're updating all the tutorials for Symfony 2.4 right now, which is causing this to be different temporarily. When we update everything for Symfony 2.4, it will all be consistent again :).

    Cheers!

  • 2014-04-11 weaverryan

    Hey keefekwan!

    Yes, there is a CSS file missing, which means that if you're coding along, the registration form works fine, but doesn't look as nice as in the screencast. Thanks for catching that! I've opened up an issue and we'll fix it and make it look pretty again for the next version: https://github.com/knpuniversi...

    Cheers!

  • 2014-04-09 Guest

    For the register form creation is there supposed to be another .css file? Currently, I am re-using the login form to use that template as it somewhat matches the video, I haven't spotted anything in the downloadable code.

    <section class="main-block">

  • 2014-04-09 Guest

    Cheers mate. Will keep an eye out, appreciate the great work.

  • 2014-04-08 weaverryan

    Thanks as always for the tip! I've updated the 2 spots that had this mistake and deployed it! If you find something in the future and want to create a pull request on the repo, feel free to do that as well. I'm certainly happy to make the changes, but I'd also love for you to get credit in the GitHub commits :) - https://github.com/knpuniversi...

    Cheers and keep going!

  • 2014-04-07 Guest

    Under 'Customizing Field Labels" - Need to change the second field: (to form.password.second) in the script file on the webpage.
    {{ form_row(form.password.first, {
    'label': 'Password'
    }) }}

    {{ form_row(form.password.first, {
    'label': 'Repeat Password'
    }) }}

  • 2014-04-06 Guest

    Figured it out, left in some code from the render portion in the loginAction method. :)

  • 2014-04-06 Guest

    Part 2 Basic Security: I got an error on the login_check setup at the point where you create the loginCheckAction(), then the annotation route then go back to browser I get the following:

    ContextErrorException: Notice: Array to string conversion in /var/www/html/test/vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Debug/TimedTwigEngine.php line 50

  • 2014-04-03 Guest

    Part 2 Basic Security:
    The twig template should extend {% extends 'EventBundle::layout.html.twig' %} not base, it works with base just off putting when it doesn't match the tutorial as your going through.

  • 2014-03-05 weaverryan

    Hi abiram!

    You're getting that error because we generate a URL to this route on the login form: http://knpuniversity.com/scree....

    Like normal routes, this route needs to be created. We do it via annotations here: http://knpuniversity.com/scree....

    So, make sure you create the route (either via annotations or in YAML), then double check via "app/console router:debug" to make sure the route is there. The confusing part is that the controller for this route doesn't need to do anything: we submit the login form to this URL, but then Symfony magically intercepts things and handles the login for us.

    Hope that helps!

  • 2014-03-05 abiram

    Hi,

    I got below error when i browse /login

    An exception has been thrown during the rendering of a template ("Unable to generate a URL for the named route "login_check" as such route does not exist

    Please Help me how to solve this error

  • 2014-03-04 weaverryan

    Hey Mathias!

    Yes, there were actually 2 changes to bind:

    1) bind was renamed to submit
    2) Passing a Request object to submit was deprecated. It's still completely valid to pass an array of data to submit (that's its purpose), but if you want to pass a Request object, use handleRequest. The reason for this second change was performance - one function for handing processing the Request object (handleRequest) and another for handling data arrays (submit).

    The big takeaway is to use handleRequest almost always. The only exception is if you are passing the array of POST data directly (pretty uncommon), then use submit(). We'll be working to update this screencast with the latest and greatest over the next several weeks too :).

    Hope that clarifies!

  • 2014-02-28 Mathias Strasser

    Hi,

    Now, bind redirect to submit.
    So why use handleRequest ?

  • 2014-02-17 weaverryan

    Cheers :). We're updating these 4 episodes right now anyways to catch these deprecations and get them up to date!

  • 2014-02-16 fabienne

    I actually do read the comments before I do the tutorials, exactly because I don't want to get stuck as some place just because something is deprecated or anything else changed;)

  • 2014-02-01 Thomas

    Okay, I guess I will just assert something that will for sure be static. I was hoping to assert using the full title that might be there and my client hasn't decided on the name of the project so I am using a placeholder but in code I am using a translation string where the name is displayed so when he decides I change it in one spot and boom it's done.

    Thanks.

  • 2014-02-01 weaverryan

    Hey Thomas!

    You should hardcode the strings you expect to see in the test. This is by design - the behavior of your application says that certain text should be on the page, so you will use these hardcoded (not dynamically loaded from somewhere) strings.

    Cheers!

  • 2014-02-01 Thomas

    I am modifying my tests and now that the translations are working while using the FOSUserBundle I'd like to use the keys in my functional tests inside my asserts. How can I retrieve the translation strings in a PHP function?

  • 2014-02-01 Thomas

    Yep. I was going to post the answer here too just in case anyone else used the REST distro and had the same problem. I also posted it as a possible answer on the stackoverflow question.

    Thanks again!

  • 2014-02-01 weaverryan

    Hey Thomas!

    I know we already discussed this, but I wanted to post your answer here. The flashbag has not changed - you're using it perfectly. But since you were using the LiipCacheControlBundle (as part of the REST distribution) with the flash listener on, the flash messages were being removed by that bundle.

    Anyways, I'm glad we got that figured out :).

    Cheers!

  • 2014-01-30 Thomas

    has the usage of the flashbag changed? that whole section with if `app.session.flashbag.get` does not work at all. I tried `dump(app.session.flashbag.all())` and it gives an empty array but I see it in my request cookies http://i.imgur.com/2TJPnvU.png .

    I even used xdebug to look at the request variable and it is there http://i.imgur.com/ANOovML.png . so I know it gets set. it's a matter of the retrieving it to print it. I have scoured the web for a solution but the official, current, docs ( http://symfony.com/doc/current... )have it the way you have it.

    I have tried it both `$this->get('session')` (like in the docs) `$request->getSession()` (which i assume is just a shortcut that points to the same thing and the result is the exact same thing.

  • 2014-01-30 Thomas

    I don't know what I did the first time around. Working on a new project I now have a UserBundle and it didn't give any problems. disregard my n00b statement. :)

  • 2014-01-21 weaverryan

    Thanks! Let me know what you find out :)

  • 2014-01-21 Thomas

    The error it gave was using the console generator. I will have to do it again and get the exact error message. I'll get back to you :)

  • 2014-01-21 weaverryan

    Hey Thomas!

    Was the error because of the table name (i.e. it was trying to create a table called "user") or something else? The table name should be "yoda_user" - we controlled it here: https://github.com/knpuniversi...

    Let me know one way or another - if there's an issue we can fix or even an easy mistake we can prevent with some extra direction, let's do it!

    Cheers!