Symfony2 form collection
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}
?>
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)
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
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;
}
?>
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)
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
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;
}
?>
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)
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
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';
}
}
?>
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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';
}
}
?>
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 -
Laat je Action (controller) eens zien?
Code (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
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;
}
}
?>
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 -
* 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)
Dan is dit nu m'n nieuwe controller:
Code (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
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;
}
?>
/**
* @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 -
Code (php)
1
2
3
4
5
6
7
8
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;
?>
/**
* @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?
Die entity ziet er overigens zo uit:
Code (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
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
?>
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 -
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..
PropertyValueType in een form class:
Code (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
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';
}
}
?>
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)
Gewijzigd op 03/05/2015 18:49:48 door Roel -
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)
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
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';
}
}
?>
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
Alleen getName(), de andere worden ingevuld als empty methods door AbstractType (als je alleen FormTypeInterface implementeert zijn ze wel verplicht).
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).
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).
Alleen getName(), de andere worden ingevuld als empty methods door AbstractType (als je alleen FormTypeInterface implementeert zijn ze wel verplicht).
Oke, dank je Wouter.
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.