diff --git a/cookbook/doctrine/registration_form.rst b/cookbook/doctrine/registration_form.rst index 418c4f1a4e6..ac6f7899943 100644 --- a/cookbook/doctrine/registration_form.rst +++ b/cookbook/doctrine/registration_form.rst @@ -5,15 +5,21 @@ How to Implement a simple Registration Form =========================================== -Some forms have extra fields whose values don't need to be stored in the -database. For example, you may want to create a registration form with some -extra fields (like a "terms accepted" checkbox field) and embed the form -that actually stores the account information. +Creating a registration form is pretty easy - it *really* means just creating +a form that will update some ``User`` object (a Doctrine entity in this example) +and then save it. -The simple User Model ---------------------- +If you don't already have a ``User`` entity and a working login system, +first start with :doc:`/cookbook/security/entity_provider`. -You have a simple ``User`` entity mapped to the database:: +Your ``User`` entity will probably at least have the following fields: +* ``email`` +* ``username`` +* ``password`` (the encoded password) +* ``plainPassword`` (*not* persisted: notice no ``@ORM\Column`` above it) +* anything else you want + +With some validation added, it may look something like this:: // src/AppBundle/Entity/User.php namespace AppBundle\Entity; @@ -21,12 +27,14 @@ You have a simple ``User`` entity mapped to the database:: use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; + use Symfony\Component\Security\Core\User\UserInterface; /** * @ORM\Entity * @UniqueEntity(fields="email", message="Email already taken") + * @UniqueEntity(fields="username", message="Username already taken") */ - class User + class User implements UserInterface { /** * @ORM\Id @@ -44,11 +52,26 @@ You have a simple ``User`` entity mapped to the database:: /** * @ORM\Column(type="string", length=255) + * @Assert\NotBlank() + */ + protected $username; + + /** * @Assert\NotBlank() * @Assert\Length(max = 4096) */ protected $plainPassword; + /** + * The below length depends on the "algorithm" you use for encoding + * the password, but this works well with bcrypt + * + * @ORM\Column(type="string", length=64) + */ + protected $password; + + // other properties + public function getId() { return $this->id; @@ -64,6 +87,16 @@ You have a simple ``User`` entity mapped to the database:: $this->email = $email; } + public function getUsername() + { + return $this->username; + } + + public function setUsername($username) + { + $this->username = $username; + } + public function getPlainPassword() { return $this->plainPassword; @@ -73,18 +106,16 @@ You have a simple ``User`` entity mapped to the database:: { $this->plainPassword = $password; } - } -This ``User`` entity contains three fields and two of them (``email`` and -``plainPassword``) should display on the form. The email property must be unique -in the database, this is enforced by adding this validation at the top of -the class. + public function setPassword($password) + { + $this->password = $password; + } + + // other methods, including security methods like getRoles() + } -.. note:: - If you want to integrate this User within the security system, you need - to implement the :ref:`UserInterface ` of the - Security component. .. _cookbook-registration-password-max: @@ -104,10 +135,10 @@ the class. Create a Form for the Model --------------------------- -Next, create the form for the ``User`` model:: +Next, create the form for the ``User`` entity:: - // src/AppBundle/Form/Type/UserType.php - namespace AppBundle\Form\Type; + // src/AppBundle/Form/UserType.php + namespace AppBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -117,12 +148,13 @@ Next, create the form for the ``User`` model:: { public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->add('email', 'email'); - $builder->add('plainPassword', 'repeated', array( - 'first_name' => 'password', - 'second_name' => 'confirm', - 'type' => 'password', - )); + $builder + ->add('email', 'email'); + ->add('username', 'text'); + ->add('plainPassword', 'repeated', array( + 'type' => 'password', + ) + ); } public function setDefaultOptions(OptionsResolverInterface $resolver) @@ -138,98 +170,13 @@ Next, create the form for the ``User`` model:: } } -There are just two fields: ``email`` and ``plainPassword`` (repeated to confirm -the entered password). The ``data_class`` option tells the form the name of the -underlying data class (i.e. your ``User`` entity). +There are just three fields: ``email``, ``username`` and ``plainPassword`` +(repeated to confirm the entered password). .. tip:: To explore more things about the Form component, read :doc:`/book/forms`. -Embedding the User Form into a Registration Form ------------------------------------------------- - -The form that you'll use for the registration page is not the same as the -form used to simply modify the ``User`` (i.e. ``UserType``). The registration -form will contain further fields like "accept the terms", whose value won't -be stored in the database. - -Start by creating a simple class which represents the "registration":: - - // src/AppBundle/Form/Model/Registration.php - namespace AppBundle\Form\Model; - - use Symfony\Component\Validator\Constraints as Assert; - - use AppBundle\Entity\User; - - class Registration - { - /** - * @Assert\Type(type="AppBundle\Entity\User") - * @Assert\Valid() - */ - protected $user; - - /** - * @Assert\NotBlank() - * @Assert\True() - */ - protected $termsAccepted; - - public function setUser(User $user) - { - $this->user = $user; - } - - public function getUser() - { - return $this->user; - } - - public function getTermsAccepted() - { - return $this->termsAccepted; - } - - public function setTermsAccepted($termsAccepted) - { - $this->termsAccepted = (bool) $termsAccepted; - } - } - -Next, create the form for this ``Registration`` model:: - - // src/AppBundle/Form/Type/RegistrationType.php - namespace AppBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - - class RegistrationType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('user', new UserType()); - $builder->add( - 'terms', - 'checkbox', - array('property_path' => 'termsAccepted') - ); - $builder->add('Register', 'submit'); - } - - public function getName() - { - return 'registration'; - } - } - -You don't need to use a special method for embedding the ``UserType`` form. -A form is a field, too - so you can add this like any other field, with the -expectation that the ``Registration.user`` property will hold an instance -of the ``User`` class. - Handling the Form Submission ---------------------------- @@ -241,127 +188,125 @@ controller for displaying the registration form:: use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use AppBundle\Form\Type\RegistrationType; - use AppBundle\Form\Model\Registration; + use AppBundle\Form\UserType; + use AppBundle\Entity\User; + use Symfony\Component\HttpFoundation\Request; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - class AccountController extends Controller + class RegistrationController extends Controller { - public function registerAction() + /** + * @Route("/register", name="user_registration") + */ + public function registerAction(Request $request) { - $registration = new Registration(); - $form = $this->createForm(new RegistrationType(), $registration, array( - 'action' => $this->generateUrl('account_create'), - )); + // 1) build the form + $user = new User(); + $form = $this->createForm(new UserType(), $user); + + // 2) handle the submit (will only happen on POST) + $form->handleRequest($request); + if ($form->isValid()) { + // save the User! + $em = $this->getDoctrine()->getManager(); + $em->persist($user); + $em->flush(); + + // do any other work - like send them an email, etc + // maybe set a "flash" success message for the user + + $redirectUrl = $this->generateUrl('replace_with_some_route'); + + return $this->redirect($redirectUrl); + } return $this->render( - 'AppBundle:Account:register.html.twig', + 'registration/register.html.twig', array('form' => $form->createView()) ); } } -And its template: - -.. code-block:: html+jinja - - {# src/AppBundle/Resources/views/Account/register.html.twig #} - {{ form(form) }} - -Next, create the controller which handles the form submission. This performs -the validation and saves the data into the database:: - - use Symfony\Component\HttpFoundation\Request; - // ... - - public function createAction(Request $request) - { - $em = $this->getDoctrine()->getManager(); - - $form = $this->createForm(new RegistrationType(), new Registration()); - - $form->handleRequest($request); - - if ($form->isValid()) { - $registration = $form->getData(); - - $em->persist($registration->getUser()); - $em->flush(); - - return $this->redirect(...); - } +.. note:: - return $this->render( - 'AppBundle:Account:register.html.twig', - array('form' => $form->createView()) - ); - } + If you decide to NOT use annotation routing (shown above), then you'll + need to create a route to this controller: + + .. configuration-block:: -Add new Routes --------------- + .. code-block:: yaml -Next, update your routes. If you're placing your routes inside your bundle -(as shown here), don't forget to make sure that the routing file is being -:ref:`imported `. + # app/config/routing.yml + user_registration: + path: /register + defaults: { _controller: AppBundle:Registration:register } -.. configuration-block:: + .. code-block:: xml - .. code-block:: yaml + + + - # src/AppBundle/Resources/config/routing.yml - account_register: - path: /register - defaults: { _controller: AppBundle:Account:register } + + AppBundle:Registration:register + + - account_create: - path: /register/create - defaults: { _controller: AppBundle:Account:create } + .. code-block:: php - .. code-block:: xml + // app/config/routing.php + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; - - - + $collection = new RouteCollection(); + $collection->add('user_registration', new Route('/register', array( + '_controller' => 'AppBundle:Registration:register', + ))); - - AppBundle:Account:register - + return $collection; - - AppBundle:Account:create - - +Next, create the template: - .. code-block:: php +.. code-block:: html+jinja - // src/AppBundle/Resources/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; + {# app/Resources/views/registration/register.html.twig #} + + {{ form_start(form) }} + {{ form_row('form.username') }} + {{ form_row('form.email') }} - $collection = new RouteCollection(); - $collection->add('account_register', new Route('/register', array( - '_controller' => 'AppBundle:Account:register', - ))); - $collection->add('account_create', new Route('/register/create', array( - '_controller' => 'AppBundle:Account:create', - ))); + {{ form_row('form.plainPassword.first', { + 'label': 'Password' + }) }} + {{ form_row('form.plainPassword.second', { + 'label': 'Repeat Password' + }) }} - return $collection; + + {{ form_end(form) }} Update your Database Schema --------------------------- -Of course, since you've added a ``User`` entity during this tutorial, make -sure that your database schema has been updated properly: +If you've updated the ``User`` entity during this tutorial, make sure that +your database schema has been updated properly: .. code-block:: bash $ php app/console doctrine:schema:update --force -That's it! Your form now validates, and allows you to save the ``User`` -object to the database. The extra ``terms`` checkbox on the ``Registration`` -model class is used during validation, but not actually used afterwards when -saving the User to the database. +That's it! Head to ``/register`` to try things out! + +Having a Registration form with only Email (no Username) +-------------------------------------------------------- + +Todo + +Adding a "accept terms" Checkbox +-------------------------------- + +Todo .. _`CVE-2013-5750`: https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form