@@ -6,20 +6,37 @@ How to Implement a simple Registration Form
66===========================================
77
88Creating a registration form is pretty easy - it *really * means just creating
9- a form that will update some ``User `` object (a Doctrine entity in this example)
9+ a form that will update some ``User `` model object (a Doctrine entity in this example)
1010and then save it.
1111
12+ .. tip ::
13+
14+ The popular `FOSUserBundle `_ provides a registration form, reset password form
15+ and other user management functionality.
16+
1217If you don't already have a ``User `` entity and a working login system,
1318first start with :doc: `/cookbook/security/entity_provider `.
1419
1520Your ``User `` entity will probably at least have the following fields:
16- * ``email ``
17- * ``username ``
18- * ``password `` (the encoded password)
19- * ``plainPassword `` (*not * persisted: notice no ``@ORM\Column `` above it)
20- * anything else you want
2121
22- With some validation added, it may look something like this::
22+ ``username ``
23+ This will be used for logging in, unless you instead want your user to
24+ :ref: `login via email <registration-form-via-email >` (in that case, this
25+ field is unnecessary).
26+
27+ ``email ``
28+ A nice piece of information to collect. You can also allow users to
29+ :ref: `login via email <registration-form-via-email >`.
30+
31+ * ``password ``
32+ The encoded password.
33+
34+ * ``plainPassword ``
35+ This field is *not * persisted: (notice no ``@ORM\Column `` above it). It
36+ temporarily stores the plain password from the registration form. This field
37+ can be validated then used to populate the ``password `` field.
38+
39+ With some validation added, your class may look something like this::
2340
2441 // src/AppBundle/Entity/User.php
2542 namespace AppBundle\Entity;
@@ -41,41 +58,36 @@ With some validation added, it may look something like this::
4158 * @ORM\Column(type="integer")
4259 * @ORM\GeneratedValue(strategy="AUTO")
4360 */
44- protected $id;
61+ private $id;
4562
4663 /**
4764 * @ORM\Column(type="string", length=255)
4865 * @Assert\NotBlank()
4966 * @Assert\Email()
5067 */
51- protected $email;
68+ private $email;
5269
5370 /**
5471 * @ORM\Column(type="string", length=255)
5572 * @Assert\NotBlank()
5673 */
57- protected $username;
74+ private $username;
5875
5976 /**
6077 * @Assert\NotBlank()
6178 * @Assert\Length(max = 4096)
6279 */
63- protected $plainPassword;
80+ private $plainPassword;
6481
6582 /**
6683 * The below length depends on the "algorithm" you use for encoding
6784 * the password, but this works well with bcrypt
6885 *
6986 * @ORM\Column(type="string", length=64)
7087 */
71- protected $password;
88+ private $password;
7289
73- // other properties
74-
75- public function getId()
76- {
77- return $this->id;
78- }
90+ // other properties and methods
7991
8092 public function getEmail()
8193 {
@@ -115,7 +127,9 @@ With some validation added, it may look something like this::
115127 // other methods, including security methods like getRoles()
116128 }
117129
118-
130+ The ``UserInterface `` requires a few other methods and your ``security.yml `` file
131+ needs to be configured properly to work with the ``User `` entity. For a more full
132+ example, see the :ref: `Entity Provider <security-crete-user-entity >` article.
119133
120134.. _cookbook-registration-password-max :
121135
@@ -152,7 +166,9 @@ Next, create the form for the ``User`` entity::
152166 ->add('email', 'email');
153167 ->add('username', 'text');
154168 ->add('plainPassword', 'repeated', array(
155- 'type' => 'password',
169+ 'type' => 'password',
170+ 'first_options' => array('label' => 'Password'),
171+ 'second_options' => array('label' => 'Repeat Password'),
156172 )
157173 );
158174 }
@@ -206,13 +222,19 @@ controller for displaying the registration form::
206222
207223 // 2) handle the submit (will only happen on POST)
208224 $form->handleRequest($request);
209- if ($form->isValid()) {
210- // save the User!
225+ if ($form->isValid() && $form->isSubmitted()) {
226+ // 3) Encode the password (you could also do this via Doctrine listener)
227+ $encoder = $this->get('security.encoder_factory')
228+ ->getEncoder($user);
229+ $password = $encoder->encodePassword($user->getPlainPassword(), $user->getSalt());
230+ $user->setPassword($password);
231+
232+ // 4) save the User!
211233 $em = $this->getDoctrine()->getManager();
212234 $em->persist($user);
213235 $em->flush();
214236
215- // do any other work - like send them an email, etc
237+ // ... do any other work - like send them an email, etc
216238 // maybe set a "flash" success message for the user
217239
218240 $redirectUrl = $this->generateUrl('replace_with_some_route');
@@ -269,44 +291,109 @@ controller for displaying the registration form::
269291
270292 Next, create the template:
271293
272- .. code -block :: html+jinja
294+ .. configuration -block ::
273295
274- {# app/Resources/views/registration/register.html.twig #}
296+ .. code-block :: html+jinja
297+
298+ {# app/Resources/views/registration/register.html.twig #}
299+
300+ {{ form_start(form) }}
301+ {{ form_row('form.username') }}
302+ {{ form_row('form.email') }}
303+ {{ form_row('form.plainPassword.first') }}
304+ {{ form_row('form.plainPassword.second') }}
305+
306+ <button type="submit">Register!</button>
307+ {{ form_end(form) }}
275308
276- {{ form_start(form) }}
277- {{ form_row('form.username') }}
278- {{ form_row('form.email') }}
309+ .. code-block :: html+php
310+
311+ <!-- app/Resources/views/registration/register.html.php -->
279312
280- {{ form_row('form.plainPassword.first', {
281- 'label': 'Password'
282- }) }}
283- {{ form_row('form.plainPassword.second', {
284- 'label': 'Repeat Password'
285- }) }}
313+ <?php echo $view['form']->start($form) ?>
314+ <?php echo $view['form']->row($form['username']) ?>
315+ <?php echo $view['form']->row($form['email']) ?>
286316
287- <button type="submit">Register!</button>
288- {{ form_end(form) }}
317+ <?php echo $view['form']->row($form['plainPassword']['first']) ?>
318+ <?php echo $view['form']->row($form['plainPassword']['second']) ?>
319+
320+ <button type="submit">Register!</button>
321+ <?php echo $view['form']->end($form) ?>
322+
323+ See :doc: `/cookbook/form/form_customization ` for more details.
289324
290325Update your Database Schema
291326---------------------------
292327
293- If you've updated the `` User `` entity during this tutorial, make sure that
294- your database schema has been updated properly :
328+ If you've updated the User entity during this tutorial, you have to update your
329+ database schema using this command :
295330
296331.. code-block :: bash
297332
298333 $ php app/console doctrine:schema:update --force
299334
300335 That's it! Head to ``/register `` to try things out!
301336
337+ .. _registration-form-via-email :
338+
302339Having a Registration form with only Email (no Username)
303340--------------------------------------------------------
304341
305- Todo
342+ If you want your users to login via email and you don't need a username, then you
343+ can remove it from your ``User `` entity entirely. Instead, make ``getUsername() ``
344+ return the ``email `` property.
345+
346+ // src/AppBundle/Entity/User.php
347+ // ...
348+
349+ class User implements UserInterface
350+ {
351+ // ...
352+
353+ public function getUsername()
354+ {
355+ return $this->email;
356+ }
357+
358+ // ...
359+ }
360+
361+ Next, just update the ``providers `` section of your ``security.yml `` so that Symfony
362+ knows to load your users via the ``email `` property on login. See
363+ :ref: `authenticating-someone-with-a-custom-entity-provider `.
306364
307365Adding a "accept terms" Checkbox
308366--------------------------------
309367
310- Todo
368+ Sometimes, you want a "Do you accept the terms and conditions" checkbox on your
369+ registration form. The only trick is that you want to add this field to your form
370+ without adding an unnecessary new ``termsAccepted `` property to your ``User `` entity
371+ that you'll never need.
372+
373+ To do this, add a ``termsAccepted `` field to your form, but set its :ref: `mapped <reference-form-option-mapped >`
374+ option to ``false ``::
375+
376+ // src/AppBundle/Form/UserType.php
377+ // ...
378+ use Symfony\\Component\\Validator\\Constraints\\IsTrue;
379+
380+ class UserType extends AbstractType
381+ {
382+ public function buildForm(FormBuilderInterface $builder, array $options)
383+ {
384+ $builder
385+ ->add('email', 'email');
386+ // ...
387+ ->add('termsAccepted', 'checkbox', array(
388+ 'mapped' => false,
389+ 'constraints' => new IsTrue(),
390+ ))
391+ );
392+ }
393+ }
394+
395+ The :ref: `constraints <form-option-constraints >` option is also used, which allows
396+ us to add validation, even though there is no ``termsAccepted `` property on ``User ``.
311397
312398.. _`CVE-2013-5750` : https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form
399+ .. _`FOSUserBundle` : https://github.com/FriendsOfSymfony/FOSUserBundle
0 commit comments