[RESOLU] [Symfony2] Form field à sauver et utilisé dans Query Builder

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : [RESOLU] [Symfony2] Form field à sauver et utilisé dans Query Builder

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 10 mai 2013, 09:49

Normalement si ton schéma est bien fait, le fait d'initialiser le cours dans le ClassSession doit suffire (avec le persist en cascade), c'est doctrine qui va faire le reste.
Initialiser l'objet ClassSession avec l'objet Cours fonctionne, il est correctement sauvé. Mais mon objet cours provient d'un paramètre passé dans l'url, donc je l'ai en GET, mais je ne l'ai plus une fois le formulaire soumis, d'où mon champ hidden.
Ensuite, j'ai l'habitude de gérer la création en une seul méthode "create" or "new" peut importe, et de se fait, à chaque fois que j'initialise mon entité, celle-ci possède toujours les informations voulut, et c'est le fait de binder qui peut éventuellement modifier les valeur.
Je n'avais pas pensé à faire ça, je suivais bêtement la structure créée par le générateur CRUD de Symfony. Dans ton exemple, si à la place de ta constante TypeClient::PARTICULIER tu avais un paramètre récupéré depuis l'url à l'initialisation du formulaire, n'aurais-tu pas besoin de le conserver dans le formulaire pour le récupérer à la sortie ?

Quoiqu'il en soit je passe le sujet en résolu, mon problème de base est réglé et j'ai appris des trucs sur les formulaires. Je vais essayer de le modifier maintenant pour avoir une liste déroulante de cours si jamais l'ID de cours n'est pas récupéré via un paramètre GET (formulaire générique) et recharger la liste des apprenants inscrits à chaque changement de cours dans cette liste.

Merci pour ton aide :)

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par Yosh » 07 mai 2013, 14:11

C'est la toute ma question :P, tu me dis depuis le début que je n'en ai pas besoin, je suis prêt à te croire sur parole mais je ne comprends pas comment, à la réception du formulaire soumis, je peux savoir sur quel cours porte mon compte-rendu. Car quand je sauve mon objet ClassSession, je dois conserver la relation vers le Cours. Je m'en sers donc dans le createAction (après soumission du formulaire) pour récupérer l'objet Course qui correspond à l'ID transmis:
Normalement si ton schéma est bien fait, le fait d'initialiser le cours dans le ClassSession doit suffire (avec le persist en cascade), c'est doctrine qui va faire le reste.

Ensuite, j'ai l'habitude de gérer la création en une seul méthode "create" or "new" peut importe, et de se fait, à chaque fois que j'initialise mon entité, celle-ci possède toujours les informations voulut, et c'est le fait de binder qui peut éventuellement modifier les valeur.

Voici un exemple d'action dans un de mes projets:
/**
     * @Route("/client/creer", name="client_new")
     * @Method({"GET", "POST"})
     * @Secure(roles="ROLE_RESPONSABLE")
     */
    public function newAction()
    {
        $request = $this->get('request');

        $typeParticulier = $this->get('inextenso.manager.typeclient')->getById(TypeClient::PARTICULIER);

		$client = new Client();
		//dans tous les cas mon entité client possède le type initialiser, MAIS l'utilisateur a potentiellement changé la valeur via un select, et c'est le bind plus bas qui va surcharger la valeur
		$client->setType($typeParticulier);
		
        $form = $this->createForm(new ClientType(), $client);
        
		//POST & bind
		if ($request->isMethod('POST')) {
			$form->bind($request);

			if ($form->isValid()) {
				$this->get('inextenso.manager.client')->save($client, $dossier);
				$this->get('session')->getFlashBag()->add('notice_success', 'Le client à été créée.');

				return $this->redirect($this->generateUrl('client_edit', array('id' => $client->getId())));
			}
		}

		return $this->get('templating')->renderResponse('InExtensoNotesFraisBundle:Client:form.html.twig',
            array(
                'entity' => $entity,
                'form' => $form->createView()
            )
        );        
    }

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 07 mai 2013, 11:51

- as-tu vraiment besoin du champs hidden "course_id" ? Tu en fait quoi, tu t'en sers ou ?
C'est la toute ma question :P, tu me dis depuis le début que je n'en ai pas besoin, je suis prêt à te croire sur parole mais je ne comprends pas comment, à la réception du formulaire soumis, je peux savoir sur quel cours porte mon compte-rendu. Car quand je sauve mon objet ClassSession, je dois conserver la relation vers le Cours. Je m'en sers donc dans le createAction (après soumission du formulaire) pour récupérer l'objet Course qui correspond à l'ID transmis:
        $courseId = $form->get('course_id')->getData();
        $course = $this->getCourseRepository()->find($courseId);
        $entity->setCourse($course);
