[Symfony2] Dynamic Forms

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • maarud
  • Registratie: Mei 2005
  • Laatst online: 22:03
Ik ben een formulier aan het maken voor het toevoegen van een taak waarbij je categorieën en subcategorieëen kan toekennen.

Bij het maken van de taak kies je dus de categorie en afhankelijk vna de categorie moet de volgende dropdownbox subcategorieëen bevatten.

Daar gebruik ik deze cookbook entry voor: http://symfony.com/doc/cu...ation-for-submitted-forms

Echter is deze volgens mij niet 100% compleet. Naast dat je zelf moet uitvogelen welke entitiy relationships je moet maken (op basis van wat ik zie voeg je dus beide velden, dus categorie én subcategorie toe aan je taak, oftewel ik heb een relatie taak-> categorie en taak->subcategorie) mist er volgens mij nog een functie, namelijk hoe getAvailablePositions (of in mijn geval getAvailableSubcategories) werkt.

Een franse website zegt dat deze automatisch door Symfony 'bedacht' wordt:
Deze functie is niet die van een repository, maar eerder het resultaat van een verbinding tussen het object en een Sport AvailablePosition object, waarschijnlijk.
Kan worden afgeleid uit het feit dat de code gebruikt ongeveer $event->getData()->getSport() . Omdat data wordt vertegenwoordigd door objecten en repositories, deze kleine bovenstaande code geeft een Sport entiteit object. Dus $event->getData()->getSport()->getAvailablePositions() retourneert een verzameling, waarschijnlijk AvailablePosition objecten.
maar ik krijg toch een exception.

Mijn code is letterlijk gekopieerd waarbij ik Sport heb vervangen door Category en Positions door Subcategories.

Ik krijg nu echter deze melding als ik in de profiler naar de Ajax call kijk:
Attempted to call an undefined method named "getAvailableSubcategories" of class "AppBundle\Entity\Category".
500 Internal Server Error - UndefinedMethodException
Vandaar dus mijn vraag.

Andere tutorials gebruiken een andere werkwijze (deze gebruikt de entity 'Category', in zijn geval 'Province' alleen voor de lookup) maar ik wil proberen de 'officiële' Symfony docs te gebruiken waar mogelijk.

[ Voor 20% gewijzigd door maarud op 04-08-2015 11:54 ]


Acties:
  • 0 Henk 'm!

  • kwaakvaak_v2
  • Registratie: Juni 2009
  • Laatst online: 10-10 08:02
De error is anders best duidelijk ;) In de class AppBundle\Entity\Category mist de functie getAvailableSubcategories

Wat die functie precies terug moet geven is aan jouw om te bepalen, maar ik gok iets van een array met value=>label erin.

Driving a cadillac in a fool's parade.


Acties:
  • 0 Henk 'm!

  • s.vaessen
  • Registratie: April 2010
  • Laatst online: 22-06 08:26
De foutmelding lijkt me redelijk duidelijk, de functie "getAvailableSubcategories" bestaat niet in je entity. Geen typo toevallig (getAvailableSubCategories)?

Acties:
  • 0 Henk 'm!

  • maarud
  • Registratie: Mei 2005
  • Laatst online: 22:03
Dat snap ik, maar ik heb niet zoveel kennis van Symfony dat ik dat er bij kan sleutelen. Ik heb getracht de functie in de repository bij te maken maar ik ontvang geen ID van de category (het is simpelweg getAvailableSubcategories()).

Dus schijnbaar weet hij a.d.h.v. het object (?) al welk ID het betreft, maar dat stapje is mij net iets te ver en dit hele stuk ontbreekt in het Cookbook.

Mijn Subcategory entity heeft id, category_id en name, en ik moet idd iets maken wat a.d.h.v. een category_id een array met names retourneert.

Maar nu ik weet dta ik het in de enitity moet zoeken (best logisch eigenlijk) ga ik de functie maar even aanmaken, kijken hoe ver ik kom :)

edit:
Ik heb getAvailableSubcategories() vervangen door getSubcategories (deze functie zit staandaard in de Entity) maar dan krijg ik de error dat ik geen lege velden in mijn tabel mag voegen. Schijnbaar wil de Ajax call al de form submitten (dat is ook logisch want de EventListener doet een POST_SUBMIT).

Maar als dat pas POST_SUBMIT gebeurt, hoe kan ik dan een dynamische subcategorie dropdownbox hebben terwijl het formulier nog in beeld is? Klopt de Cookbook wel?

edit2: Ik heb een mengseltje gemaakt van wat tutorials, zal morgen posten. Het werkt iig, of het solide is, is vraag 2 :+

[ Voor 30% gewijzigd door maarud op 04-08-2015 17:01 ]


Acties:
  • 0 Henk 'm!

  • maarud
  • Registratie: Mei 2005
  • Laatst online: 22:03
Uiteindelijke oplossing (voor mij):

Category.php
PHP:
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
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Category
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class Category
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;
    
    /**
     * @ORM\OneToMany(targetEntity="Subcategory", mappedBy="category")
     */
    protected $subcategories;

    public function __construct()
    {
        $this->subcategories = new ArrayCollection();
    }


