Symfony2 form collection

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Roel -

Roel -

02/05/2015 16:03:16
Quote Anchor link
Hi,

Ik heb een webapp waarmee klanten beheerd kunnen worden. Om het een stuk dynamischer te maken, is het mogelijk om aangepaste velden in de database te kunnen zetten zodat deze per klant ingevuld kunnen worden. Dit is hoe het er globaal uitziet: (alleen de belangrijkste ORM annotations zijn weergegeven)

Customer:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class Customer
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */

    private $id;
    private $firstname;
    private $insertion;
    private $lastname;
    private $address;
    private $city;
    private $zipcode;
    private $gender;

    /**
     * @ORM\OneToMany(targetEntity="PropertyValue", mappedBy="customer")
     */

    private $propertyValues;
    private $active;
}

?>


Property:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
class Property
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */

    private $id;
    private $name;
    private $required;

    /**
     * @ORM\OneToMany(targetEntity="Value", mappedBy="property")
     */

    private $values;

    /**
     * @ORM\OneToMany(targetEntity="PropertyValue", mappedBy="property")
     */

    private $propertyValues;
    private $type;
    private $active;
}

?>


PropertyValue:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
class PropertyValue
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */

    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Customer", inversedBy="propertyValues")
     * @ORM\JoinColumn(name="customer_id", referencedColumnName="id", nullable=false)
     */

    private $customer;

    /**
     * @ORM\ManyToOne(targetEntity="Property", inversedBy="propertyValues")
     * @ORM\JoinColumn(name="property_id", referencedColumnName="id", nullable=false)
     */

    private $property;

    /**
     * @ORM\ManyToOne(targetEntity="Value", inversedBy="propertyValues")
     * @ORM\JoinColumn(name="value_id", referencedColumnName="id", nullable=true)
     */

    private $referencedValue;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */

    private $value;
}

?>


Ik heb een form class aangemaakt voor het aanpassen van klanten:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
class CustomerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('firstname')
            ->
add('insertion')
            ->
add('lastname')
            ->
add('gender', 'choice', array(
                'choices' =>  array(1 => 'Man', 2 => 'Vrouw'),
                'expanded' => true,
                'multiple' => false
            ))
            ->
add('address')
            ->
add('city')
            ->
add('zipcode')
            ->
add('propertyvalues', 'collection', array('type' => new PropertyValueType()));
    }


    public function configureOptions(OptionsResolver $resolver)
    {

        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Customer'
        ));
    }


    public function getName()
    {

        return 'customer';
    }
}

?>


En dan de form class van PropertyValue:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class PropertyValueType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('value', 'text');
    }


    public function configureOptions(OptionsResolver $resolver)
    {

        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\PropertyValue'
        ));
    }


    public function getName()
    {

        return 'propertyvalue';
    }
}

?>


Naar mijn idee moet het zo, maar ik krijg de volgende foutmelding en heb na een tijd Googelen nog steeds geen idee wat ik fout doe:
The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class AppBundle\Entity\PropertyValue. You can avoid this error by setting the "data_class" option to "AppBundle\Entity\PropertyValue" or by adding a view transformer that transforms an instance of class AppBundle\Entity\PropertyValue to scalar, array or an instance of \ArrayAccess.

Kan iemand me helpen? Bij voorbaat dank! :)
Gewijzigd op 02/05/2015 16:05:34 door Roel -
 
PHP hulp

PHP hulp

19/04/2024 20:38:09
 
Frank Nietbelangrijk

Frank Nietbelangrijk

03/05/2015 11:58:43
Quote Anchor link
Laat je Action (controller) eens zien?
 
Roel -

Roel -

03/05/2015 13:07:41
Quote Anchor link
Dat is inderdaad misschien handig om ook even te laten zien. Dit is mijn controller class:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
namespace AppBundle\Controller;