C'est là que je ne comprends comment je peux me passer de cette information dans le formulaire, vu que dans le createAction je crée une instance de ClassSession vide avant de binder mon formulaire avec la requête, si je ne transmets pas cet ID dans le formulaire, je ne sais pas comment récupérer cette info. Est-ce qu'il y a un moyen de conserver l'objet ClassSession initialisé dans le newAction (avant sousmission du formulaire) ?
    /**
     * Creates a new ClassSession entity.
     *
     * @Route("/create", name="classsession_create")
     * @Method("POST")
     * @Template("VirguleMainBundle:ClassSession:new.html.twig")
     */
    public function createAction(Request $request) {
        $entity = new ClassSession();     // ==============================> Ici je réceptionne le formulaire, et j'ai besoin d'un objet ClassSession pour le "createForm". Donc j'en crée un nouveau et donc l'attribut "course" vaut null. Est-il possible ici de réutiliser l'objet ClassSession qui a été initialisé avec le bon cours dans newAction ?
        $organizationBranchId = $this->getSelectedOrganizationBranchId();
        $currentTeacher = $this->getConnectedUser();
               
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $organizationBranchId, $currentTeacher), $entity, array(
    'em' => $this->getDoctrine()->getManager(),
));
- pas bien compris ton histoire de ArrayCollection ?
C'est dans l'appel de la méthode "createNamed", tu avais mis ça :
$form->add($this->factory->createNamed('classSessionStudents', 'entity', $data['courseId'], array(
C'est l'utilisation du $data['courseId'] qui posait problème, de ce que j'ai compris comme j'ai une relation ManyToMany cet champ là attend une collection si tu lui passes des données. Finalement en lisant la doc de l'API j'ai compris que je n'avais pas besoin de ce paramètre, j'ai donc passé une ArrayCollection vide, dans ton dernier exemple tu as mis null, je pense que ça revient au même.

Merci pour ton aide en tout cas :)

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par Yosh » 07 mai 2013, 10:01

Ça me parait pas trop mal.

Juste deux points à discuter,

- as-tu vraiment besoin du champs hidden "course_id" ? Tu en fait quoi, tu t'en sers ou ?
- pas bien compris ton histoire de ArrayCollection ?

Voila un exemple de code me permettant d'afficher un select lorsque mon entité ne possède un pas d'ID, cela me permet de définir le type seulement en création.
/**
     * @param FormEvent $event
     */
    public function preSetData(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

        if(!$data->getId())
        {
            $form->add($this->factory->createNamed('typeDepense', 'entity', null, array(
                'label' => 'Type de dépense',
                'class' => 'InExtensoNotesFraisBundle:TypeDepense',
                'query_builder' => function(TypeDepenseRepository $er) {
                    return $er->createDefaultJoinQueryBuilder()
                        ->addOrderBy('td.label');
                },
                'property' => 'label',
                'required' => true,
                'multiple' => true,
                'empty_value' => '',
                'attr' => array(
                    'placeholder' => 'Sélectionner'
                )
            )));
        }
    }
J'ai tester le multiple et je n'ai pas de problème...

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 06 mai 2013, 15:55

J'ai réussi à faire marcher le truc mais je suis pas convaincu que c'est ce que tu avais en tête ;)

Le controller:
    /**
     * Displays a form to create a new ClassSession entity.
     *
     * @Route("/add", name="classsession_new")
     * @Route("/add/course/{id}", name="classsession_add")
     * @Template()
     */
    public function newAction(Course $course) {       
        $classSession = new ClassSession();
        $classSession->setCourse($course);
       
        $organizationBranchId = $this->getSelectedOrganizationBranchId();
        $currentTeacher = $this->getConnectedUser();
        $semesterId = $this->getSelectedSemesterId();
        
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $organizationBranchId, $currentTeacher, $semesterId), $classSession, array('em' => $this->getDoctrine()->getManager()));
       
        return array(
            'entity' => $classSession,
            'form' => $form->createView(),
            'course'=> $course
        );
    }


    /**
     * Creates a new ClassSession entity.
     *
     * @Route("/create", name="classsession_create")
     * @Method("POST")
     * @Template("VirguleMainBundle:ClassSession:new.html.twig")
     */
    public function createAction(Request $request) {
        $entity = new ClassSession();
        $organizationBranchId = $this->getSelectedOrganizationBranchId();
        $currentTeacher = $this->getConnectedUser();
                
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $organizationBranchId, $currentTeacher), $entity, array('em' => $this->getDoctrine()->getManager()));
        $form->bind($request);   
        
        $entity->setReportDate(new \Datetime('now'));
        
        $courseId = $form->get('course_id')->getData();
        $course = $this->getCourseRepository()->find($courseId);
        $entity->setCourse($course);
            
        $connectedUser = $this->getConnectedUser();
        $entity->setReportTeacher($connectedUser);
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($entity);           
            $em->flush();

            return $this->redirect($this->generateUrl('classsession_index'));
        }
        
        $this->logDebug("Students saved: " . count($entity->getClassSessionStudents()));
        foreach ($entity->getClassSessionStudents() as $student) {
            $this->logDebug($student->getId());
        }
        
        return array(
            'entity' => $entity,
            'form' => $form->createView(),
        );
    }
