Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Buenas prácticas en el desarrollo de aplicaciones con Symfony2

Buenas prácticas en el desarrollo de aplicaciones con Symfony2

Slides de la charla en deSymfony 2013

Fran Moreno

June 22, 2013
Tweet

Other Decks in Programming

Transcript

  1. • Trabajo en Tierra Virtual
    • Miembro Symfony Valencia
    • @franmomu
    • showmethecode.es
    FRAN MORENO

    View full-size slide

  2. • Buenas prácticas
    • Bundles
    • Skinny Controllers, fat model
    • Model Managers
    • Eventos
    • Formularios
    ÍNDICE

    View full-size slide

  3. BUENAS PRÁCTICAS
    • Conjunto de acciones que han
    funcionado bien en un contexto
    • Criticables
    • Dependen del contexto

    View full-size slide

  4. BÁSICAS
    • RTFM (Read The Fucking Manual)
    • Mirar el código de Symfony2
    • Mirar el código de los bundles

    View full-size slide

  5. BÁSICAS
    • RTFM (Read The Fucking Manual)
    • Mirar el código de Symfony2
    • Mirar el código de los bundles
    • Cambiar el favicon y el apple touch
    icon

    View full-size slide

  6. BUNDLES
    Imitar la estructura de directorios
    MmsfBundle
    !"" Command/
    !"" Controller/
    !"" Event/
    !"" EventListener/
    !"" Form/
    # !"" EventListener/
    # $"" Type/
    !"" Resources/
    !"" Security/
    !"" Tests/
    !"" Twig/
    $"" Validator/

    View full-size slide

  7. class AppMmsfExtension extends Extension
    {
    public function load(array $configs, ContainerBuilder $container)
    {
    // ...
    $loader->load('forms.xml');
    $loader->load('validation.xml');
    $loader->load('twig.xml');
    }
    }
    Separar las declaraciones de
    los servicios

    View full-size slide

  8. # config.yml
    parameters:
    paginator:
    page_range: 5
    template:
    pagination: MmsfBundle:Pagination:pagination.html.twig
    Parámetros de configuración
    $container->getParameter('paginator.page_range');

    View full-size slide

  9. Usar Configuration
    class Configuration implements ConfigurationInterface
    {
    public function getConfigTreeBuilder()
    {
    $treeBuilder = new TreeBuilder();
    $rootNode = $treeBuilder->root('app_mmsf');
    $this->addPaginatorSection($rootNode);
    return $treeBuilder;
    }
    public function addPaginatorSection(ArrayNodeDefinition $rootNode)
    {
    $rootNode
    ->children()
    ->arrayNode('paginator')
    ->addDefaultsIfNotSet()
    ->children()
    ->arrayNode('template')
    // ...
    ->end()
    ->scalarNode('page_range')
    ->defaultValue(5)
    ->validate()
    ->ifTrue(function ($v) { return ((int) $v !== $v);})
    ->thenInvalid('Page range must be an integer')
    ->end()
    ->end()
    ->end()
    ->end();
    }
    }
    Añadir los parámetros en la Extension!

    View full-size slide

  10. SKINNY CONTROLLERS,
    FAT MODEL
    • Controladores pequeños
    • Symfony2 no provee Model
    • Capa Model
    • Objetos de dominio
    • Capa de persistencia
    • Entity
    • Repository
    • Servicios

    View full-size slide

  11. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @Template
    */
    public function addPlayerToTeamAction($id)
    {
    $em = $this->getDoctrine()->getManager();
    $team = $em->getRepository('MmsfBundle:Team')->find($id);
    if (!$team) {
    throw $this->createNotFoundException('Unable to find Team entity.');
    }
    $player = new Player();
    $form = $this->createFormBuilder($player)
    ->add('name')
    ->add('surname')
    //...
    ;
    $request = $this->getRequest();
    if ($request->isMethod('POST')) {
    $form->bind($request);
    if($form->isValid()) {
    // Amazing things...
    }
    }
    return array('form' => $form->createView(), 'team' => $team);
    }
    Fat Controller

    View full-size slide

  12. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @Template
    */
    public function addPlayerToTeamAction($id)
    {
    $em = $this->getDoctrine()->getManager();
    $team = $em->getRepository('MmsfBundle:Team')->find($id);
    if (!$team) {
    throw $this->createNotFoundException('Unable to find Team entity.');
    }
    $player = new Player();
    $form = $this->createFormBuilder($player)
    ->add('name')
    ->add('surname')
    //...
    ;
    $request = $this->getRequest();
    if ($request->isMethod('POST')) {
    $form->bind($request);
    if($form->isValid()) {
    // Amazing things...
    }
    }
    return array('form' => $form->createView(), 'team' => $team);
    }
    Fat Controller

    View full-size slide

  13. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @Template
    */
    public function addPlayerToTeamAction($id)
    {
    $em = $this->getDoctrine()->getManager();
    $team = $em->getRepository('MmsfBundle:Team')->find($id);
    if (!$team) {
    throw $this->createNotFoundException('Unable to find Team entity.');
    }
    $player = new Player();
    $form = $this->createFormBuilder($player)
    ->add('name')
    ->add('surname')
    //...
    ;
    $request = $this->getRequest();
    if ($request->isMethod('POST')) {
    $form->bind($request);
    if($form->isValid()) {
    // Amazing things...
    }
    }
    return array('form' => $form->createView(), 'team' => $team);
    }
    Fat Controller

    View full-size slide

  14. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @Template
    */
    public function addPlayerToTeamAction($id)
    {
    $em = $this->getDoctrine()->getManager();
    $team = $em->getRepository('MmsfBundle:Team')->find($id);
    if (!$team) {
    throw $this->createNotFoundException('Unable to find Team entity.');
    }
    $player = new Player();
    $form = $this->createFormBuilder($player)
    ->add('name')
    ->add('surname')
    //...
    ;
    $request = $this->getRequest();
    if ($request->isMethod('POST')) {
    $form->bind($request);
    if($form->isValid()) {
    // Amazing things...
    }
    }
    return array('form' => $form->createView(), 'team' => $team);
    }
    Fat Controller

    View full-size slide

  15. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @Template
    */
    public function addPlayerToTeamAction($id)
    {
    $em = $this->getDoctrine()->getManager();
    $team = $em->getRepository('MmsfBundle:Team')->find($id);
    if (!$team) {
    throw $this->createNotFoundException('Unable to find Team entity.');
    }
    $player = new Player();
    $form = $this->createFormBuilder($player)
    ->add('name')
    ->add('surname')
    //...
    ;
    $request = $this->getRequest();
    if ($request->isMethod('POST')) {
    $form->bind($request);
    if($form->isValid()) {
    // Amazing things...
    }
    }
    return array('form' => $form->createView(), 'team' => $team);
    }
    Fat Controller

    View full-size slide

  16. No crear los formularios en el Controller
    $form = $this->createFormBuilder($player)
    ->add('name')
    ->add('surname')
    //...
    ;

    View full-size slide

  17. No crear los formularios en el Controller
    $form = $this->createFormBuilder($player)
    ->add('name')
    ->add('surname')
    //...
    ;
    $form = $this->createForm(new PlayerType(), $player);

    View full-size slide

  18. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @Template
    */
    public function addPlayerToTeamAction($id, Request $request)
    {
    //...
    $request = $this->getRequest();
    // ..
    }
    Pasar la petición como parámetro

    View full-size slide

  19. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @Template
    */
    public function addPlayerToTeamAction($id, Request $request)
    {
    $em = $this->getDoctrine()->getManager();
    $team = $em->getRepository('MmsfBundle:Team')->find($id);
    if (!$team) {
    throw $this->createNotFoundException('Unable to find Team entity.');
    }
    // ...
    }

    View full-size slide

  20. ParamConverter
    /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @ParamConverter("team", class="MmsfBundle:Team")
    * @Template
    */
    public function addPlayerToTeamAction(Team $team, Request $request)
    {
    $em = $this->getDoctrine()->getManager();
    // ...
    }

    View full-size slide

  21. ParamConverter
    /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @ParamConverter("team", class="MmsfBundle:Team")
    * @Template
    */
    public function addPlayerToTeamAction(Team $team, Request $request)
    {
    $em = $this->getDoctrine()->getManager();
    // ...
    }

    View full-size slide

  22. ParamConverter
    /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @ParamConverter("team", class="MmsfBundle:Team")
    * @Template
    */
    public function addPlayerToTeamAction(Team $team, Request $request)
    {
    $em = $this->getDoctrine()->getManager();
    // ...
    }

    View full-size slide

  23. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @ParamConverter("team", class="MMSFBundle:Team")
    * @Template
    */
    public function addPlayerToTeamAction(Team $team, Request $request)
    {
    // ...
    if($form->isValid()) {
    $em = $this->getDoctrine()->getManager();
    $team->addPlayer($player);
    $em->persist($team);
    $em->flush();
    $mailer = $this->get('app.mmsf.mailer');
    $mailer->sendWelcomeToTeamPlayer($team, $player);
    return $this->redirect(/* ... */);
    }
    // ...
    }
    Persistencia

    View full-size slide

  24. MODEL MANAGERS
    • Servicio
    • Manipular modelos
    • Reutilizar
    • Sacar lógica del Controller

    View full-size slide

  25. class TeamManager
    {
    protected $objectManager;
    protected $class;
    protected $repository;
    protected $mailer;
    public function __construct(ObjectManager $om, $class, MailerInterface $mailer)
    {
    $this->objectManager = $om;
    $this->repository = $om->getRepository($class);
    $metadata = $om->getClassMetadata($class);
    $this->class = $metadata->getName();
    $this->mailer = $mailer;
    }
    // ...
    }
    TeamManager - Constructor
    App\MmsfBundle\Entity\Team

    View full-size slide

  26. class TeamManager
    {
    // ...
    public function createTeam()
    {
    $class = $this->getClass();
    $team = new $class;
    return $team;
    }
    public function findTeamBy(array $criteria)
    {
    return $this->repository->findOneBy($criteria);
    }
    public function updateTeam(TeamInterface $team, $andFlush = true)
    {
    $this->objectManager->persist($team);
    if ($andFlush) {
    $this->objectManager->flush();
    }
    }
    public function addPlayerToTeam(PlayerInterface $player, TeamInterface $team)
    {
    $team->addPlayer($player);
    $this->updateTeam($team);
    $this->mailer->sendWelcomeToTeamPlayer($team, $player);
    }
    }
    TeamManager - Métodos

    View full-size slide

  27. interface UserManagerInterface
    {
    public function createUser();
    public function deleteUser(UserInterface $user);
    public function findUserBy(array $criteria);
    public function findUserByUsername($username);
    public function findUserByEmail($email);
    public function findUserByUsernameOrEmail($usernameOrEmail);
    public function findUserByConfirmationToken($token);
    public function findUsers();
    public function getClass();
    public function reloadUser(UserInterface $user);
    public function updateUser(UserInterface $user);
    public function updateCanonicalFields(UserInterface $user);
    public function updatePassword(UserInterface $user);
    }
    FOSUserBundle - UserManagerInterface

    View full-size slide

  28. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @ParamConverter("team", class="MMSFBundle:Team")
    * @Template
    */
    public function addPlayerToTeamAction(Team $team)
    {
    // ...
    if($form->isValid()) {
    $teamManager = $this->get('app.mmsf.manager.team');
    $teamManager->addPlayerToTeam($player, $team);
    return $this->redirect(/* ... */);
    }
    // ...
    }
    TeamManager en el Controller

    View full-size slide

  29. /**
    * @Route("/team/{id}/add_player", name="mmsf_main_team_add_player")
    * @ParamConverter("team", class="MmsfBundle:Team")
    * @Template
    */
    public function addPlayerToTeamAction(Team $team, Request $request)
    {
    $player = new Player();
    $form = $this->createForm(new PlayerType(), $player);
    if ($request->isMethod('POST')) {
    $form->bind($request);
    if($form->isValid()) {
    $teamManager = $this->get('app.mmsf.manager.team');
    $teamManager->addPlayerToTeam($player, $team);
    return $this->redirect(/* ... */);
    }
    }
    return array('form' => $form->createView(), 'team' => $team);
    }
    Skinny Controller

    View full-size slide

  30. ¿Y si queremos añadir más acciones
    cuando se añade un jugador a un
    equipo?
    • Enviar un sms al usuario
    • Notificar a los demás miembros del equipo
    • ...

    View full-size slide

  31. EVENTOS
    MmsfBundle
    !"" ...
    !"" Event/
    # !"" TeamPlayerEvent.php
    !"" EventListener/
    # $"" SendWelcomeToNewPlayerListener.php
    !"" Resources/
    # $"" config/
    # $"" listeners.xml
    $"" TeamEvents.php

    View full-size slide

  32. namespace App\MmsfBundle;
    final class TeamEvents
    {
    /**
    * The TEAMPLAYER_ADDED event occurs when a player is added to a team
    *
    * The event listener method
    * receives a App\MmsfBundle\Event\TeamPlayerEvent
    * instance.
    *
    * @var string
    *
    */
    const TEAMPLAYER_ADDED = 'app.mmsf.player.added';
    }
    Nombre del evento

    View full-size slide

  33. namespace App\MmsfBundle\Event;
    use Symfony\Component\EventDispatcher\Event;
    use App\MmsfBundle\Model\TeamInterface;
    use App\MmsfBundle\Model\PlayerInterface;
    class TeamPlayerEvent extends Event
    {
    protected $team;
    protected $player;
    public function __construct(TeamInterface $team, PlayerInterface $player)
    {
    $this->team = $team;
    $this->player = $player;
    }
    public function getTeam()
    {
    return $this->team;
    }
    public function getPlayer()
    {
    return $this->player;
    }
    }
    Objeto Event

    View full-size slide

  34. namespace App\MmsfBundle\EventListener;
    class SendWelcomeToNewPlayerListener implements EventSubscriberInterface
    {
    private $mailer;
    public function __construct(Mailer $mailer)
    {
    $this->mailer = $mailer;
    }
    public function onTeamPlayerAdded(TeamPlayerEvent $event)
    {
    $team = $event->getTeam();
    $player = $event->getPlayer();
    $this->mailer->sendWelcomeToTeamPlayer($team, $player);
    }
    public static function getSubscribedEvents()
    {
    return array(TeamEvents::TEAMPLAYER_ADDED => 'onTeamPlayerAdded');
    }
    }
    Listeners

    View full-size slide

  35. class="App\MmsfBundle\EventListener\SendWelcomeToNewPlayerListener">



    Registrar listeners
    (listeners.xml)

    View full-size slide

  36. class TeamManager
    {
    protected $dispatcher;
    public function __construct(ObjectManager $om, $class,
    EventDispatcherInterface $dispatcher)
    {
    // ...
    $this->dispatcher = $dispatcher;
    }
    // ...
    public function addPlayerToTeam(PlayerInterface $player, TeamInterface $team)
    {
    $team->addPlayer($player);
    $this->updateTeam($team);
    $event = new TeamPlayerEvent($team, $player);
    $this->dispatcher->dispatch(TeamEvents::TEAMPLAYER_ADDED, $event);
    }
    }
    Lanzar Eventos

    View full-size slide

  37. FOSUserBundle
    class RegistrationController extends ContainerAware
    {
    public function registerAction(Request $request)
    {
    //..
    $dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE,
    new UserEvent($user, $request));
    //..
    if ('POST' === $request->getMethod()) {
    $form->bind($request);
    if ($form->isValid()) {
    $dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS,
    new FormEvent($form, $request));
    //..
    $dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED,
    new FilterUserResponseEvent($user, $request, $response));
    return $response;
    }
    }
    //..
    }
    }

    View full-size slide

  38. FOSCommentBundle
    abstract class CommentManager implements CommentManagerInterface
    {
    // ...
    public function saveComment(CommentInterface $comment)
    {
    if (null === $comment->getThread()) {
    throw new InvalidArgumentException('The comment must have a thread');
    }
    $event = new CommentPersistEvent($comment);
    $this->dispatcher->dispatch(Events::COMMENT_PRE_PERSIST, $event);
    if ($event->isPersistenceAborted()) {
    return;
    }
    $this->doSaveComment($comment);
    $event = new CommentEvent($comment);
    $this->dispatcher->dispatch(Events::COMMENT_POST_PERSIST, $event);
    }
    }

    View full-size slide

  39. FORMULARIOS
    MmsfBundle
    $"" Form/
    !"" DataTransformer/
    !"" EventListener/
    !"" Extension/
    !"" Model/
    $"" Type/
    Tip: Usar el campo intention para más seguridad (CSRF)
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array(
    'data_class' => $this->class,
    'intention' => 'change_password',
    ));
    }

    View full-size slide

  40. Un formulario está compuesto por formularios

    View full-size slide

  41. Un formulario está compuesto por formularios
    Form

    View full-size slide

  42. Un formulario está compuesto por formularios
    Form
    Form
    Form
    Form

    View full-size slide

  43. Un formulario está compuesto por formularios
    Form
    Form
    Form
    Form
    FORM
    FORM

    View full-size slide

  44. $builder->add('name', 'text', array('label' => 'Nombre'))

    View full-size slide

  45. $builder->add('name', 'text', array('label' => 'Nombre'))
    $builder->add('name' new TextType(), array('label' => 'Nombre'))

    View full-size slide

  46. $builder->add('name', 'text', array('label' => 'Nombre'))
    $builder->add('name' new TextType(), array('label' => 'Nombre'))
    namespace Symfony\Component\Form\Extension\Core\Type;
    use ...
    class TextType extends AbstractType
    {
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array(
    'compound' => false,
    ));
    }
    public function getParent()
    {
    return 'field';
    }
    public function getName()
    {
    return 'text';
    }
    }

    View full-size slide

  47. Formato de los datos

    View full-size slide

  48. Formato de los datos
    DateType

    View full-size slide

  49. Formato de los datos
    DateType
    string
    DateTime
    array
    integer

    View full-size slide

  50. Formato de los datos
    DateType
    string
    DateTime
    array
    integer
    choice (array)
    text (array)
    single_text

    View full-size slide

  51. Formato de los datos
    DateType
    string
    DateTime
    array
    integer
    choice (array)
    text (array)
    single_text
    DateTime

    View full-size slide

  52. EJEMPLO - TeamType
    class Team
    {
    /**
    * @ORM\ManyToOne(targetEntity="App\MmmsfBundle\Entity\Player")
    * @ORM\JoinColumn(name="first_player_id", referencedColumnName="id")
    */
    protected $firstPlayer;
    /**
    * @ORM\ManyToOne(targetEntity="App\MmmsfBundle\Entity\Player")
    * @ORM\JoinColumn(name="second_player_id", referencedColumnName="id")
    */
    protected $secondPlayer;
    // ...
    }

    View full-size slide

  53. class TeamType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder
    ->add('name')
    ->add('firstPlayerEmail', 'email', array('mapped' => false))
    ->add('secondPlayerEmail', 'email', array('mapped' => false))
    ;
    }
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array(
    'data_class' => 'App\MmsfBundle\Entity\Team'
    ));
    }
    public function getName()
    {
    return 'team_type';
    }
    }

    View full-size slide

  54. DataTransformers
    interface DataTransformerInterface
    {
    public function transform($value);
    public function reverseTransform($value);
    }
    Modifican la representación y no la información

    View full-size slide

  55. Formato de los datos

    View full-size slide

  56. App\MmsfBundle\Entity\Player
    Formato de los datos

    View full-size slide

  57. App\MmsfBundle\Entity\Player
    ‘string’
    ‘string’
    Formato de los datos

    View full-size slide

  58. PlayerToEmailTransformer
    App\MmsfBundle\Entity\Player
    ‘string’
    ‘string’
    Formato de los datos

    View full-size slide

  59. class PlayerToEmailTransformer implements DataTransformerInterface
    {
    private $om;
    public function __construct(ObjectManager $om)
    {
    $this->om = $om;
    }
    public function transform(Player $player = null)
    {
    if (null === $player) {
    return "";
    }
    return $player->getEmail();
    }
    public function reverseTransform($email)
    {
    if (!$email) {
    return null;
    }
    $player = $this->om->getRepository('AppMmsfBundle:Player')
    ->findOneBy(array('email' => $email))
    ;
    if (null === $player) {
    throw new TransformationFailedException("...");
    }
    return $player;
    }
    }

    View full-size slide

  60. class PlayerSelectorType extends AbstractType
    {
    private $om;
    public function __construct(ObjectManager $om)
    {
    $this->om = $om;
    }
    public function buildForm(FormBuilderInterface $builder,
    array $options)
    {
    $transformer = new PlayerToEmailTransformer($this->om);
    $builder->addModelTransformer($transformer);
    }
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array(
    'invalid_message' => 'El email introducido no existe',
    ));
    }
    public function getParent()
    {
    return 'email';
    }
    public function getName()
    {
    return 'player_selector';
    }
    }

    View full-size slide

  61. class="App\MmsfBundle\Form\Type\PlayerSelectorType">



    Registrar el type como servicio

    View full-size slide

  62. class TeamType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder
    ->add('name')
    ->add('firstPlayer', 'player_selector')
    ->add('secondPlayer', 'player_selector')
    ;
    }
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
    $resolver->setDefaults(array(
    'data_class' => 'App\MmsfBundle\Entity\Team'
    ));
    }
    public function getName()
    {
    return 'team_type';
    }
    }

    View full-size slide

  63. Formularios dinámicos
    class PlayerType extends AbstractType
    {
    protected $player;
    public function __construct(Player $player = null)
    {
    $this->player = $player;
    }
    public function buildForm(FormBuilderInterface $builder,
    array $options)
    {
    // ...
    if ($this->player) {
    $builder->add('level');
    }
    }
    // ...
    }

    View full-size slide

  64. Formularios dinámicos
    class PlayerType extends AbstractType
    {
    protected $player;
    public function __construct(Player $player = null)
    {
    $this->player = $player;
    }
    public function buildForm(FormBuilderInterface $builder,
    array $options)
    {
    // ...
    if ($this->player) {
    $builder->add('level');
    }
    }
    // ...
    }
    Sólo dependencias globales!
    EntityManager
    SecurityContext
    ...

    View full-size slide

  65. Eventos en formularios
    Model Norm View
    $form->setData($player)

    View full-size slide

  66. Eventos en formularios
    Model Norm View
    $form->setData($player)
    PRE_SET_DATA POST_SET_DATA

    View full-size slide

  67. Eventos en formularios
    Model Norm View
    $form->setData($player)
    $form->bind($request)
    PRE_SET_DATA POST_SET_DATA

    View full-size slide

  68. Eventos en formularios
    Model Norm View
    $form->setData($player)
    $form->bind($request)
    PRE_SET_DATA POST_SET_DATA
    PRE_BIND
    POST_BIND BIND

    View full-size slide

  69. Eventos en formularios
    Model Norm View
    $form->setData($player)
    $form->bind($request)
    PRE_SET_DATA POST_SET_DATA
    PRE_BIND
    POST_BIND BIND
    POST_SUBMIT SUBMIT PRE_SUBMIT
    Symfony 2.3+
    $form->submit($request)

    View full-size slide

  70. class PlayerType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    // ...
    $builder->addEventListener(FormEvents::PRE_SET_DATA,
    function(FormEvent $event){
    $data = $event->getData();
    $form = $event->getForm();
    if ($data && $data->getId()) {
    $form->add('level');
    }
    });
    }
    }
    Formularios dinámicos

    View full-size slide

  71. LEER CÓDIGO!

    View full-size slide

  72. REFERENCIAS
    • Documentación Symfony
    http://symfony.com/doc/
    • FOSUserBundle
    https://github.com/FriendsOfSymfony/FOSUserBundle
    • FOSCommentBundle
    https://github.com/FriendsOfSymfony/FOSCommentBundle
    • 3 steps to Symfony2 Form Mastery - Bernhard Schussek
    https://speakerdeck.com/bschussek/3-steps-to-symfony2-form-mastery

    View full-size slide

  73. GRACIAS!
    ¿Preguntas?
    Fran Moreno
    [email protected]
    @franmomu
    showmethecode.es
    http://joind.in/talk/view/8837

    View full-size slide