use AppBundle\Form\CustomerType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class CustomerController extends Controller
{
    /**
     * @Route("/customers", name="customers")
     */

    public function indexAction()
    {

        $em = $this->getDoctrine()->getManager();
        $customers = $em->getRepository('AppBundle:Customer')->findAll();

        return $this->render('customer/index.html.twig', array(
            'customers' => $customers
        ));
    }


    /**
     * @Route("/customer/{id}", name="customer_details")
     */

    public function detailsAction($id, Request $request)
    {

        $result = null;

        $repository = $this->getDoctrine()
            ->
getRepository('AppBundle:Customer');
        $customer = $repository
            ->find($id);

        if (!$customer) {
            throw $this->createNotFoundException(
                'Er is geen klant gevonden met ID ' . $id
            );
        }


        $form = $this->createForm(new CustomerType(), $customer);

        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            if ($form->isValid()) {
                $em = $this->getDoctrine()->getManager();

                $em->persist($customer);
                $em->flush();

                $result = new Response(json_encode(array(
                        'url' => $this->generateUrl('customers'))
                ));

                $result->headers->set('Content-Type', 'application/json');
            }
else {
                $validator = $this->get('validator');
                $errors = $validator->validate($customer);

                $messages = array();

                foreach ($errors as $error) {
                    $messages['message'] = $error->getMessage();
                    $messages['property'] = $error->getPropertyPath();
                }


                $result = new Response(json_encode(array(
                    'errors' => $messages)
                ));

                $result->headers->set('Content-Type', 'application/json');
            }
        }


        if ($result == null) {
            $result = $this->render('customer/details.html.twig', array(
                'form' => $form->createView()
            ));
        }


        return $result;
    }
}

?>


Het gaat in dit geval om detailsAction(). Offtopic: is de manier met JSON die ik gebruik goed of kan ik hier beter iets anders doen?
Gewijzigd op 03/05/2015 14:19:31 door Roel -
 
Wouter J

Wouter J

03/05/2015 13:43:32
Quote Anchor link
Ik heb nog even niet goed naar je probleem gekeken. Maar even wat andere tips:

* Gebruik JsonResponse. Deze geef je een array mee en deze zorgt zelf voor de juiste encoding, headers, etc.
* Ga niet zelf met de validator aan de slag, de Form component doet dit al voor je! Gebruik $form->getErrors().
* Vergeet ook niet je HTTP status codes te veranderen. Je geeft nu altijd 200 terug (Success), terwijl het soms helemaal geen success is (bijv. wanneer het form niet valid is)
 
Roel -

Roel -

03/05/2015 14:15:34
Quote Anchor link
Bedankt voor je tips! Scheelt weer wat extra moeite bij validatie. Ik had de validator aangesproken omdat ik dacht dat m'n entities niet gevalideerd werden. Bleek dat ik geen constraints aan m'n entities had gegeven. Daarna ben ik vergeten het weg te halen.

Dan is dit nu m'n nieuwe controller:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
/**
     * @Route("/customer/{id}", name="customer_details")
     */

    public function detailsAction($id, Request $request)
    {

        $result = null;

        $repository = $this->getDoctrine()
            ->
getRepository('AppBundle:Customer');
        $customer = $repository
            ->find($id);

        if (!$customer) {
            throw $this->createNotFoundException(
                'Er is geen klant gevonden met ID ' . $id
            );
        }


        $form = $this->createForm(new CustomerType(), $customer);

        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            if ($form->isValid()) {
                $em = $this->getDoctrine()->getManager();

                $em->persist($customer);
                $em->flush();

                $result = new JsonResponse(array(
                    'url' => $this->generateUrl('customers')
                ));
            }
else {
                $errors = $form->getErrors();

                $messages = array();

                foreach ($errors as $error) {
                    $messages['message'] = $error->getMessage();
                    $messages['property'] = $error->getPropertyPath();
                }


                $result = new JsonResponse(array(
                    'errors' => $messages
                ), 400);
            }
        }


        if ($result == null) {
            $result = $this->render('customer/details.html.twig', array(
                'form' => $form->createView()
            ));
        }


        return $result;
    }

?>


Toevoeging op 03/05/2015 14:36:20:

Mijn lijst in $form->getErrors() is overigens leeg. Weten jullie hoe dat kan? In m'n validator is hij niet leeg.
Gewijzigd op 03/05/2015 14:37:06 door Roel -
 
Frank Nietbelangrijk

Frank Nietbelangrijk

03/05/2015 14:38:18
Quote Anchor link
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?php

    /**
     * @ORM\ManyToOne(targetEntity="Value", inversedBy="propertyValues")
     * @ORM\JoinColumn(name="value_id", referencedColumnName="id", nullable=true)
     */

    private $referencedValue;
?>


Probeerde jouw projectje even na te maken maar nu zie ik het bovenstaande. Heb je dan ook nog een entity Value? en wat is het nut hiervan?
 
Roel -

Roel -

03/05/2015 14:43:05
Quote Anchor link
Ik heb inderdaad ook nog een entity Value. Het idee daarachter is dat als er een aangepast veld komt met een dropdownbox, dat Property een collectie heeft van Value. In PropertyValue wordt daarnaar verwezen. Ik vond het nogal een lastig probleem om zowel een vrije input als vooraf gekozen input toe te staan. Nu zijn het allebei nullable velden. Snap je het idee erachter?