Subcategory.php
PHP:
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
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * Subcategory
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class Subcategory
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @Groups({"subcatgroup"})
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     * @Groups({"subcatgroup"})
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="Subcategories")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    protected $category;


Task.php
PHP:
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
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Task
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="AppBundle\Entity\TaskRepository")
 */
class Task
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="date_created", type="datetime")
     */
    private $dateCreated;

    /**
     * @var integer
     *
     * @ORM\ManyToOne(targetEntity="User", inversedBy="task")
     * @ORM\JoinColumn(name="creator", referencedColumnName="id")
     */
    private $creator;
    
    /**
     * @var integer
     *
     * @ORM\Column(name="priority", type="integer")
     */
    private $priority;

    /**
     * @var integer
     *
     * @ORM\Column(name="status", type="integer")
     */
    private $status;
    
    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="string", length=255)
     */
    private $description;
    
    /**
     * @ORM\OneToMany(targetEntity="Taskcomments", mappedBy="Task")
     */
    protected $comments;
    
    /**
     * @ORM\OneToMany(targetEntity="Taskattachments", mappedBy="Task")
     */
    protected $attachments;
    
    /**
     * @ORM\ManyToOne(targetEntity="Category")
     */
    protected $category;
    
    /**
     * @ORM\ManyToOne(targetEntity="Subcategory")
     */
    protected $subcategory;

    public function __construct()
    {
        $this->comments = new ArrayCollection();
        $this->attachments = new ArrayCollection();
        $this->dateCreated = new \DateTime();
    }


De TaskType.php heb ik gewoon overgenomen van de Symfony website, maar dan met een extra property=name zodat hij geen toString error geeft.
PHP:
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
public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            //->add('status')
            ->add('title')
            ->add('priority', 'choice', array(
                'choices' => array(1 => 'Hoog', 2 => 'Middel', 3 => 'Laag'),
                'placeholder' => '')
            )           
            ->add('description', 'textarea')
            ->add('category', 'entity', array(
            'class'       => 'AppBundle:Category',
            'placeholder' => '',
            'property'    => 'name'
        ))
        ;
        
        $formModifier = function (FormInterface $form, Category $category = null) {
            $subcategories = null === $category ? array() : $category->getSubcategories();
            $form->add('subcategory', 'entity', array(
                'class'       => 'AppBundle:Subcategory',
                'placeholder' => '',
                'choices'     => $subcategories,
                'property'    => 'name'
            ));
        };

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifier) {
                // this would be your entity, i.e. SportMeetup
                $data = $event->getData();

                $formModifier($event->getForm(), $data->getCategory());
            }
        );

        $builder->get('category')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifier) {
                // It's important here to fetch $event->getForm()->getData(), as
                // $event->getData() will get you the client data (that is, the ID)
                $category = $event->getForm()->getData();

                // since we've added the listener to the child, we'll have to pass on
                // the parent to the callback functions!
                $formModifier($event->getForm()->getParent(), $category);
            }
        );
    }


Maar dan nu...omdat de Symfony tutorial errors gaf (hij wilde de form submitten en toen kreeg ik allemaal 'cannot insert into .... null null null' errors) heb ik de controller iets uitgebreid.

Vóór de
PHP:
1
if ($form->isValid()) {}


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if ($request->isXmlHttpRequest()){
            $id1 = $request->request->get('appbundle_task');
            $id = $id1['category'];
            $em = $this->getDoctrine()->getManager();
            $subcategories = $em->getRepository('AppBundle:Subcategory')->findByCategory($id);
            
            /* return $this->render('Task/new.html.twig', array(
                'entity' => $entity,
                'form'   => $form->createView(),
            )); */
            
            $jsonserial = $serializer->serialize($subcategories, 'json', array('groups' => array('subcatgroup')));
            $response = new Response();
            $response->headers->set('Content-Type', 'application/json');
            $response->setContent($jsonserial);;
            return $response;
        }


Dit komt van http://stackoverflow.com/...to-another-one-using-ajax
Het gekke is, $id1 = $request->request->get('appbundle_task'); zou al de category_id moeten returnen maar die zit nog één level dieper, vandaar dus mijn extra id variabele.

In het voorbeeld van Symfony stuurt de controller gewoon de hele HTML terug maar uit tijdsbesparing heb ik er een JSON string van gemaakt met de ingebouwde serializer. Die deed het eerst ook niet vanwege een circular reference (category->subcategory->category->subcategory etc) dus heb ik met Annotations een group aangemaakt zodat alleen de ID + naam wordt geserialized.


Dit werkt nu dankzij wat kunst en vliegwerk, maar ik vind het vreemd dat de Symfony cookbook manier niet in eerste instantie werkt. Ik vraag mij dan ook af of 'mijn' manier wel programmeur-waardig is of dat ik straks in het programmeursfouten-topic mag gaan posten :+

edit: Het gekke is, nu werkt de cookbook manier wel 8)7. De isXmlHttpRequest heb ik er nu dus maar weer uitgehaald 8)7