Как реализовать простую форму регистрации

Создать форму регистрации достаточно просто - это действительно значит просто создать форму, которая будет обновлять некоторый объект модели User (сущность Doctrine в этом примере) и потом сохранять его.

Tip

Популярный FOSUserBundle предоставляет форму регистрации, форму сброса пароля и другой функционал управления пользователем.

Если у вас еще нет сущности User и работающей системы входа, начните с провайдера сущностей.

Ваша сущность User скорее всего будет иметь хотя бы одно из следущих полей:

username
Будет использовано для выполнения входа, разве что вы вместо этого захотите, чтобы ваш пользователь выполнял вход через электронную почту (в таком случае, это поле не нужно).
email
Хорошая информация для сбора. Вы также можете позволить пользователям выполнять вход через электронную почту.
password
Зашифрованный пароль.
plainPassword
Это поле не сохраняется: (заметьте, что над ним нет @ORM\Column). Оно временно сохраняет простой пароль из формы регистрации. Это поле может быть валидировано и потом использовано, для заполнения поля password.

С добавлением некоторой валидации, вам класс может выглядеть как-то так:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

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 implements UserInterface
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     * @Assert\NotBlank()
     * @Assert\Email()
     */
    private $email;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     * @Assert\NotBlank()
     */
    private $username;

    /**
     * @Assert\NotBlank()
     * @Assert\Length(max=4096)
     */
    private $plainPassword;

    /**
     * Длина ниже зависит от "алгоритма", используемого для шифрования
     * пароля, но это также хорошо работает с bcrypt.
     *
     * @ORM\Column(type="string", length=64)
     */
    private $password;

    // другие свойства и методы

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }

    public function getUsername()
    {
        return $this->username;
    }

    public function setUsername($username)
    {
        $this->username = $username;
    }

    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    public function setPlainPassword($password)
    {
        $this->plainPassword = $password;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function setPassword($password)
    {
        $this->password = $password;
    }

    public function getSalt()
    {
        // Алгоритм bcrypt не требует отдельной "соли".
        // Вам *может* понадобиться настоящая соль, если вы выберете другой кодер.
        return null;
    }

    // другие методы, включая методы безопасности как getRoles()
}

Класс UserInterface требует несколько других методов и ваш файл security.yml должен быть сконфигурирован соответственно, чтобы работать с сущностью User. Для более полного примера, см. статью Провайдер сущностей.

Отметьте, что поле plainPassword имеет максимальную длину в 4096 символов. По соображениям безопасности (CVE-2013-5750), Symfony ограничивает длину простого пароля до 4096 символов при шифровании. Добавление этого ограничения помогает убедиться в том, что ваша форма будет выдавать ошибку валидации, если кто-либо попробует использовать очень длинный пароль.

Вам понадобится добавлять это ограничение везде в вашем приложении, где ваш пользователь отправляет нешифрованный пароль (например, форма смены пароля). Единственное место, где вам не нужно беспокоиться об этом - это ваша форма входа, так как компонент безопасности Symfony заботится об этом за вас.

Создание формы для сущности

Залее, создайте форму для сущности User:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/AppBundle/Form/UserType.php
namespace AppBundle\Form;

use AppBundle\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', EmailType::class)
            ->add('username', TextType::class)
            ->add('plainPassword', RepeatedType::class, array(
                'type' => PasswordType::class,
                'first_options'  => array('label' => 'Password'),
                'second_options' => array('label' => 'Repeat Password'),
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => User::class,
        ));
    }
}

Существуют всего три поля: email, username и plainPassword (повторяемое, чтобы подтвердить введенный пароль).

Tip

Чтобы изучить больше вещей о компонентне Формы, прочтите справочник Forms guide.

Управление отправкой форм

Далее вам понадобится контроллер, чтобы управлять отображением и оправкой формы. Если форма отправлена, контроллер выполняет валидацию и сохраняет данные в БД:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// src/AppBundle/Controller/RegistrationController.php
namespace AppBundle\Controller;

use AppBundle\Form\UserType;
use AppBundle\Entity\User;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Doctrine\ORM\EntityManagerInterface;