Le formulaire:
public function buildForm(FormBuilderInterface $builder, array $options) {    
        $now = new \DateTime('now');
        $sNow = $now->format('d/m/Y');
        
        $builder
                ->add('sessionDate', 'date', array(
                    'widget' => 'single_text',
                    'format' => 'dd/MM/yyyy',
                    'attr' => array('class' => 'date', 'value' => $sNow)))
                ->add('summary')          
                ->add('sessionTeacher', 'entity', array(
                        'class' => 'VirguleMainBundle:Teacher', 
                        'query_builder' => $this->getTeachers($this->organizationBranchId),
                        'multiple' => false,
                        'expanded' => false,
                        'property_path' => 'sessionTeacher',
                        'attr' => array('class' => 'small-select'),
                        'preferred_choices' => array($this->currentTeacher))
                    );
        
        $subscriber = new AddClassSessionStudentsFieldSubscriber($builder->getFormFactory(), $options['em']);
        $builder->addEventSubscriber($subscriber);
    }
Le field subscriber:
<?php

namespace Virgule\Bundle\MainBundle\Form\EventListener;

use \Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class AddClassSessionStudentsFieldSubscriber implements EventSubscriberInterface {

    /**
     * @var FormFactoryInterface
     */
    private $factory;

    /**
     * @var EntityManager
     */
    private $em;

    /**
     * @param factory FormFactoryInterface
     */
    public function __construct(FormFactoryInterface $factory, EntityManager $em) {
        $this->factory = $factory;
        $this->em = $em;
    }

    public static function getSubscribedEvents() {
        return array(
            FormEvents::PRE_BIND => 'preBind',
            FormEvents::PRE_SET_DATA => 'preSetData',
        );
    }

    /**
     * @param \Symfony\Component\Form\FormEvent $event
     */
    public function preSetData(FormEvent $event) {
        /** @var ClassSession $data */
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data || null === $data->getCourse()) {
            return;
        }

        $courseId = $data->getCourse()->getId();
        $this->customizeForm($form, $courseId);
    }

    public function preBind(FormEvent $event) {
        $data = $event->getData();
        $courseId = $data['course_id'];
        $form = $event->getForm();

        $this->customizeForm($form, $courseId);
    }

    protected function customizeForm($form, $courseId) {
        if ($courseId) {
            $field = $this->factory->createNamed('classSessionStudents', 'entity', new ArrayCollection(), array(
                'class' => 'VirguleMainBundle:Student',
                'query_builder' => function(EntityRepository $er) use ($courseId) {
                    return $er->createQueryBuilder('s')
                                    ->add('orderBy', 's.lastname ASC, s.firstname ASC')
                                    ->innerJoin('s.courses', 'c2', 'WITH', 'c2.id = ?1')
                                    ->setParameter('1', $courseId);
                },
                'expanded' => true,
                'multiple' => true,
                'property_path' => 'classSessionStudents',
                'property' => 'fullname'
                    ));
            $form->add($field);
                    
            $form->add('course_id', 'hidden', array(
                'data' => $courseId,
                'mapped' => false))
            ;
        }
    }

}
Dans ce dernier je passe un ArrayCollection au createNamed afin de pouvoir passer mon champ en multiple. Je sais pas trop si le "preBind" sert à quelque chose, je récupère toujours l'objet cours dans mon controller, dans createAction. Je n'ai pas non plus compris comment j'aurais pu me passer du champ "hidden".

Je serais ravi d'avoir un avis sur tout ça :)

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 03 mai 2013, 15:08

Ok merci, donc j'ai viré tout ça. A l'exception du problème avec mon champ quand "multiple = true", mon formulaire s'affiche correctement.

