Skip to content

Commit 57d9cf2

Browse files
committed
[Cookbook] Make registration_form follow best practices
1 parent 1972757 commit 57d9cf2

File tree

1 file changed

+105
-128
lines changed

1 file changed

+105
-128
lines changed
+105-128
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.. index::
22
single: Doctrine; Simple Registration Form
33
single: Form; Simple Registration Form
4+
single: Security; Simple Registration Form
45

56
How to Implement a simple Registration Form
67
===========================================
@@ -10,13 +11,13 @@ database. For example, you may want to create a registration form with some
1011
extra fields (like a "terms accepted" checkbox field) and embed the form
1112
that actually stores the account information.
1213

13-
The simple User Model
14-
---------------------
14+
The simple User Entity
15+
----------------------
1516

1617
You have a simple ``User`` entity mapped to the database::
1718

18-
// src/Acme/AccountBundle/Entity/User.php
19-
namespace Acme\AccountBundle\Entity;
19+
// src/AppBundle/Entity/User.php
20+
namespace AppBundle\Entity;
2021

2122
use Doctrine\ORM\Mapping as ORM;
2223
use Symfony\Component\Validator\Constraints as Assert;
@@ -36,7 +37,7 @@ You have a simple ``User`` entity mapped to the database::
3637
protected $id;
3738

3839
/**
39-
* @ORM\Column(type="string", length=255)
40+
* @ORM\Column(type="string", length=255, unique=true)
4041
* @Assert\NotBlank()
4142
* @Assert\Email()
4243
*/
@@ -45,9 +46,9 @@ You have a simple ``User`` entity mapped to the database::
4546
/**
4647
* @ORM\Column(type="string", length=255)
4748
* @Assert\NotBlank()
48-
* @Assert\Length(max = 4096)
49+
* @Assert\Length(max=4096)
4950
*/
50-
protected $plainPassword;
51+
protected $password;
5152

5253
public function getId()
5354
{
@@ -64,21 +65,27 @@ You have a simple ``User`` entity mapped to the database::
6465
$this->email = $email;
6566
}
6667

67-
public function getPlainPassword()
68+
public function getPassword()
69+
{
70+
return $this->password;
71+
}
72+
73+
public function setPassword($password)
6874
{
69-
return $this->plainPassword;
75+
$this->password = $password;
7076
}
7177

72-
public function setPlainPassword($password)
78+
public function getSalt()
7379
{
74-
$this->plainPassword = $password;
80+
return null;
7581
}
7682
}
7783

7884
This ``User`` entity contains three fields and two of them (``email`` and
79-
``plainPassword``) should display on the form. The email property must be unique
80-
in the database, this is enforced by adding this validation at the top of
81-
the class.
85+
``password``) should be displayed by the form. The ``email`` property must
86+
be unique in the database, this is enforced by adding an ``@UniqueEntity``
87+
validation constraint at the top of the class for application-side validation
88+
and by adding ``unique=true`` to the column mapping for the database schema.
8289

8390
.. note::
8491

@@ -90,7 +97,7 @@ the class.
9097

9198
.. sidebar:: Why the 4096 Password Limit?
9299

93-
Notice that the ``plainPassword`` field has a max length of 4096 characters.
100+
Notice that the ``password`` field has a max length of 4096 characters.
94101
For security purposes (`CVE-2013-5750`_), Symfony limits the plain password
95102
length to 4096 characters when encoding it. Adding this constraint makes
96103
sure that your form will give a validation error if anyone tries a super-long
@@ -101,13 +108,13 @@ the class.
101108
only place where you don't need to worry about this is your login form,
102109
since Symfony's Security component handles this for you.
103110

104-
Create a Form for the Model
105-
---------------------------
111+
Create a Form for the Entity
112+
----------------------------
106113

107-
Next, create the form for the ``User`` model::
114+
Next, create the form for the ``User`` entity::
108115

109-
// src/Acme/AccountBundle/Form/Type/UserType.php
110-
namespace Acme\AccountBundle\Form\Type;
116+
// src/AppBundle/Form/Type/UserType.php
117+
namespace AppBundle\Form\Type;
111118

112119
use Symfony\Component\Form\AbstractType;
113120
use Symfony\Component\Form\FormBuilderInterface;
@@ -118,17 +125,17 @@ Next, create the form for the ``User`` model::
118125
public function buildForm(FormBuilderInterface $builder, array $options)
119126
{
120127
$builder->add('email', 'email');
121-
$builder->add('plainPassword', 'repeated', array(
122-
'first_name' => 'password',
123-
'second_name' => 'confirm',
124-
'type' => 'password',
128+
$builder->add('password', 'repeated', array(
129+
'first_name' => 'password',
130+
'second_name' => 'confirm',
131+
'type' => 'password',
125132
));
126133
}
127134

128135
public function setDefaultOptions(OptionsResolverInterface $resolver)
129136
{
130137
$resolver->setDefaults(array(
131-
'data_class' => 'Acme\AccountBundle\Entity\User'
138+
'data_class' => 'AppBundle\Entity\User'
132139
));
133140
}
134141

@@ -138,7 +145,7 @@ Next, create the form for the ``User`` model::
138145
}
139146
}
140147