class RegistrationController extends Controller
{
    /**
     * @Route("/register", name="user_registration")
     */
    public function registerAction(Request $request, UserPasswordEncoderInterface $passwordEncoder, EntityManagerInterface $em)
    {
        // 1) Постройте форму
        $user = new User();
        $form = $this->createForm(UserType::class, $user);

        // 2) Обработайте отправку (случится только с POST)
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            // 3) Зашифруйте пароль (вы также можете сделать это через слушатель Doctrine)
            $password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
            $user->setPassword($password);

            // 4) Сохраните пользователя!
            $em->persist($user);
            $em->flush();

            // ... выполните любую другую работу - отправку электронных писем и т.д.
            // возможно, установите флеш-сообщение об успехе для пользователя
            return $this->redirectToRoute('replace_with_some_route');
        }

        return $this->render(
            'registration/register.html.twig',
            array('form' => $form->createView())
        );
    }
}

Чтобы определить алгоритм, использованный для шифрования пароля в 3-ем шаге, сконфигурируйте кодировщик в конфигурации безопасности:

  • YAML
    1
    2
    3
    4
    # app/config/security.yml
    security:
        encoders:
            AppBundle\Entity\User: bcrypt
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- app/config/security.xml -->
    <?xml version="1.0" charset="UTF-8" ?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <encoder class="AppBundle\Entity\User">bcrypt</encoder>
        </config>
    </srv:container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // app/config/security.php
    use AppBundle\Entity\User;
    
    $container->loadFromExtension('security', array(
        'encoders' => array(
            User::class => 'bcrypt',
        ),
    ));
    

В этом случае, используется рекомендуемый алгоритм bcrypt. Если необходимо, прочтите статью шифрование пароля пользователя.

Note

Если вы решите НЕ использовать аннотации при маршрутизации (как показано выше), то вам понадобится создать маршрут к этому контроллеру:

  • YAML
    1
    2
    3
    4
    # app/config/routing.yml
    user_registration:
        path:     /register
        defaults: { _controller: AppBundle:Registration:register }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="user_registration" path="/register">
            <default key="_controller">AppBundle:Registration:register</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // 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',
    )));
    
    return $collection;
    

Далее, создайте шаблон:

  • Twig
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    {# app/Resources/views/registration/register.html.twig #}
    
    {{ form_start(form) }}
        {{ form_row(form.username) }}
        {{ form_row(form.email) }}
        {{ form_row(form.plainPassword.first) }}
        {{ form_row(form.plainPassword.second) }}
    
        <button type="submit">Register!</button>
    {{ form_end(form) }}
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- app/Resources/views/registration/register.html.php -->
    
    <?php echo $view['form']->start($form) ?>
        <?php echo $view['form']->row($form['username']) ?>
        <?php echo $view['form']->row($form['email']) ?>
    
        <?php echo $view['form']->row($form['plainPassword']['first']) ?>
        <?php echo $view['form']->row($form['plainPassword']['second']) ?>
    
        <button type="submit">Register!</button>
    <?php echo $view['form']->end($form) ?>
    

Смотрите Персонализация форм, чтобы узнать больше деталей.

Обновление вашей схемы БД

Если во время этого туториала вы обновляли сущность User, то вам нужно обновить вашу схему БД, используя эту команду:

1
$ php bin/console doctrine:schema:update --force

Вот и всё! Переходите к /register, чтобы опробовать!

Форма регистрации только с электронной почты (без имени пользователя)

Если вы хотите, чтобы ваши пользователи выполняли вход через электронную почту, и вам не нужно имя пользователя, то вы можете полностью удалить его из вашей сущности User. Вместо этого, заставьте getUsername() возвращать свойство``email``:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/AppBundle/Entity/User.php
// ...

class User implements UserInterface
{
    // ...

    public function getUsername()
    {
        return $this->email;
    }

    // ...
}

Далее, просто обновите раздел``providers`` вашего файла security.yml, чтобы Symfony знала, как загружать ваших пользователей через свойство email в качестве логина. См. Аутентификация с пользовательским провайдером сущностей.

Добавление поля (чекбокса) "принятие условий"

Иногда вам может захотеться добавить поле "Принимаете ли вы условия" в вашу форму регистрации. Фокус в том, что вы хотите добавить это поле к форме, не добавляя нового ненужного свойства termsAccepted к вашей сущности User, так как оно вам никогда не понадобится.

Чтобы сделать это, добавьте в вашу форму поле termsAccepted, но установите его опцию отображение как false:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// src/AppBundle/Form/UserType.php
// ...
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', EmailType::class);
            // ...
            ->add('termsAccepted', CheckboxType::class, array(
                'mapped' => false,
                'constraints' => new IsTrue(),
            ))
        );
    }
}

Опция ограничений также может быть использована, что позволяет нам добавлять валидацию, несмотря на то, что у User нет свойства termsAccepted.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.