Por trás do site IngressoPrático: O formulário de cadastro - parte 3

Você está lendo a parte três de Por trás do site IngressoPrático, uma série em quatro posts que mostra como nós usamos o Symfony2 para desenvolver o site.

Na parte 1 dessa série, eu disse que um dos requisitos do projeto era de que haveria um período de venda de ingressos que limitaria a quantidade de ingressos que um usuário poderia comprar. Esse período de vendas com cota é definido arbitrariamente pelos administradores do site. Cada evento tem um elenco formado pelos alunos da escola. A cota é definida durante a definição do elenco para cada evento e para cada participante (aluno). Um participante é criado quando o pai (ou responsável legal) cadastra-se no site pelo formulário de cadastro:

signup-form-en.png

Os trechos de código e diagramas de classe foram simplificados com o objetivo de ilustrar como nós organizamos o código para customizar o formulário de cadastro. Eu adicionei referências onde você encontrará informações mais detalhadas sobre um determinado assunto.

FOSUserBundle

Para não reinventar a roda, nós usamos o bundle FOSUserBundle que provê um framework flexível para o gerenciamento de usuários. Basicamente nós sobrescrevemos a classe FOS\UserBundle\Controller\RegistrationController e empacotamos a classe FOS\UserBundle\Form\Type\RegistrationFormType na nossa classe RegistrationFormType customizada.

Por conveniência, nós iremos usar o FQCN para as classes do FOSUserBundle. Classes sem o FQCN são implicitamente do namespace IngressoPratico, exceto FormBuilder (Symfony\Component\Form\FormBuilder), FormFactory (Symfony\Component\Form\FormFactory) e FormType (sempre referindo como uma implementação de Symfony\Component\Form\FormTypeInterface).

As entidades e os formulários

Ao invés de adicionar os atributos na nossa classe User, nós escolhemos criar uma classe Profile que possui todos os atributos adicionais como endereço, nome completo e os nomes dos filhos (Participant):

class-diagram_1.png

As entidades para as classes acima:

Note que o método User::setUsername() lança uma exceção. Nós removemos o campo username do formulário de cadastro. O atributo username terá o mesmo valor que o atributo email já que queríamos que toda a autenticação fosse feita apenas pelo endereço de e-mail. No final, terminamos com as seguintes classes: ParticipantFormType, ProfileFormType e RegistrationFormType (todas subclasses de FormType). RegistrationFormType empacota FOS\UserBundle\Form\Type\RegistrationFormType e ProfileFormType empacota ParticipantFormType.

Para usar a nossa classe RegistrationFormType ao invés da classe do FOSUserBundle, definimos o formulário como um serviço (ao qual será consumido por RegistrationController encontrado no final desse post).

Dê uma olhada atenta na definição de serviço user.registration.form.type:

Nós definimos um serviço que usa o FormFactory para retornar um FormBuilder para a classe FOS\UserBundle\Form\Type\RegistrationFormType ao qual é o primeiro argumento do construtor para do nosso serviço user.registration.form.type (FOSUserBundle especificava um serviço semelhante até a revisão fccde65b7):

Note que esta é uma aproximação diferente da que está documentada na documentação do FOSUserBundle. A documentação do Symfony2 tem um cookbook descrevendo como usar um Factory para criar serviços.

Adicionando campos extras usando JavaScript

O FormType collection tem uma forma conveniente que permite que você adicione campos adicionais dinamicamente com JavaScript. Em ProfileFormType nós definimos que é possível adicionar novos campos ou remover campos existentes para o campo children (ParticipantFormType):

Aqui nós divergimos um pouco da forma sugerida para adicionar/remover campos com JavaScript que está documentado na documentação do FormType collection. Ao invés de usar o atributo prototype, nosso JavaScript copia um container div inteiro que contém o campo:

Fallback para browsers com JavaScript desabilitado

O código acima funciona bem para browsers com JavaScript habilitado mas quebra em browsers que não suportam JavaScript (como o test client do Symfony2). Como nós usamos TDD, já tínhamos o formulário funcional para browsers não-JavaScript e apenas adicionamos o JavaScript após ter o formulário de cadastro totalmente testado com testes de unidade e testes funcionais. Essa prática se chama melhoria progressiva é não é algo difícil de seguir quando você desenvolve uma aplicação usando um framework como o Symfony2.

O resultado é que o nosso RegistrationFormHandler é preparado para a possibilidade de que campos children extras (Participant) podem ser adicionados ou removidos. Se o formulário não for válido ou se a requisição for para adicionar ou remover um child, o manuaseador irá retornar false, o controller não irá agir (p.e., não irá enviar uma mensagem para o modelo agir), irá apenas exibir o formulário populado e a mensagem de erro para o usuário.

Criar manuseadores de formulários dessa forma é uma boa prática para tornar os controllers magros. Finalmente, nós sobrescrevemos o action do controller de cadastro do FOSUserBundle:

A documentação do FOSUserBundle explica como sobrescrever um controller.