141-
There are just two fields: ``email`` and ``plainPassword`` (repeated to confirm
148+
There are just two fields: ``email`` and ``password`` (repeated to confirm
142149
the entered password). The ``data_class`` option tells the form the name of the
143150
underlying data class (i.e. your ``User`` entity).
144151

@@ -156,17 +163,17 @@ be stored in the database.
156163

157164
Start by creating a simple class which represents the "registration"::
158165

159-
// src/Acme/AccountBundle/Form/Model/Registration.php
160-
namespace Acme\AccountBundle\Form\Model;
166+
// src/AppBundle/Form/Model/Registration.php
167+
namespace AppBundle\Form\Model;
161168

162169
use Symfony\Component\Validator\Constraints as Assert;
163170

164-
use Acme\AccountBundle\Entity\User;
171+
use AppBundle\Entity\User;
165172

166173
class Registration
167174
{
168175
/**
169-
* @Assert\Type(type="Acme\AccountBundle\Entity\User")
176+
* @Assert\Type(type="AppBundle\Entity\User")
170177
* @Assert\Valid()
171178
*/
172179
protected $user;
@@ -200,8 +207,8 @@ Start by creating a simple class which represents the "registration"::
200207

201208
Next, create the form for this ``Registration`` model::
202209

203-
// src/Acme/AccountBundle/Form/Type/RegistrationType.php
204-
namespace Acme\AccountBundle\Form\Type;
210+
// src/AppBundle/Form/Type/RegistrationType.php
211+
namespace AppBundle\Form\Type;
205212

206213
use Symfony\Component\Form\AbstractType;
207214
use Symfony\Component\Form\FormBuilderInterface;
@@ -211,11 +218,9 @@ Next, create the form for this ``Registration`` model::
211218
public function buildForm(FormBuilderInterface $builder, array $options)
212219
{
213220
$builder->add('user', new UserType());
214-
$builder->add(
215-
'terms',
216-
'checkbox',
217-
array('property_path' => 'termsAccepted')
218-
);
221+
$builder->add('termsAccepted', 'checkbox', array(
222+
'label' => 'Terms accepted',
223+
));
219224
$builder->add('Register', 'submit');
220225
}
221226

@@ -233,121 +238,93 @@ of the ``User`` class.
233238
Handling the Form Submission
234239
----------------------------
235240

236-
Next, you need a controller to handle the form. Start by creating a simple
237-
controller for displaying the registration form::
241+
Next, you need a controller to handle the form rendering and submission. If the
242+
form is submitted, the controller performs the validation and saves the data
243+
into the database::
238244

239-
// src/Acme/AccountBundle/Controller/AccountController.php
240-
namespace Acme\AccountBundle\Controller;
245+
// src/AppBundle/Controller/AccountController.php
246+
namespace AppBundle\Controller;
241247

242248
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
249+
use Symfony\Component\HttpFoundation\Request;
250+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
243251

244-
use Acme\AccountBundle\Form\Type\RegistrationType;
245-
use Acme\AccountBundle\Form\Model\Registration;
252+
use AppBundle\Form\Type\RegistrationType;
253+
use AppBundle\Form\Model\Registration;
246254

247255
class AccountController extends Controller
248256
{
249-
public function registerAction()
257+
/**
258+
* @Route("/register", name="account_register")
259+
*/
260+
public function registerAction(Request $request)
250261
{
251-
$registration = new Registration();
252-
$form = $this->createForm(new RegistrationType(), $registration, array(
253-
'action' => $this->generateUrl('account_create'),
254-
));
262+
$form = $this->createForm(new RegistrationType(), new Registration());
255263

256-
return $this->render(
257-
'AcmeAccountBundle:Account:register.html.twig',
258-
array('form' => $form->createView())
259-
);
260-
}
261-
}
264+
$form->handleRequest($request);
262265

263-
And its template:
266+
if ($form->isSubmitted() && $form->isValid()) {
267+
$registration = $form->getData();
268+
$user = $registration->getUser();
264269

265-
.. code-block:: html+jinja
270+
$password = $this
271+
->get('security.encoder_factory')
272+
->getEncoder($user)
273+
->encodePassword(
274+
$user->getPassword(),
275+
$user->getSalt()
276+
);
277+
$user->setPassword($password);
266278

267-
{# src/Acme/AccountBundle/Resources/views/Account/register.html.twig #}
268-
{{ form(form) }}
279+
$em = $this->getDoctrine()->getManager();
280+
$em->persist($user);
281+
$em->flush();
269282

270-
Next, create the controller which handles the form submission. This performs
271-
the validation and saves the data into the database::
272-
273-
use Symfony\Component\HttpFoundation\Request;
274-
// ...
283+
return $this->redirect($this->generateUrl('homepage'));
284+
}
275285

276-
public function createAction(Request $request)
277-
{
278-
$em = $this->getDoctrine()->getManager();
279-
280-
$form = $this->createForm(new RegistrationType(), new Registration());
281-
282-
$form->handleRequest($request);
283-
284-
if ($form->isValid()) {
285-
$registration = $form->getData();
286-
287-
$em->persist($registration->getUser());
288-
$em->flush();
289-
290-
return $this->redirect(...);
286+
return $this->render(
287+
'account/register.html.twig',
288+
array('form' => $form->createView())
289+
);
291290
}
292-
293-
return $this->render(
294-
'AcmeAccountBundle:Account:register.html.twig',
295-
array('form' => $form->createView())
296-
);
297291
}
298292

299-
Add new Routes
300-
--------------
301-
302-
Next, update your routes. If you're placing your routes inside your bundle
303-
(as shown here), don't forget to make sure that the routing file is being
304-
:ref:`imported <routing-include-external-resources>`.
305-
306-
.. configuration-block::
293+
Storing plain-text passwords is bad practice, so before saving the user
294+
data into the database the submitted plain-text password is replaced by
295+
an encoded one. To define the algorithm used to encode the password
296+
configure the encoder in the security configuration:
307297

308-
.. code-block:: yaml
298+
.. code-block:: yaml
309299
310-
# src/Acme/AccountBundle/Resources/config/routing.yml
311-
account_register:
312-
path: /register
313-
defaults: { _controller: AcmeAccountBundle:Account:register }
300+
# app/config/security.yml
301+
security:
302+
encoders:
303+
AppBundle\Entity\User: bcrypt
314304
315-
account_create:
316-
path: /register/create
317-
defaults: { _controller: AcmeAccountBundle:Account:create }
305+
In this case the recommended ``bcrypt`` algorithm is used. To learn more
306+
about how to encode the users password have a look into the
307+
:ref:`security chapter <book-security-encoding-user-password>`
318308

319-
.. code-block:: xml
320-
321-
<!-- src/Acme/AccountBundle/Resources/config/routing.xml -->
322-
<?xml version="1.0" encoding="UTF-8" ?>
323-
<routes xmlns="http://symfony.com/schema/routing"
324-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
325-
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
309+
.. note::
326310

327-
<route id="account_register" path="/register">
328-
<default key="_controller">AcmeAccountBundle:Account:register</default>
329-
</route>
311+
While ``$form->isSubmitted()`` isn't technically needed, since isValid()
312+
first calls `isSubmitted()`, it is recommended to use it to improve
313+
readability.
330314

331-
<route id="account_create" path="/register/create">
332-
<default key="_controller">AcmeAccountBundle:Account:create</default>
333-
</route>
334-
</routes>
315+
And its template:
335316

336-
.. code-block:: php
317+
.. code-block:: html+jinja
337318

338-
// src/Acme/AccountBundle/Resources/config/routing.php
339-
use Symfony\Component\Routing\RouteCollection;
340-
use Symfony\Component\Routing\Route;
319+
{# app/Resources/views/account/register.html.twig #}
320+
{{ form(form) }}
341321

342-
$collection = new RouteCollection();
343-
$collection->add('account_register', new Route('/register', array(
344-
'_controller' => 'AcmeAccountBundle:Account:register',
345-
)));
346-
$collection->add('account_create', new Route('/register/create', array(
347-
'_controller' => 'AcmeAccountBundle:Account:create',
348-
)));
322+
Add new Routes
323+
--------------
349324

350-
return $collection;
325+
Don't forget to make sure that the routes defined as annotations in your
326+
controller are :ref:`loaded <routing-include-external-resources>` by your main
327+
routing configuration file.
351328

352329
Update your Database Schema
353330
---------------------------
@@ -360,8 +337,8 @@ sure that your database schema has been updated properly:
360337
$ php app/console doctrine:schema:update --force
361338
362339
That's it! Your form now validates, and allows you to save the ``User``
363-
object to the database. The extra ``terms`` checkbox on the ``Registration``
364-
model class is used during validation, but not actually used afterwards when
365-
saving the User to the database.
340+
object to the database. The extra ``termsAccepted`` checkbox on the
341+
``Registration`` model class is used during validation, but not actually used
342+
afterwards when saving the User to the database.
366343

367344
.. _`CVE-2013-5750`: http://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form

0 commit comments

Comments
 (0)