Mon problème est désormais à la réception du formulaire, sans champ hidden, et sans ma classSession correctement initialisée, comment puis-je savoir sur quel cours porte mon compte-rendu ?

Dans mon createAction j'initialise avec un nouvel objet, donc je ne récupère pas mon cours dans le preSetData, mais en même temps il me faut bien un ClassSession pour initialiser le formulaire:
    public function createAction(Request $request) {
        $entity = new ClassSession();      // ========> Good ?
        
        $organizationBranchId = $this->getSelectedOrganizationBranchId();
        $currentTeacher = $this->getConnectedUser();
                
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $organizationBranchId, $currentTeacher), $entity, array(
    'em' => $this->getDoctrine()->getManager(),
J'ai beau relire la doc et tes messages, j'avoue que je ne comprends pas trop :)

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par Yosh » 03 mai 2013, 14:56

Oui c'est bien le point abordé dans la doc.

Par contre, le fait d'initialiser la course dans ton controller en faisant:
$classSession = new ClassSession();
$classSession->setCourse($course);
Cela te permet de complètement supprimer ton champ hidden course_id, tu en avais besoin avant car tu ne faisait pas l'initialisation.

Ensuite ta classe permettant de transformer un ID en Entity n'est plus utile non plus dans ce cas.

Donc pour reprendre (il vaut mieux y aller par étape), ton controller semble ok.

ClassSessionType:
public function buildForm(FormBuilderInterface $builder, array $options) {    
        $now = new \DateTime('now');
        $sNow = $now->format('d/m/Y');
       
        $builder
                ->add('sessionDate', 'date', array(
                    'widget' => 'single_text',
                    'format' => 'dd/MM/yyyy',
                    'attr' => array('class' => 'date', 'value' => $sNow)))
                ->add('summary')          
                ->add('sessionTeacher', 'entity', array(
                        'class' => 'VirguleMainBundle:Teacher',
                        'query_builder' => $this->getTeachers($this->organizationBranchId),
                        'multiple' => false,
                        'expanded' => false,
                        'property_path' => 'sessionTeacher',
                        'attr' => array('class' => 'small-select'),
                        'preferred_choices' => array($this->currentTeacher))
                    )
                /*->add('course_id', 'hidden', array(
                    'data' => $data->getCourse()->getId(),
                    'mapped' => false))*/ --------------------------------------> plus besoin de ce champ, l'id de la course est stocker dans l'entité
                ;
               
         // this assumes that the entity manager was passed in as an option
        //$entityManager = $options['em'];
        //$transformer = new CourseToIdTransformer($entityManager);
/*--------------------------------------> idem plus besoin pour le moment */
       
        $subscriber = new AddClassSessionStudentsFieldSubscriber($builder->getFormFactory());
        $builder->addEventSubscriber($subscriber);
    }

FieldSubscriber
public function preSetData(FormEvent $event) {
        /** @var ClassSession $data */
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

        //ici ton objet $data fait référence à l'entité ClassSession, ce qui te permet de récupérer la course et son id (vu qu'on a fait l'initialisation dans la méthode newAction avec "$classSession->setCourse($course);")
        //je passe par la récup afin de passer la valeur à la fonction anonyme plus bas - ma version de PHP ne gère pas sinon
        $courseId = $data->getCourse()->getId();
        if ($courseId) {
            $field = $this->factory->createNamed('classSessionStudents', 'entity', $data->getCourse()->getId(), array(
                        'class' => 'VirguleMainBundle:Student',
                        'query_builder' => function(EntityRepository $er) use ($courseId) {
                            return $er->createQueryBuilder('s')
                                            ->add('orderBy', 's.lastname ASC, s.firstname ASC')
                                            ->innerJoin('s.courses', 'c2', 'WITH', 'c2.id = ?1')
                                            ->setParameter('1', $courseId);
                        },
                        'expanded' => false,
                        'multiple' => false,
                        'property_path' => 'classSessionStudents',
                        'property' => 'fullname'
                    ));
            $form->add($field);
                         
            /*$form->add('course_id', 'hidden', array(
                    'data' => $courseId,
                    'mapped' => false))
                ;*/  --------------------------------------> plus besoin de ce champ, l'id de la course est stocker dans l'entité
        }
    }
Voila déjà deux trois points que je regarderais en priorité.

Je reste dispo si tu as besoin.

++

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 03 mai 2013, 14:41

Voici:

ClassSessionController:
    /**
     * Displays a form to create a new ClassSession entity.
     *
     * @Route("/add", name="classsession_new")
     * @Route("/add/course/{id}", name="classsession_add")
     * @Template()
     */
    public function newAction(Course $course) {       
        $classSession = new ClassSession();
        $classSession->setCourse($course);
       
        $organizationBranchId = $this->getSelectedOrganizationBranchId();
        $currentTeacher = $this->getConnectedUser();
        $semesterId = $this->getSelectedSemesterId();
        
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $organizationBranchId, $currentTeacher, $semesterId), $classSession, array('em' => $this->getDoctrine()->getManager()));
       
        return array(
            'entity' => $classSession,
            'form' => $form->createView(),
            'course'=> $course
        );
    }


    /**
     * Creates a new ClassSession entity.
     *
     * @Route("/create", name="classsession_create")
     * @Method("POST")
     * @Template("VirguleMainBundle:ClassSession:new.html.twig")
     */
    public function createAction(Request $request) {
        $entity = new ClassSession();      
        //$form = $this->initClassSessionForm($entity, $courseId);
        
        $organizationBranchId = $this->getSelectedOrganizationBranchId();
        $currentTeacher = $this->getConnectedUser();
                
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $organizationBranchId, $currentTeacher), $entity, array(
    'em' => $this->getDoctrine()->getManager(),
));
        $form->bind($request);   
        
        $entity->setReportDate(new \Datetime('now'));
            
        $connectedUser = $this->getConnectedUser();
        $entity->setReportTeacher($connectedUser);
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($entity);           
            $em->flush();

            return $this->redirect($this->generateUrl('classsession_index'));
        }
        
        $this->logDebug("Students saved: " . count($entity->getClassSessionStudents()));
        foreach ($entity->getClassSessionStudents() as $student) {
            $this->logDebug($student->getId());
        }
        
        return array(
            'course_id' => $courseId,
            'course' => $course,
            'entity' => $entity,
            'form' => $form->createView(),
        );
    }
ClassSessionType:
<?php

namespace Virgule\Bundle\MainBundle\Form;

use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

use Virgule\Bundle\MainBundle\Entity\Teacher;
use Virgule\Bundle\MainBundle\Form\EventListener\AddClassSessionStudentsFieldSubscriber;
use Virgule\Bundle\MainBundle\Form\DataTransformer\CourseToIdTransformer;

class ClassSessionType extends AbstractType {
        
    private $organizationBranchId;
    
    private $doctrine;
    
    private $currentTeacher;

    public function __construct(RegistryInterface $doctrine, $organizationBranchId = null, Teacher $currentTeacher = null) {
        $this->doctrine = $doctrine;
        $this->organizationBranchId = $organizationBranchId;
        $this->currentTeacher = $currentTeacher;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {    
        $now = new \DateTime('now');
        $sNow = $now->format('d/m/Y');
        
        $builder
                ->add('sessionDate', 'date', array(
                    'widget' => 'single_text',
                    'format' => 'dd/MM/yyyy',
                    'attr' => array('class' => 'date', 'value' => $sNow)))
                ->add('summary')          
                ->add('sessionTeacher', 'entity', array(
                        'class' => 'VirguleMainBundle:Teacher', 
                        'query_builder' => $this->getTeachers($this->organizationBranchId),
                        'multiple' => false,
                        'expanded' => false,
                        'property_path' => 'sessionTeacher',
                        'attr' => array('class' => 'small-select'),
                        'preferred_choices' => array($this->currentTeacher))
                    )
                ->add('course_id', 'hidden', array(
                    'data' => $data->getCourse()->getId(),
                    'mapped' => false))
                ;
                
         // this assumes that the entity manager was passed in as an option
        $entityManager = $options['em'];
        $transformer = new CourseToIdTransformer($entityManager);
        
        $subscriber = new AddClassSessionStudentsFieldSubscriber($builder->getFormFactory());
        $builder->addEventSubscriber($subscriber);
    }
    
    private function getTeachers($organizationBranchId) {
        $qb = $this->doctrine->getRepository('VirguleMainBundle:Teacher')->getAvailableTeachersQueryBuilder($organizationBranchId);
        return $qb;
    }
    
    private function getStudents($courseId) {
        $qb = $this->doctrine->getRepository('VirguleMainBundle:Student')->getQueryBuilderForStudentEnrolledInCourses(Array($courseId));
        return $qb;
    }
    
    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'data_class' => 'Virgule\Bundle\MainBundle\Entity\ClassSession',
        ));
        
        
        $resolver->setRequired(array(
            'em',
        ));

        $resolver->setAllowedTypes(array(
            'em' => 'Doctrine\Common\Persistence\ObjectManager',
        ));
    }

    public function getName() {
        return 'virgule_bundle_mainbundle_classsessiontype';
    }

}
Dans celui-ci, je ne sais pas comment accéder à l'objet ClassSession passé en paramètre du Form, ici:
 $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $organizationBranchId, $currentTeacher, $semesterId), $classSession, array('em' => $this->getDoctrine()->getManager()));