Die entity ziet er overigens zo uit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="values")
 */

class Value
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */

    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Property", inversedBy="values")
     * @ORM\JoinColumn(name="property_id", referencedColumnName="id", nullable=false)
     */

    private $property;

    /**
     * @ORM\OneToMany(targetEntity="PropertyValue", mappedBy="referencedValue")
     */

    private $propertyValues;

    /**
     * @ORM\Column(type="string", length=50, nullable=false)
     */

    private $value;
    /**
     * Constructor
     */

    public function __construct()
    {

        $this->propertyValues = new \Doctrine\Common\Collections\ArrayCollection();
    }

    
    // Getters en setters weggelaten
?>
Gewijzigd op 03/05/2015 14:44:56 door Roel -
 
Frank Nietbelangrijk

Frank Nietbelangrijk

03/05/2015 16:44:59
Quote Anchor link
Ben nog op zoek naar de class PropertyValueType en ValueType :p

Wat me nu wel opvalt is dat je een verwijzing hebt van de tabel propertyvalues en van de tabel values naar de tabel property. Lijkt me een beetje dubbel..
 
Roel -

Roel -

03/05/2015 18:49:35
Quote Anchor link
Zo werkt Entity Framework ook, twee kanten op. Komt altijd van pas.
PropertyValueType in een form class:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PropertyValueType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('value', 'text');
    }


    public function configureOptions(OptionsResolver $resolver)
    {

        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\PropertyValue'
        ));
    }


    public function getName()
    {

        return 'propertyvalue';
    }
}

?>


PropertyType is een geïmproviseerde enum:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace AppBundle\Entity;

abstract class PropertyType
{
    const TextBox = 1;
    const TextArea = 2;
    const CheckBox = 3;
    const CheckList = 4;
    const Selection = 5;
    const Date = 6;
    const DateTime = 7;
}

?>
Gewijzigd op 03/05/2015 18:49:48 door Roel -
 
Frank Nietbelangrijk

Frank Nietbelangrijk

03/05/2015 19:54:25
Quote Anchor link
Hmm verbaasd me niet dat je foutmeldingen krijgt.

a) Je zegt PropertyType. Ik zou verwachten ValueType.
b) de methods buildForm, configureOptions en getName zijn die niet verplicht?
c) namespace AppBundle\Entity in plaats van AppBundle\Form

Ik heb deze dus toegevoegd:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
<?php
namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ValueType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('value', 'text');
    }


    public function configureOptions(OptionsResolver $resolver)
    {

        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Value'
        ));
    }


    public function getName()
    {

        return 'value';
    }
}

?>


en bij mij werkt het zover ik kan beoordelen.
Gewijzigd op 03/05/2015 19:54:40 door Frank Nietbelangrijk
 
Wouter J

Wouter J

03/05/2015 19:58:57
Quote Anchor link
>> b) de methods buildForm, configureOptions en getName zijn die niet verplicht?

Alleen getName(), de andere worden ingevuld als empty methods door AbstractType (als je alleen FormTypeInterface implementeert zijn ze wel verplicht).
 
Roel -

Roel -

03/05/2015 22:35:19
Quote Anchor link
Waarom ValueType? Een customer heeft meerdere PropertyValues, dus heb ik een form class PropertyValueType. In CustomerType zit dan die collectie daarvan.

Toevoeging op 03/05/2015 22:37:58:

Het is overigens ook verwarrend dat er een PropertyType is en een PropertyValueType, die niet van hetzelfde soort zijn. De eerste is namelijk een enum en de tweede een form class (staat zo in de Symfony documentatie).
 
Frank Nietbelangrijk

Frank Nietbelangrijk

03/05/2015 23:57:41
Quote Anchor link
Wouter J op 03/05/2015 19:58:57:
>> b) de methods buildForm, configureOptions en getName zijn die niet verplicht?

Alleen getName(), de andere worden ingevuld als empty methods door AbstractType (als je alleen FormTypeInterface implementeert zijn ze wel verplicht).


Oke, dank je Wouter.
 
Roel -

Roel -

05/05/2015 07:13:50
Quote Anchor link
Is er verder niemand die mijn probleem met betrekking tot de form collection snapt?

Toevoeging op 06/05/2015 00:56:48:

Even een offtopic vraag overigens. Waarom is de statuscode fout? Als ik 400 terugstuur krijg ik een fout in m'n console.
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.