Afin de récupérer l'ID du cours pour mon champ caché.

Et donc le field subscriber:
<?php

namespace Virgule\Bundle\MainBundle\Form\EventListener;

use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class AddClassSessionStudentsFieldSubscriber implements EventSubscriberInterface {

    /**
     * @var FormFactoryInterface
     */
    private $factory;

    /**
     * @var EntityManager
     */
    private $om;
    
    /**
     * @param factory FormFactoryInterface
     */
    public function __construct(FormFactoryInterface $factory) {
        $this->factory = $factory;
    }

    public static function getSubscribedEvents() {
        return array(
            FormEvents::PRE_BIND => 'preBind',
            FormEvents::PRE_SET_DATA => 'preSetData',
        );
    }

    /**
     * @param \Symfony\Component\Form\FormEvent $event
     */
    public function preSetData(FormEvent $event) {
        /** @var ClassSession $data */
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

        //ici ton objet $data fait référence à l'entité ClassSession, ce qui te permet de récupérer la course et son id (vu qu'on a fait l'initialisation dans la méthode newAction avec "$classSession->setCourse($course);")
        //je passe par la récup afin de passer la valeur à la fonction anonyme plus bas - ma version de PHP ne gère pas sinon
        $courseId = $data->getCourse()->getId();
        if ($courseId) {
            $field = $this->factory->createNamed('classSessionStudents', 'entity', $data->getCourse()->getId(), array(
                        'class' => 'VirguleMainBundle:Student',
                        'query_builder' => function(EntityRepository $er) use ($courseId) {
                            return $er->createQueryBuilder('s')
                                            ->add('orderBy', 's.lastname ASC, s.firstname ASC')
                                            ->innerJoin('s.courses', 'c2', 'WITH', 'c2.id = ?1')
                                            ->setParameter('1', $courseId);
                        },
                        'expanded' => false,
                        'multiple' => false,
                        'property_path' => 'classSessionStudents',
                        'property' => 'fullname'
                    ));
            $form->add($field);
                          
            $form->add('course_id', 'hidden', array(
                    'data' => $courseId,
                    'mapped' => false))
                ;
        }
    }
    
    public function preBind(FormEvent $event) {
        $data = $event->getData();
    }
}
Le field transformer mais que je n'utilise pas encore:
<?php

namespace Virgule\Bundle\MainBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\TaskBundle\Entity\Issue;

/**
 * Description of CourseToNumberTransformer
 *
 * @author guillaume
 */
class CourseToIdTransformer implements DataTransformerInterface {

    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om) {
        $this->om = $om;
    }

    /**
     * Transforms an object (issue) to a string (number).
     *
     * @param  Issue|null $issue
     * @return string
     */
    public function transform($course) {
        if (null === $course) {
            return "";
        }

        return $course->getId();
    }

    /**
     * Transforms a string (id) to an object (course).
     *
     * @param  string $id
     *
     * @return Course|null
     *
     * @throws TransformationFailedException if object (course) is not found.
     */
    public function reverseTransform($id) {
        if (!$id) {
            return null;
        }

        $issue = $this->om
                ->getRepository('VirguleMainBundle:Course')
                ->findOneBy(array('id' => $id))
        ;

        if (null === $course) {
            throw new TransformationFailedException(sprintf(
                            'A course with ID "%s" does not exist!', $id
            ));
        }

        return $course;
    }

}

?>
Donc voilà, de ce que j'ai compris de tes explications, le point de la doc qui se rapporte à mon problème serait celui-là: http://symfony.com/doc/current/cookbook ... tted-forms

Je me trompe ?

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par Yosh » 03 mai 2013, 14:31

Dans ton exemple tu as mis $data['courseId'], c'est une erreur où c'est censé marcher ? Car $data contient ma classSession donc je ne peux pas y accéder en tant que tableau.

Erreur de ma part, désolé.

Tu pourrais mettre tous les fichiers utilisés ? Ce sera plus simple pour faire un point d'ensemble.

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 03 mai 2013, 13:52

J'ai commencé à bosser dessus, j'ai déjà un problème avec la création de mes checkboxes avec l'event, dès que je passe en multiple = true j'ai cette erreur :
Expected argument of type "Doctrine\Common\Collections\Collection", "integer" given
En attendant je mets en multiple = false, et si je comprends bien la doc j'ai besoin du preBindData pour récupérer le cours transmis par le formulaire.
<?php

namespace Virgule\Bundle\MainBundle\Form\EventListener;

use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class AddClassSessionStudentsFieldSubscriber implements EventSubscriberInterface {

    /**
     * @var FormFactoryInterface
     */
    private $factory;

    /**
     * @var EntityManager
     */
    private $om;
    
    /**
     * @param factory FormFactoryInterface
     */
    public function __construct(FormFactoryInterface $factory) {
        $this->factory = $factory;
    }

    public static function getSubscribedEvents() {
        return array(
            FormEvents::PRE_BIND => 'preBind',
            FormEvents::PRE_SET_DATA => 'preSetData',
        );
    }

    /**
     * @param \Symfony\Component\Form\FormEvent $event
     */
    public function preSetData(FormEvent $event) {
        /** @var ClassSession $data */
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

        //ici ton objet $data fait référence à l'entité ClassSession, ce qui te permet de récupérer la course et son id (vu qu'on a fait l'initialisation dans la méthode newAction avec "$classSession->setCourse($course);")
        //je passe par la récup afin de passer la valeur à la fonction anonyme plus bas - ma version de PHP ne gère pas sinon
        $courseId = $data->getCourse()->getId();
        if ($courseId) {
            $field = $this->factory->createNamed('classSessionStudents', 'entity', $data->getCourse()->getId(), array(
                        'class' => 'VirguleMainBundle:Student',
                        'query_builder' => function(EntityRepository $er) use ($courseId) {
                            return $er->createQueryBuilder('s')
                                            ->add('orderBy', 's.lastname ASC, s.firstname ASC')
                                            ->innerJoin('s.courses', 'c2', 'WITH', 'c2.id = ?1')
                                            ->setParameter('1', $courseId);
                        },
                        'expanded' => false,
                        'multiple' => false,
                        'property_path' => 'classSessionStudents',
                        'property' => 'fullname'
                    ));
            $form->add($field);
                          
            $form->add('course_id', 'hidden', array(
                    'data' => $courseId,
                    'mapped' => false))
                ;
        }
    }
    
    public function preBind(FormEvent $event) {
        $data = $event->getData();
    }
}
Mais après avoir soumis le formulaire, je passe aussi dans le preSetData et donc cela fait une erreur:
Call to a member function getId() on a non-object in /var/www/html/Virgule/src/Virgule/Bundle/MainBundle/Form/EventListener/AddClassSessionStudentsFieldSubscriber.php line 51
Car $data est un objet ClassSession vide:
FatalErrorException: Error: Call to a member function getId() on a non-object in /var/www/html/Virgule/src/Virgule/Bundle/MainBundle/Form/EventListener/AddClassSessionStudentsFieldSubscriber.php line 51
Du coup j'ai l'impression d'en être au même point qu'avant, avec plus de fichiers et plus de code :P

Dans ton exemple tu as mis $data['courseId'], c'est une erreur où c'est censé marcher ? Car $data contient ma classSession donc je ne peux pas y accéder en tant que tableau.

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par Yosh » 25 avr. 2013, 16:00

Pas de soucis.

C'est juste des pistes, pour le datatransformer, je ne pense pas que tu en ai vraiment besoin, regarde plutôt les fieldsubscriber.

Si tu galère trop on peut se faire une session skype pour en discuter si tu veux.

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 25 avr. 2013, 15:44

Déjà premier point, j'ai perdu le nom du process mais pas grave (si je remet la main dessus, je te mettrais un link), sais-tu que tu récupérer automatique une entity via ta méthode newAction ?
Non je ne le savais pas, merci.

Pour le reste je vais prendre le temps de lire et de faire des essais. J'étais déjà tombé sur les DataTransformer mais je ne voyais pas en quoi ça pouvait m'aider, c'est parfois difficile d'appliquer la doc à ses propres problèmes.

Un très grand merci :)

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par Yosh » 25 avr. 2013, 09:45

Si je fais ça dans createAction ça fonctionne, les étudiants sélectionnés sont enregistrés, mais c'est crado non ?
        $form1 = $this->createForm(new ClassSessionType($this->getDoctrine(), null, $organizationBranchId, $currentTeacher), $entity);
        $form1->bind($request);
        $courseId = $form1->get('course_id')->getData();
        
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $courseId, $organizationBranchId, $currentTeacher), $entity);
        $form->bind($request);        
C'est plus que crado :)

Je comprend ton soucis.

Pourquoi tu veux absolument passer par un champ hidden ? De ce que je comprend, ton champ hidden correspond à une entité Course ? et tu te simplifierait énormément la vie en l'utilisant en tant que tel.

Déjà premier point, j'ai perdu le nom du process mais pas grave (si je remet la main dessus, je te mettrais un link), sais-tu que tu récupérer automatique une entity via ta méthode newAction ?

Au lieu de
public function newAction($courseId)
Essaye
public function newAction(Course $course) {
Cela va te permettre de dire à sf2 que l'entité à récupérer est de type Course dont l'id est celui de la requête.

Juste une précision au cas ou.

Pour en revenir à ton cas, maintenant que tu as une entité course, je ferais un truc tout simple. C'est bien une création de session ?

Je ferais un truc tout simple, en omettant volontairement tout le reste
public function newAction(Course $course) {
        $classSession = new ClassSession();
        $classSession->setCourse($course);
       
        $form = $this->createForm(new ClassSessionType(), $classSession);
       
        return array(
            'entity' => $classSession,
            'form' => $form->createView(),
            'course'=> $course
        );
    }
9a te permet de dire que tu créé un formulaire de type ClassSession dont la course est défini.

Point important et je te laisse te référer à la doc, un form à 3 jeux de données, les données sources, les données normalisé et les données de la vue (ou truc dans le genre), en faisant ce que l'on vient de faire on initialise les data sources, ce qui va nous permettre d'utiliser un truc très sympa, c'est la génération de form dynamique.

FORM
public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $subscriber = new AddClassSessionStudentsFieldSubscriber($builder->getFormFactory());
        $builder->addEventSubscriber($subscriber);
    }
FIELD SUBSCRIBER (cf. http://symfony.com/doc/current/cookbook ... ation.html)

A gérer dans le PRE_SET_DATA (voir si le PRE_BIND est nécessaire)
/**
     * @param \Symfony\Component\Form\FormEvent $event
     */
    public function preSetData(FormEvent $event)
    {
        /** @var ClassSession $data */
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

//ici ton objet $data fait référence à l'entité ClassSession, ce qui te permet de récupérer la course et son id (vu qu'on a fait l'initialisation dans la méthode newAction avec "$classSession->setCourse($course);")
//je passe par la récup afin de passer la valeur à la fonction anonyme plus bas - ma version de PHP ne gère pas sinon
        $courseId = $data->getCourse()->getId();
if($courseId) {
        $form->add($this->factory->createNamed('classSessionStudents', 'entity', $data['courseId'], array(
             'class' => 'VirguleMainBundle:Student',
                      'query_builder' => function(EntityRepository $er) use ($courseId) {
                                          return $er->createQueryBuilder('s')
                                              ->innerJoin('s.courses', 'c2', 'WITH', 'c2.id = ?1')
                                              ->setParameters(array('1' => $courseId));
                                          },
                      'expanded' => true,
                      'multiple' => true,
                      'property_path' => 'classSessionStudents',
                      'property' => 'fullname'
        )));
}
Et a priori, tu n'a rien de plus à faire.

EDIT: a priori, il va peut-être te falloir laissé le champ hidden, et dans ce cas, je te conseille de passer par un DataTransformer qui te permet de transformer un id en entité et vice versa pour les form. bref de la lecture déjà et du code a adapter je pense.

A tester.

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 24 avr. 2013, 17:56

Si je fais ça dans createAction ça fonctionne, les étudiants sélectionnés sont enregistrés, mais c'est crado non ?
        $form1 = $this->createForm(new ClassSessionType($this->getDoctrine(), null, $organizationBranchId, $currentTeacher), $entity);
        $form1->bind($request);
        $courseId = $form1->get('course_id')->getData();
        
        $form = $this->createForm(new ClassSessionType($this->getDoctrine(), $courseId, $organizationBranchId, $currentTeacher), $entity);
        $form->bind($request);        

Re: [Symfony2] Form field à sauver et utilisé dans Query Bui

par ouckileou » 24 avr. 2013, 17:37

En fait je réalise que je n'ai pas bien posé la question dans mon premier post, c'est ça mon problème de base: les valeurs sélectionnées dans les cases à cocher générée par le QueryBuilder ne sont pas sauvées, vu que à la création du formulaire dans le createAction, le $courseId est null.

Et j'ai du mal à comprendre pourquoi, vu que pour moi le QueryBuilder sert à construire cette liste, mais qu'une fois les valeurs sélectionnées transmises, on s'en fout on en a plus besoin.

Voici un screenshot du formulaire généré si ça aide à la compréhension, URL: demo/web/classsession/add/course/1