As of beta 5, the new Form component of Zend Framework 2 gained a lot of new, exciting features ! As always, feel free to test them to tell us how it works ! In this article, I will describe them using a concrete use-case, and I will show you some best practices about forms in ZF 2. But before, here are the new features of beta 5 :
- All HTML5 elements are now present. This means that it will produce correct HTML5 markup for inputs, validate and filter the value.
- Fieldsets can now have their own hydrators, and we’ll see how this capability drastically simplify things when dealing with entities (for example with Doctrine 2).
- A new Collection element that allow you to easily add new elements on-the-fly using JavaScript, and still being able to validate and retrieve the values.
Our entities
In this example, we are going to use three entities. First, the Product entity :
|
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 92 93 94 95 96 97 98 |
<?php namespace Application\Entity; class Product { /** * @var string */ protected $name; /** * @var int */ protected $price; /** * @var Brand */ protected $brand; /** * @var array */ protected $categories; /** * @param string $name * @return Product */ public function setName($name) { $this->name = $name; return $this; } /** * @return string */ public function getName() { return $this->name; } /** * @param int $price * @return Product */ public function setPrice($price) { $this->price = $price; return $this; } /** * @return int */ public function getPrice() { return $this->price; } /** * @param Brand $brand * @return Product */ public function setBrand(Brand $brand) { $this->brand = $brand; return $this; } /** * @return Brand */ public function getBrand() { return $this->brand; } /** * @param array $categories * @return Product */ public function setCategories(array $categories) { $this->categories = $categories; return $this; } /** * @return array */ public function getCategories() { return $this->categories; } } |
Next, the Brand entity :
|
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 |
<?php namespace Application\Entity; class Brand { /** * @var string */ protected $name; /** * @var string */ protected $url; /** * @param string $name * @return Brand */ public function setName($name) { $this->name = $name; return $this; } /** * @return string */ public function getName() { return $this->name; } /** * @param string $url * @return Brand */ public function setUrl($url) { $this->url = $url; return $this; } /** * @return string */ public function getUrl() { return $this->url; } } |
And finally, the Category entity :
|
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 |
<?php namespace Application\Entity; class Category { /** * @var string */ protected $name; /** * @param string $name * @return Category */ public function setName($name) { $this->name = $name; return $this; } /** * @return string */ public function getName() { return $this->name; } } |
As you can see, this is really simple code. A Product has two scalar properties (name and price), a OneToOne relationship (one product has one brand), and a OneToMany relationship (one product has many categories).
But everyone who worked with ZF 1 or even ZF 2 beta 1-4 know that this simple case is already quite tricky to code, and can lead to ugly code (as you have to either override bindValues in the form or manually recreating the object). Good news, THIS IS OVER !
The fieldsets
The first step is to create three fieldsets. Each fieldset will contain all the field and the relationships for a specific entity.
Here is the Brand fieldset :
|
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 |
<?php namespace Application\Form; use Application\Entity\Brand; use Zend\Form\Fieldset; use Zend\InputFilter\InputFilterProviderInterface; use Zend\Stdlib\Hydrator\ClassMethods as ClassMethodsHydrator; class BrandFieldset extends Fieldset implements InputFilterProviderInterface { public function __construct() { parent::__construct('brand'); $this->setHydrator(new ClassMethodsHydrator(false)) ->setObject(new Brand()); $this->setLabel('Brand'); $this->add(array( 'name' => 'name', 'options' => array( 'label' => 'Name of the brand' ), 'attributes' => array( 'required' => 'required' ) )); $this->add(array( 'name' => 'url', 'type' => 'Zend\Form\Element\Url', 'options' => array( 'label' => 'Website of the brand' ), 'attributes' => array( 'required' => 'required' ) )); } /** * @return array */ public function getInputFilterSpecification() { return array( 'name' => array( 'required' => true, ) ); } } |
We can discover some new things here. As you can see, the fieldset calls the function setHydrator, giving it a ClassMethods hydrator, and the setObject function, by giving it an empty instance of a concrete Brand object.
When the data will be validated, the Form will automatically iterate through all the field sets it contains, and automatically populate the sub-objects, in order to return a complete entity.
Also notice that the “Url” element has a type ‘Zend\Form\Element\Url’. This information will be used to validate the input field. You don’t need to manually add filters or validators for this input anymore.
Finally, the getInputSpecification gives the specification for the remaining input (name), and telling that this input is required. Note that the required in the array attributes (when elements are added) is only meant to add the required attribute (and therefore have a more semantic markup).
Here is the Category fieldset :
|
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 |
<?php namespace Application\Form; use Application\Entity\Category; use Zend\Form\Fieldset; use Zend\InputFilter\InputFilterProviderInterface; use Zend\Stdlib\Hydrator\ClassMethods as ClassMethodsHydrator; class CategoryFieldset extends Fieldset implements InputFilterProviderInterface { public function __construct() { parent::__construct('category'); $this->setHydrator(new ClassMethodsHydrator(false)) ->setObject(new Category()); $this->setLabel('Category'); $this->add(array( 'name' => 'name', 'options' => array( 'label' => 'Name of the category' ), 'attributes' => array( 'required' => 'required' ) )); } /** * @return array */ public function getInputFilterSpecification() { return array( 'name' => array( 'required' => true, ) ); } } |
Nothing new here.
And finally the Product fieldset :
|
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 |
<?php namespace Application\Form; use Application\Entity\Product; use Zend\Form\Fieldset; use Zend\InputFilter\InputFilterProviderInterface; use Zend\Stdlib\Hydrator\ClassMethods as ClassMethodsHydrator; class ProductFieldset extends Fieldset implements InputFilterProviderInterface { public function __construct() { parent::__construct('product'); $this->setHydrator(new ClassMethodsHydrator(false)) ->setObject(new Product()); $this->add(array( 'name' => 'name', 'options' => array( 'label' => 'Name of the product' ), 'attributes' => array( 'required' => 'required' ) )); $this->add(array( 'name' => 'price', 'options' => array( 'label' => 'Price of the product' ), 'attributes' => array( 'required' => 'required' ) )); $this->add(array( 'type' => 'Application\Form\BrandFieldset', 'name' => 'brand', 'options' => array( 'label' => 'Brand of the product' ) )); $this->add(array( 'type' => 'Zend\Form\Element\Collection', 'name' => 'categories', 'options' => array( 'label' => 'Please choose categories for this product', 'count' => 2, 'should_create_template' => true, 'allow_add' => true, 'target_element' => array( 'type' => 'Application\Form\CategoryFieldset' ) ) )); } /** * Should return an array specification compatible with * {@link Zend\InputFilter\Factory::createInputFilter()}. * * @return array */ public function getInputFilterSpecification() { return array( 'name' => array( 'required' => true, ), 'price' => array( 'required' => true, 'validators' => array( array( 'name' => 'Float' ) ) ) ); } } |
We also have a lot of new things here !
First, notice how the brand element is added : we specify it to be of type ‘Application\Form\BrandFieldset’. This is how you handle OneToOne relationship. When the form will be validated, the BrandFieldset will first be populated, and will return a Brand entity (as we have specified a ClassMethods hydrator, and bound the fieldset to a Brand entity using the setObject method). This Brand entity will then be used to populate the Product entity by calling the setBrand method. Cool, huh ?
But this is nothing. The next element shows you how to handle OneToMany relationship. The type is ‘Zend\Form\Element\Collection’, which is a specialized element to handle such cases. As you can see, the name of the element (categories) perfectly match the name of the property in the Product entity.
This element has a few interesting options :
- count : this is how many times the element (in this case a category) has to be rendered. I set it to two.
- should_create_template : if set to true, it will generate a template markup in a <span> element, in order to simplify adding new element on the fly (we will speak about this one later).
- allow_add : if set to true (which is the default), dynamically added elements will be retrieved and validated, otherwise, they will be completely ignored. This, of course, depends on what you want to do.
- target_element : this is either an element or, as this is the case in this example, an array that describes the element or fieldset that will be used in the collection. In this case, the target_element is a Category fieldset.
The Form element
So far, so good. We now have our field sets in place. But those are field sets, not forms. And only Form instances can be validated. So here is the form :
|
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 |
<?php namespace Application\Form; use Zend\Form\Form; use Zend\InputFilter\InputFilter; use Zend\Stdlib\Hydrator\ClassMethods as ClassMethodsHydrator; class CreateProduct extends Form { public function __construct() { parent::__construct('create_product'); $this->setAttribute('method', 'post') ->setHydrator(new ClassMethodsHydrator(false)) ->setInputFilter(new InputFilter()); $this->add(array( 'type' => 'Application\Form\ProductFieldset', 'options' => array( 'use_as_base_fieldset' => true ) )); $this->add(array( 'type' => 'Zend\Form\Element\Csrf', 'name' => 'csrf' )); $this->add(array( 'name' => 'submit', 'attributes' => array( 'type' => 'submit' ) )); } } |
In fact, the CreateForm is quite simple, as it only have a Product fieldset, as well as some other useful fields (CSRF for security, and a Submit button).
Notice the use_base_fieldset options. This attribute is here to say to the form : “hey, the object I bind to you is, in fact, bound to the fieldset that is the base fieldset”. This will be to true most of the times.
What’s cool with this approach is that each entity can have its own Fieldset and can be reused. You describe the elements, the filters and validators for each entity only once, and the concrete Form instance will only compose those fieldsets. You no more have to add this “username” input to every form that deals with users
!
The controller
Now, let’s create the action in the controller :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * @return array */ public function indexAction() { $form = new CreateProduct(); $product = new Product(); $form->bind($product); if ($this->request->isPost()) { $form->setData($this->request->getPost()); if ($form->isValid()) { var_dump($product); } } return array( 'form' => $form ); } |
This is SUPER easy. Nothing to do in the controllers. All the magic is done behind the scene.
The view
And finally the view :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php $form->setAttribute('action', $this->url('home')) ->prepare(); echo $this->form()->openTag($form); $product = $form->get('product'); echo $this->formRow($product->get('name')); echo $this->formRow($product->get('price')); echo $this->formCollection($product->get('categories')); $brand = $product->get('brand'); echo $this->formRow($brand->get('name')); echo $this->formRow($brand->get('url')); echo $this->formHidden($form->get('csrf')); echo $this->formElement($form->get('submit')); echo $this->form()->closeTag($form); ?> |
A few new things here :
- the prepare function. You HAVE TO call it prior to render anything in the view (this function is only meant to be called in views, not in controllers).
- the FormRow is a new helper that render a label (if present), the input itself, and errors.
- the FormCollection helper will iterate through every elements in the collection, and render every element with the FormRow helper. If you need more control about the way you render your forms, you can iterate through the elements in the collection, and render them manually one by one.
And here is the result :
As you can see, Collection are wrapped inside a fieldset, and every item in the collection is itself wrapped in the fieldset. In fact, the Collection element uses label for each item in the collection, while the label of the Collection element itself is used to wrapped the whole collection. If you don’t want it to be wrapped, just add false as a second parameter of the the FormCollection view helper.
If you validate, all elements will show errors (this is normal, as we’ve sent them required). As soon as the form is valid, this is what we get :
As you can see, the bound object is completely field. Not with arrays, but with others objects ! Ready to be saved by Doctrine 2
!
But that’s not all…
Adding new elements dynamically
Remember the should_create_template ? We are going to use it now ! Often, forms are not completely static. In our case, let’s say that we don’t want only two categories, but we want the user to be able to add other ones at runtime !
Zend\Form is able to do that ! First, let’s see what Zend generates when we ask him to create template :
As you can see, the collection generates two fieldsets (the two categories) PLUS a span with a data-template attribute that contains the full HTML code to copy to create a new element in the collection. Of course __index__ (this is the placeholder generated) has to be changed to a valid value. Currently, we have two elements (categories[0] and categories[1], so __index__ has to be changed to two.
If you want, this placeholder (__index__ is the default) can be changed using the template_placeholder option key :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$this->add(array( 'type' => 'Zend\Form\Element\Collection', 'name' => 'categories', 'options' => array( 'label' => 'Please choose categories for this product', 'count' => 2, 'should_create_template' => true, 'template_placeholder' => '__placeholder__', 'target_element' => array( 'type' => 'Application\Form\CategoryFieldset' ) ) )); |
First, let’s add a small button “Add new category” anywhere in the form :
|
1 |
<button onclick="return add_category()">Add a new category</button> |
The add_category function is, actually, pretty simple :
- First count the number of elements we already have.
- Get the template from the span data-template attribute.
- Change the placeholder to a valid index.
- Add the element to the DOM.
|
1 2 3 4 5 6 7 8 9 10 11 |
<script> function add_category() { var currentCount = $('form > fieldset > fieldset').length; var template = $('form > fieldset > span').data('template'); template = template.replace('__index__', currentCount); $('form > fieldset').append(template); return false; } </script> |
Sorry, I am not a JavaScript guru
One small remark about the template.replace : I am using currentCount and not currentCount + 1, as the index are zero-index (so, if we have two elements in the collection, the third one will have the index 2 – 0, 1 and 2 -).
Now, if we validate the form, it will automatically take into account this new element by validating it, filtering it and retrieving it :
Yes. Zend Framework 2 is really gonna be an awesome framework
. Of course, if you don’t want that people add elements in a collection, you have to set the option allow_add to false. This way, even if new elements are added, they won’t be validated and, hence, not be adding to the entity. Here is how you do it (and, as we don’t want elements to be added, we don’t need the data template too) :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$this->add(array( 'type' => 'Zend\Form\Element\Collection', 'name' => 'categories', 'options' => array( 'label' => 'Please choose categories for this product', 'count' => 2, 'should_create_template' => false, 'allow_add' => false, 'target_element' => array( 'type' => 'Application\Form\CategoryFieldset' ) ) )); |
However, I want to highlight some limitations of this capability (this may change in the future – if you want to contribute to add these features, feel free to do it
– ) :
- Although you can add new elements and remove them, you CANNOT remove more elements in a collection than the initial count (for instance, if your code specify count == 2, you will be able to add a third one, remove it… but you won’t be able to remove more. If the initial count is 2, you have to have at least two elements.
- Dynamically added elements have to be added at the end of the collection. They can be added anywhere (these elements will still be validated and inserted into the entity), but if the validation fails, this newly added element will be automatically be replaced at the end of the collection.
Validation groups for fieldsets and collection
Now, I am going to talk about the last feature : validation groups. Validation groups allow you to validate only some fields. And this is really useful.
Imagine that, although the Brand entity has a URL property, we don’t want to user to specify it an the creation form (but in the Edit Product form for instance). Let’s update the view to remove the URL input :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php $form->setAttribute('action', $this->url('home')) ->prepare(); echo $this->form()->openTag($form); $product = $form->get('product'); echo $this->formRow($product->get('name')); echo $this->formRow($product->get('price')); echo $this->formCollection($product->get('categories')); $brand = $product->get('brand'); echo $this->formRow($brand->get('name')); echo $this->formHidden($form->get('csrf')); echo $this->formElement($form->get('submit')); echo $this->form()->closeTag($form); ?> |
This is what we get :
The URL input has disappeard, but even if we fill every inputs, the form won’t validate. In fact, this is normal. We specified in the input filter that the URL is a required field, so if the form does not have it… it won’t validate, even though we didn’t add it to the view !
Of course, you could create a BrandFieldsetWithoutURL fieldset, but of course this is not recommended, as a lot of code will be duplicated.
The solution : validation group. Validation group is specified in a Form object (hence, in our case, in the CreateProduct form), by giving an array of all the elements we want to validate. Our CreateForm now looks like this :
|
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 |
<?php namespace Application\Form; use Zend\Form\Form; use Zend\InputFilter\InputFilter; use Zend\Stdlib\Hydrator\ClassMethods as ClassMethodsHydrator; class CreateProduct extends Form { public function __construct() { parent::__construct('create_product'); $this->setAttribute('method', 'post') ->setHydrator(new ClassMethodsHydrator()) ->setInputFilter(new InputFilter()); $this->add(array( 'type' => 'Application\Form\ProductFieldset', 'options' => array( 'use_as_base_fieldset' => true ) )); $this->add(array( 'type' => 'Zend\Form\Element\Csrf', 'name' => 'csrf' )); $this->add(array( 'name' => 'submit', 'attributes' => array( 'type' => 'submit' ) )); $this->setValidationGroup(array( 'csrf', 'product' => array( 'name', 'price', 'brand' => array( 'name' ), 'categories' => array( 'name' ) ) )); } } |
Of course, don’t forget to add the “CSRF” element, as we want it to be validated too (but notice that I didn’t write the submit element, we don’t care about it). You can recursively select the elements you want.
There is one simple limitation currently (once again, it may be fixed in the future) : validation group for collection are set on a collection basis, not element in a collection basis. This means you cannot say : “validate the name input for the first element of the categories collection, but don’t validate it for the second one”. But, honestly, this is really an edge-case.
Now, the form validates (and the URL is set to null as we didn’t specify it).
Conclusion
In overall, Form in Zend Framework 2 are now extremely flexible, and should cover most of your form needs. Please use them, and don’t hesitate to report bugs (as this is still a beta, you can expect bugs !) here or, preferably, in the issue tracker. If you need help about them, I would be willing to help in zftalk.2 IRC channel !






Very good post
Thanks for the great summary about the features!
I have one question:
In one-to-many or many-to-many relations, you often want to reuse existing entities. I previously (ZF1 style) did this for example with a (multi)select or radio group. Say the product category relation: My controller would fetch all categories, set those to the form and this will be populated to a multi-checkbox.
How would such a feature work together with the features you describe? It would be nice to be able to select/deselect existing categories and create new ones on the fly. For example, I would set “count” to zero and let users add additional fields besides that checkbox group.
Two typos:
* In the part where you talk about template_placeholder and validation groups, you can’t have template_placeholder or $this->setValidationGroup(array()) tags in you code (apparently). The strong tags are rendered in verbatim.
* The paragraph with allow_add set to false, you talk accidentally about true, what should be “false”.
Hi Jurians !
Thanks for the typo, everything has been fixed.
I am not sure to understand your question, could you provide some code ? If the categories are fixed, you should create your own utility element that would fetch the categories. We have this in DoctrineORMModule for example (https://github.com/doctrine/DoctrineORMModule/blob/master/src/DoctrineORMModule/Form/Element/DoctrineEntity.php).
Anyway, this Collection thing is preliminary thing, it lacks a lot of features, I’ll try to add them as soon as I encounter them in my project.
Sure, this is an example for what I meant: https://gist.github.com/3053248
It simply does not helps with the creation of new categories (I took you example with a product, but with an 1:many relation to a category). You can select an existing category though.
With some javascript we insert a “Create a new category” link. This performs an XmlHttpRequest to the “newAction” from the “CategoryController”. It returns some html with a form tag, we put that into a popup. The visitor enters the information and submits the form. Now that new category is persisted and we update the select list inside the product form with that new category.
The javascript is fairly simple and we based it on jQuery. We put that on GitHub here: https://github.com/Soflomo/jQueryModal
To conclude: it would be very nice to have a “select existing category” like I already have now with an “or add a new category” as you describe in your blog. It makes the javascript part obsolete, which is good for simple objects (like a category with only a name property).
I haven’t looked into the Doctrine entity element, but I am not sure it is a good idea to have this kind of form behaviour depending on your persistency layer. Like my jQueryModal, that doesn’t depend on a persistency layer at all, it just expects some html returned with a form tag.
Haaa I think I undesrtood Jurians ! This use case is because the data template does not contain the options of the select if they are loaded dynamically from data base ?
Sounds great… but it’s kind of funny, nobody seems to care to explain concepts like ‘Hydrator’. ZF 1 doesn’t have anything with such a name and still everybody seems to just assume it’s a concept everybody should know… is that so? I’ve never heard of it…
Good point ! Well, to be simple, hydrators are simple classes that allow you to either extract data from an object to transform it to an array – this is the “extract” function -, or make the contrary, that is to say take simple array of data, and “map” this array to an object – the is the “hydrate” function -.
By default ZF 2 have some built-in hydrators :
- ArraySerializable (this one needs the object to have some methods defined)
- ClassMethods that will iterate through the data, and call the corresponding methods in the object (for example, if the key “username” is meets, the hydrator will call setUsername function of the object).
- ObjectProperty : this one will check if the object has a public property whose name is equals to the key of data.
- Reflection : this one will introspect the object using Reflection in order to know the properties.
And you can of course create your own hydrators by implement the HydratorInterface.
Thanks for taking the time to explain! A common concept with a (for me) new name!
Hi Bakura,
Here is my form view helper.
It works fine but maybe code is not very good.
http://pastebin.com/hFP9YvmY
The problem with those “one-line” render everything is that it really lacks flexibility.
By the way, wrapping inputs around < dt > is really not the best way to do it
…
I agree it’s not the best way, but i’ve got 1 only line to display my forms:
echo $this->simpleForm($myform);
David
Bonjour toi!
That just about as far as my french reaches
my question is how do you handle data dynamically from the database, E.g article categories which is a many to one relation (many articles per category)
Of course if you need data from your database, you’ll need something to retrieve data from your form (a mapper, an entity repository if you are using Doctrine…).
I think the simplest way is to give the Service Manager to your form, and load the data directly in your form/fieldset.
i see, i was hoping for a more sexy solution
Well… The form can’t automagically make Db calls (and, after all, it’s not the goal of a Form to do that !). Therefore, this action has to be deferred to someone else.
Antoine, this is what I also wrote in the first comment of this article: http://www.michaelgallego.fr/blog/?p=190#comment-30
Together with Michael I could perhaps work on this issue. This “relational-form” feature is great, but a couple of things need to be addressed before it is fully functional (like, an arbitrary count, not set a priori). I really hope we can fix those small bits and pieces to get to a great form component
Hi,
is it still possible to create a form over a config file? In ZF1 I used to describe complex forms in one config file, with all its filters, validators, attributes an so on.
In the view it was only an “echo $this->form”. Is there a way to do that in ZF2?
Thank You for an answer.
Jan
There is currently no way to create Forms using config file. However, if you need rapid development, you can use annotations (this comes with a performance cost, though). About the echo $this->form, we made the choice not to provide such a view helper for the moment, as it can be complicated to add specific markup. However, this should be pretty easy to write your own view helper for that.
Ping : Neue Features von ZendForm erklärt - Zend Framework Magazin
Just went through it again today to get to grip with my new project!
Thx again for the great overview!
Ping : Bootstrapping ZF2 Forms
Hi,
Thx for this great post.
Just one question : Is it possible to pass some kind of parameters between parent/child fieldsets.
The idea would be, for example, to change dynamically the label, name and id.
My example is using a datepicker (jQuery over tag) and I’d like to have the same fieldset for date_from and date_to)
Thanks
Yes, you can override any values either using the factories or by creating directly the element. For instance, if you have created a Fieldset with a default label, you can override it when adding it to the form :
$this->add(array(
‘type’ => ‘My\Fieldset’,
‘options’ => array(
‘label’ => ‘An overriden label’
)
));
Working great!
By the way, what would be the cleanest way to translate these labels using the I18n class?
In the view or in the deepest child fieldset (i.e. brand)?
It’s working in the view using the label’s getter/setter ($dateTo->setLabel($this->translate($dateTo->getLabel()));) but I don’t find it very nice.
I’d prefer to do it in the deepest child fieldset (translate only once) but I get errors.
What would you advise?
Thanks
Hi Laurence,
I never used translation module (as my application is in French only), so I don’t really know how it works. Don’t hesitate to go to zftalk.2 IRC channel, and ask for cgmartin or dasprids
.
Why don’t you translate label directly in view ?
in the form you defined the default label
in the view you call the translate using .po/.mo files (generaly in : \module\Application\language )
Excuse me ? I didn’t use any translation in this tutorial as, when I was writing in, the new I18n layer was not ready.
it’s not a pb in the tuto, i’m not speaking about it but answer directly to laurence about his question
using translate not in form but in view
ThanksThanks ThanksThanks
Thanks for a great blogpost!
I’m trying to do this with doctrine 2 entities using DoctrineORMModule hydrator. I first ran into a problem with the owning and inverse side in case of a onetomany relationship. With doctrine entities the owning side is on many-side of a onetomany relationship. Exactly the opposite of your example. I was able to work around this.
I got into real trouble trying to _update_ an entity instead of creating a new one. In case of your example, that would look like this: it’s not a problem to update the Product entity, but updating the Categories results in adding the categories instead of updating them (so instead of 2, you end up with 4 of them). Your case works fine when updating because, the categories collection (array) isn’t managed like in doctrine.
Any thoughts about this? Hope so!
Hi,
You’re right. Although I’m using Doctrine, I didn’t write this example by testing it with Doctrine, and didn’t take into account the owning/inverse side problematic while writing it.
Concerning your problem, I think I ran into similar issue. In fact, you have many solutions. The first one (the easiest one), is to simply clear the Doctrine collections before calling setSomething. For instance :
public function setCategories(array $categories)
{
$this->categories->clear();
// Then add the categories
return $this;
}
Of course, this imply that the values submitted contains all the data, even the old one (otherwise you will lose some).
The other solution would be to write your own hydrator for specific purposes, and call an hypothetic “addCategory” instead of “setCategories”. In fact, I had an idea first to allow the hydrator to call “add*” instead of “set*” but it’s kinda weird as you have to transform plural into singular (and of course this trick won’t work with some languages like French).
Hope I answered you
.
Hi, thanks for the detailed breakdown. We’re having trouble figuring out how to use annotations to make select form elelments. Do you know of any examples implementation use of select element with annotations? Specifically we want to make a to use with the jQuery Chosen plugin. The options will be references to other Doctrine entities via a ManyToMany association.
Hi Jeremiah,
I don’t use annotations so I can’t really help you with these (well, in fact, I did try them once, and it was easy, but I prefer to explicitely create my form).
Are you using DoctrineORMModule ? We have a specific element that is used to extract values from entities, and populate a select with it.
Yes, DoctrineORMModule. That sounds good. Where should I look for that?
https://github.com/doctrine/DoctrineORMModule
Just use composer and it will install all the dependencies. If you need help about this element feel free to come back !
Thanks! Yes, we’re already using composer and have DoctrineORMModule. I thought you could point out the class you mention for extracting entities. Are you referring to \DoctrineORMModule\StdLib\Hydrator\DoctrineEntity above?
No, it’s DoctrineORMModule\Form\Element\DoctrineEntity
Thanks again!
Hello again,
Could you point us to any examples of using DoctrineORMModule\Form\Element\DoctrineEntity to make an option list?
Jeremiah
Sure. This is how you use it :
$this->add(array('type' => 'DoctrineORMModule\Form\Element\DoctrineEntity',
'name' => 'category',
'options' => array(
'label' => 'Your label',
'object_manager' => Registry::get('EntityManager'),
'target_class' => 'User\Entity\Category',
'property' => 'name'
)
));
As you can see this element needs the object_manager (that is to say, an entity manager). For this you can either use the service manager to pull the entity manager from it (so this means that, at some point, you have to give the Service Manager to the form that have to use this element). Or, as I do, you can re-create a Zend\Registry class. Registry has been removed from Zend because it encourages bad practices, but in this specific case, it appears to be the most simplest use.
The target_class is the FQCN to the entity you want to retrieve.
The property is the name of property that will be used as a label in the select.
Thanks, this helps a lot. Rather than using the registry, we’ll compose the ServiceManager (or maybe just the EntityManager) into our AbstractForm so that every one of our forms has a simple getEntityManager() method.
Is it possible to load data (categories) from ini file ?
Create a Category Entity and a Category Repository ?
Create a findAll() method that does a parse_ini file and return data ?
This will be a big help, but it’s too bad that the last argument has to be a literal property. I see that you convert it to a getter, but it will fail of the getter is for a “virtual” property. For example, we have some entities that don’t store their own name, because they are concrete joins to another entity (e.g. can’t be Doctrine joins, because they need a user assigned sort, or whatnot). So in those cases, our EntityInterface requires that we implement getName(), and it retrieves it from another entity. This, however, would be the “property” we need to use in the select list.
Do you see any reason why it should just rely on is_callable? Looking at it, I suppose we could work around the current design by implementing __toString().
Sorry, I’m not sure to understand. You mean that for a given form, you don’t know which property to display ? If this is the case, as you said you have to implement the __toString function.
My question boils down to this. In the foreach loop of loadOptions() there are two tests if $this->property is set.
if (!$metadata->hasField($property)) {
throw new RuntimeException(sprintf(
‘Property “%s” could not be found in entity “%s”‘,
$property,
$targetClass
));
}
$getter = ‘get’ . ucfirst($property);
if (!is_callable(array($entity, $getter))) {
throw new RuntimeException(sprintf(
‘Method “%s::%s” is not callable’,
$this->targetClass,
$getter
));
}
What is the point of the first test? I have an entity with a getName() method but with no $name property of its own. Its getName() method looks like this:
public function getName() {
return $this->getTestSetupParameterTypeValue()->getName();
}
The first test will make this entity throw an exception.
Hi, I just want to help you in the question above, if it is current! I ran into the same problem some weeks ago, but fortunately, I managed to solve it. I had also created a feature-request ticket, but nobody has answered it.
Here it is: https://github.com/doctrine/DoctrineModule/issues/120
If you follow the instructions in it, you can easily solve the problem!
Good luck,
Máté Kocsis
Is there a way to filter out empty items in the collection before validating?
What is your use case ? As filtering occurs prior to validation, you could add a filter that would clear up the collection, but I’m not really sure it would work.
Is there a way to filter out empty items in the collection before validating? -
Bonjour, j’ai deux question :
la 1ere croise certaines discutions précédentes :
J’ai un formulaire avec un select composé de valeurs provenant de la BDD comment le remplir en étant “propre” dans l’architecture ZF2 ?
J’ai essayé différentes facon (sans doctrine) et j’ai réussi en faisant :
dans le controleur getRows de mon select, que j’assigne en 2eme parametre au constructeur de mon form puis dans mon form (article) qui contient un fieldset (articleFieldset) qui contient un select (illustration) je fais :
ArticleForm :
public function __construct($name = ‘article’, $illustrations = array())
{
parent::__construct($name);
$this->setAttribute(‘method’, ‘post’)
->setHydrator(new ClassMethodsHydrator(false))
->setInputFilter(new InputFilter());
$this->add(array(
‘type’ => ‘Article\Form\ArticleFieldset’,
‘options’ => array(
‘use_as_base_fieldset’ => true,
),
));
$this->get(‘articleFieldset’)->get(‘illustration’)->setValueOptions($illustrations);
Est-ce correcte ?
du coup chaque fois que je fais un new ArticleForm, il me faut aussi faire un new illustration, faire l’accès en statique à la BDD depuis le form eu été une solution correcte non ?
——————————————————————————————–
Autre point,
lorsque que je souhaite éditer mon form, j’utilise dans le contrôleur ce qui provient du tuto :
$id = (int) $this->params()->fromRoute(‘id’, 0);
… verification de l’id …
$article = $this->getArticleTable()->getArticle($id);
$illustrations = $this->getArticleIllustrationTable()->fetchPair();
$form = new ArticleForm(‘article’, $illustrations);
$form->bind($article);
…
=> hors depuis que j’ai mis en place le fieldset, mon formulaire n’est plus rempli! Que faut-il rajouter ?
au niveau du bind)
Avant de mettre le formfield tout fonctionnait très bien
Salut,
Quand tu as du code, copie le dans un outil en ligne (genre gist.com), autrement c’est assez illisible
.
Pour ta deuxième question : as-tu bien défini l’hydrateur également dans le fieldset ? Il ne faut pas oublier de faire un $this->setHydrator(new ClassMethodsHydrator(false)) également dans le fieldset (ou alors directement depuis la configuration dans le add, avec la clé “hydrator”).
Pour ta première question, ça me paraît effectivement être la solution la plus “simple”. Faire l’accès à la BDD depuis le form serait également une autre solution, peut-être plus élégante. Par exemple, au lieu de passer $illustrations à ton constructeur, tu pourrais y passer le ServiceManager. Avec ce service manager, tu peux ainsi récupérer ton mapper de table, et récupérer les données. Après il faut trouver le juste milieu entre élégance du code et la simplicité à maintenir
…
Je pense avoir trouvé le problème.
Je veux binder un objet contenant par exemple “titre”
sauf que pour lui l’element dans le fieldset ne s’appelle plus “titre” comme au niveau du form mais “articleFieldset[titre]” avec articleFieldset qui est le nom du fieldset…
au fait, juste message à supprimer :
j’ai voulu mais je n’ai pas pu m’inscrire sur gist, la page d’inscription est une 404
Hello Sir is there a version of this tutorial that has Edit Product form? or can you teach me how to do it? Thank you!
My question is how to do the Edit Product form on this tutorial. Thanks!
Hi Mickael,
Brand has a Doctrine ManyToOne relationship (brand pk into Product entity)
Categories has a Doctrine OneToMany relationship (product pk into Category entity)
It is possible to update entities with OneToMany relationships (for example here update a category entity with product fieldset as base fieldset) ?
It fails for me . No errors but no update lol
Michaël, with an h =).
Well, it’s possible to update, but it’s not always intuitive (I should blog about it, or update the doc of DoctrineModule). (I assume that you are using DoctrineModule hydrator).
When dealing with collections, the hydrator receives data whose type is always array. It first tries to convert the array to an object. In order to do this, you have to add a hidden field whose name is equal to the name of the identifier of the entity (most of the time it will simply be “id”). If the entity in the relationships, just set the id value to the id of the existing entity. With so, the hydrator will realize that the id is set, which means that the object already exist and will first fetch it from database and THEN update it with the data.
On the other hand, if the id is empty, this means that this is a new entity.
Hi Michaël,
I’d greatly appreciate another of your very helpful blogposts about the interactions between DoctrineModule and Zend Form editing of objects.
I’ve been reading up your documentation as I’m trying to combine the Zend\Form\Annotations with Doctrine-persisted objects and the @ComposedObject is giving me a hard time.
However I encountered a difficulty with the solution proposed in your doctrine documentation, particularly for
public function setTags(ArrayCollection $tags)It appears that once an article is persisted and loaded from the entitymanager the $tags property is no longer an ArrayCollection but a PersistentCollection and when I try to copy the tags from one article to another with
$article1->setTags($article2->getTags());this fails with a PHP error, that the parameter type is incorrect.
How can this be fixed most elegantly?
Hi,
As a rule of thumb, try to do it first using the array config. The reason is that I don’t use annotation and I don’t really know what the annotation implementation of DoctrineModule covers.
About your problem I should update the documenation. In your setter, you need to typehint in Collection (the interface that both ArrayCollection and PersistentCollection implement) instead of ArrayCollection. So setTags(Collection $tags)
Thanks a lot for your hint.
There’s also another small typo in the doc:
$this->tags = CollectionUtils::intersectionUnion($this->tags, $tags);should be
$this->tags = CollectionUtils::intersectUnion($this->tags, $tags);Thank you Michaël,
I’m going to add this hidden input and read your doc about doctrine hydrator (https://github.com/doctrine/DoctrineModule/blob/master/docs/hydrator.md).
Because i don’t know anything about hydration.
I’m just using this one (into zf2 doc) for forms and fieldsets:
$this->setHydrator(new ClassMethodsHydrator(false));
Hi Micheal
I have a similar use case as your example, but when i am trying to do an edit,
when the $form->bind() is called, the nested fieldsets don’t get populated, what do you think the problem is?
Hi Michaël,
first off thank you very much for your contributions. I’m making very good use of them.
However I have one question that puzzles me: Why are these elements required by default? And why can’t this be overriden?
Example: I’ve built a search form that also allows to search for a number so I added a Zend\Form\Element\Number to the form. However the isValid checks always fails when I didn’t search for that number.
I’m used to Zend\Form\Factory to create new elements from a specification array, but the setting required => false doesn’t seem to have any effect. How can I override the default setting without having to create a new child class?
Thanks in advance,
Cris
Hi,
Elements are required by default, I don’t know exactly why… this is more an arbitrary choice cgmartin made when he created the first HTML5 element, so we created the next ones following the same pattern.
That’s strange, because normally you could override without any problems. Could you show me some code (element construction + input filter spec definition).
Hi,
thanks for your reply.
First off I’ve mistakenly asumed that I can set the required attribute upon creating the element as with
$form->add(array(
‘type’ => “Zend\Form\Element\Number”,
‘name’ => ‘id’,
‘required’ => false,
));
Then however I have no idea how to unset that required bit again, except for
class MyNumber extends Zend\Form\Element\Number {
public function getInputSpecification() {
$spec = parent::getInputSpecification();
$spec['required'] = false;
return $spec;
}
}
There has to be a better solution. Got a hint?
I got it. You’re doing it wrong. You don’t do it in ZF 1 way. “required” in the “add” function does nothing.
Here’s an example using Fieldset : https://gist.github.com/4234075
Or with a Form : https://gist.github.com/4234089
Basically, with Forms in ZF 2 you have a true separation of concerns. An element in a form know nothing about validation. It’s the task of the Input Filter.
ok, thanks. Got it now.
However speaking of separation of concerns I wonder what that function getInputSpecification is doing in the element then where the element is defining inputfilters?
But your sample code enlightened me and works like a charm now.
Cris : element only contains information about element itself (its name, its HTML attributes…). The input filter contains the validation and filter rules. The getInputSpecification returns an array that is used by a factory to build an InputFilter object.
I am trying to implement a Form for an Entity with a simple select box that takes value from another Entity.
Example : Song entity with select box to select an Artist for it. There is a Many-To-One relationship between Songs and Artists.
Zend really lacks any useful documentation, so I have been trying this for long. However, now it works to add a new Song with an Artist for it
However, on edit a Song, I cannot bind the current Artist for it.
I continuosly get this error (one for each Artist option from the select box) and the current Artist for the Song is not selected:
“Notice: Object of class Doctrine\ORM\Persisters\BasicEntityPersister could not be converted to int ”
This means, the binding fails. I am using the “DoctrineEntity” Hydrator.
I got this “help” : https://github.com/doctrine/DoctrineORMModule/issues/93 but didnt help too much.
May I ask..is it really normal to spend hours and hours to make this simple task while on other frameworks like Symfony, this is a child play?
Please help me on this, if you have any knowledge.
Hi,
It’s funny because I just helped someone that asked me another issue (and it’s the guy of the issue you are linking). I have to admit this use case is not intuitive at all, and I didn’t write documentation for it (it already took me long time to write the existing documentation of DoctrineModule :p).
Please could you provide me some code (in a Gist or anywhere else). I’m pretty sure it comes because you need to add a strategy to the hydrator. This is pretty obvious once you know it
.
Hello,
Thank you for responsing me.
Here is the gist link (with the controller, fieldset, view and a dummy stategy file). The “editAction” is the one with the problem (“Notice: Object of class Doctrine\ORM\Persisters\BasicEntityPersister could not be converted to int”);
https://gist.github.com/95b10bb7adbc0f565de2
I am stuck at the strategy file because I have googled but didnt find much info on how to create a custom strategy file…
However, I think a tutorial on how to make forms with One-To-Many select boxes would be very interesting to read. The tutorial above is also very useful, but there are many times when you dont want to add a new Product with a new Category and a new Brand, but to add a Product and set for it some existing Categories and an existing Brand.
It seems that it is the exact same problem that I helped yesterday. I admit this is counter-intuitive. In fact, currently, the Doctrine Hydrator uses internally a ClassMethods hydrator in order to avoid rewrite some code that both hydrators share. This means that the strategies must be attached to the embedded ClassMethods hydrator, and not to the Doctrine hydrator itself. Basically, your code should be rewrite like this :
$classMethods = new \Zend\Stdlib\Hydrator\ClassMethods(false);
$classMethods->addStrategy('artist', new ArtistHydrationStrategy());
$doctrineHydrator = new DoctrineEntity($this->em, $classMethods);
$this->setHydrator($doctrineHydrator);
Note that the strategies have been attached to the ClassMethods hydrator instead, and that this hydrator is given to the Doctrine hydrator as a second parameter.
As I told manuakasam yesterday, the thing is that I completely rewrote the DoctrineHydrator a few months ago to make it more flexible, as well as to remove the dependency to ClassMethods (so that you can directly add a strategy to the doctrine hydrator).
You can see the work here : https://github.com/doctrine/DoctrineModule/pull/106. The reason for it not being merged is that because ocramius didn’t review it yet so I can’t continue on it. I’ll try to finish it and write tests so that he can get some courage to review it carefully
.
That’s also the reason why I didn’t write extensive docs for the current hydrator, it’s because it should have been refactored a long time ago :’(.
Hey,
Thank you, that worked. However, I dont know where to go next since the Select/Checkbox list doesnt show the current Songs’s Artist as selected (so binding the external Many-To-One entity doesnt work, although the Strategy is called). The “extract” method of the strategy returns the ID of the Songs’s Artist.
Inside the AlbumFieldset constructor I construct the Select box of Artists like this:
”
$artists = $this->em->getRepository(‘Album\Entity\Artist’)
->findAll();
$artistArr = array();
foreach ($artists as $artist) {
$artistArr[$artist->getId()] = $artist->getName();
}
$artistFormElementSelect = new \Zend\Form\Element\Radio;
$artistFormElementSelect->setName(‘artist’);
$artistFormElementSelect->setLabel(‘Please choose an artist for this album’);
$artistFormElementSelect->setValueOptions($artistArr);
$this->add($artistFormElementSelect); ”
Inside the view, I simply get the select box/checkboxes with all options set as “selected”/”checked”.
Anyway, I dont want to take anymore of your time. What shocks me is that here in Zend, this little simple task becomes so incredibly complicated while in Symfony2 (where I am coming from), this is a child play. I dont honestly think this is normal.
So could you please point me out how should I learn this stuff in Zend? Where should I start from since the Zend help is very poor and does only cover the basic stuff.
Thank you
Hi, sorry for that.
I’ve been working on the new hydrator this morning, it’s nearly finished, I’m currently writing tests. We’ll wait until it’s reviewed and I hope it could be merged soon in Doctrine Module (however this will be a BC so you will need to make changes of your code).
I’ll write too a lot of examples for various cases, I hope it’ll help everyone.
Please wait a few days more until the holidays are over. If you want to follow the work on the new hydrator, please see: https://github.com/doctrine/DoctrineModule/pull/106
Hi,
I’ve now completed all the work for the new hydrator, including tests. I’m waiting for review.
In the mean-time, I’ve reworked on the documentation by updating it to the new hydrator and adding some examples. Can you give me your feeling about it ? https://github.com/bakura10/DoctrineModule/blob/a76195401d8f9fbc2c32e4435edf889793a1d6c8/docs/hydrator.md
(NOTE : as the new hydrator has not been merged yet, you cannot run the examples for the moment).
Thank you for the new and very clear explanation! I liked the split into possible scenarios (one-to-one, many-to-many, and one-to-many relationships). I noticed the “many-to-one” is not there, but I just made it work on my side, based on your HydratorStrategy feedback & help.
I also find the optimization part as useful. I will test the actual code once the new Hydrator is merged
The new hydrator just have been merged
. Enjoy.
Hi and thanks for some great code.
Two questions:
1. Can the template be used recursively i.e. consider a form with three types of fieldsets, call them A, B, and C. There’s a 1 to many relationship between A and B. There’s also a 1:m relationship between B and C. Can an A fieldset have an “add more Bs” button and each B have an “add more Cs” button? I suspect it might require two distinctly-named data templates such as data-template-b and data-template-c but maybe I’m wrong.
2. Is there any multi-page form guidance for Zend2 Form available as yet?
Thanks,
Larry
I’ve never tried the use case you’re talking about but I don’t see why it would not work. The only thing you would need to pay attention is using different placeholder for the template (by default, it’s __INDEX__, so your JavaScript code may replace __INDEX__ two times as you have a nested situation).
For the Multi-page, sorry, no we don’t have any examples yet.
I did rename __INDEX__ for both B and C so that should be okay. Consider this situation: the user has added 2 B’s. The first has two C’s and the second three C’s. If they want to add another C to the first B, how can the javascript select just the correct fieldsets to count them and increment the right data-template?
Ha… This is quite a hard case. In fact this is more a JS question :p. I’m not an expert of JS so I cannot really answer you on this, but anyway, the Form can handle those cases without any problem.
Thanks. I think I can figure out a way to do it. But what seems curious is that the B and C element HTML looks to be concatenated in the data-template rather than the C data-template being a child of the B data-template. So I can’t seem to add a C fieldset without getting another B as well. Does that make sense or does it just mean I likely just have an error somewhere in my FieldSet setup? Thanks again and promise I won’t be a pest.
Got it. I needed to replace “> fieldset” with “> fieldset > fieldset” in the javascript.
Hi Michael,
I’m trying to create some group-edit form.
I need it to edit some random number of pictures. So what I would like to get is smth like this here: http://goo.gl/cdm7c
There will be some pictures and some textarea, chceckbox maybe smth else.
My idea is to create some photoFieldset with above input elements, that fieldset i would like to attach to zend/form/element/collection but I don’t know how i can attach there photos and idPhotos.
Do you have any idea how i should do this?
thx
I have another question:
I would like to setup count number of my collection at controller
(...)
$form = new PhotoForm();
$photos = new Photos();
$form->get('photos')->setCount(3);
$form->bind($photos);
(...)
Where element ‘photos’ is
(...)
$this->add(array(
'type' => 'Zend\Form\Element\Collection',
'name' => 'photos',
'options' => array(
'target_element' => array(
'type' => 'Photo\Form\PhotoFieldset',
),
),
));
(...)
And what i get is only one fieldset, but when I add ‘count’ => 5, to definition of ‘photos’ everything is correct, so where is my mystake, what i’m doing wrong?
ok doesn’t matter i had to write this way:
$form->get('photos')->setCount(2)->prepareFieldset();
and then is working as i want
Ha ok, sorry didn’t see your comment. Btw, you should call “$form->prepare()” in the view. iirc it will call all the prepare. You should not call prepareFieldset by yourself.
I have $form->prepare() in the view, and when i remove ‘prepareFieldset’ from controller, it say i have count 3 but it draw only one, so I’m back with the same problem is it cos as u wrote i bind only one object?
And what do you mean by bind an array of photos?
Do I understand correctly that I have to create an array with as many “photos-entity” as I will have photos?
And do you know how to add know to this actual fotos with ?
Hi. I suppose you need to bind an array of photos (you have a collection but you bind only one object).
Salut Michael.
J’ai une question au sujet des formulaires de recherche.
Par exemple, lorsqu’on enregistre un produit, on peut avoir un menu déroulant proposant un choix de coloris par exemple.
Dans le formulaire de recherche de produit on n’a plus de menu déroulant mais un checkbox multiple.
Ma question est la suivante :
- doit-on créer un fieldset spécifique pour la recherche (multicheckbox remplacant selectbox) ou bien garder le fieldset tel qu’il est et rendre le “selectbox” dans la vue avec un helper formMulticheckbox ?
De plus les données de ce genre de formulaire, tout comme des formulaires de contact par exemple ne sont pas forcément destinées a etre enregistrées en base et donc l’association (bind) a une entité quelconque ne semble pas forcément obligatoire.
As tu une réponse a apporter a ce genre de problématique?
PS: Excuse moi pour la question en francais, tu peux y répondre en anglais si tu le souhaite, je saurai comprendre lol
Non, j’ai pas vraiment de réponse à ça (j’avoue que j’ai un peu de mal à visualiser en fait). Les formulaires de recherche c’est un peu compliqué puisque les critères peuvent être multiples, et allant taper sur de nombreuses tables à la fois.
Du coup dans mon application, je récupère mes critères de recherche sans trop passer par ce mécanisme de binding (je récupère juste les valeurs brutes, les données POST quoi), et après je me débrouille dans mon repository pour filtrer.
Ok donc je crée un ou plusieurs fieldsets spécifique a ma recherche, je n’hydrate pas et je ne binde pas.
Je gere les données postées “manuellement”.
Je vais essayer de me débrouiller comme ca.
Merci quand meme pour ta réponse ca va m’éviter de cogiter des semaines de plus lol
Je ne sais pas si c’est la meilleure solution. Parfois j’avoue que plutô^t que d’essayer de me prendre la tête à faire un truc générique, c’est plus simple d’y aller “à l’arrache”.
Une solution si tu utilises Doctrine c’est de coupler ça à l’API Criteria de Doctrine 2.2 (ou 2.3, me souvient plus).
Effectivement j’utilise la derniere version de Doctrine, je vais étudier cette API “Criteria” que je ne connaissais pas.
Merci du tuyau !
Hi Michael,
I don’t want to create the output of the Collection with the function formCollection() but use a loop and write each line with the function formRow(). Is there a possibility to get the code for the data-template as well? I didn’t find any function for that. Do you have a hint or a solution for me? That would be great!
Hi,
You can use the renderTemplate function of the Collection view helper: $this->formCollection->renderTemplate($myCollection);
Awesome tutorial..really. I was looking for a way to merge doctrine with zend but not though annotation but this new thing called “Hydrator” and voila I found this post. Thanks for creating it!
Hi Michael,
I am trying to add a validation group to my form, the form contains a Collection. If I just save the given form (without changing the collection), everything works fine. But if I add new lines of the collection on the fly, I get the following error:
What do you think, am I doing wrong? I would appreciate your help, many thanks in advance.
Hi,
Please could you send me some code ? What parameters did you pass to setValidationGroup ?
Hi Michael, thank you for your answer!
This ist my form:
This is my HTML-code after adding the second line of the Collection on the fly
And while writing this answer, I found the mistake
It should be eg.
and not
in the new line.
Thanks for your help!
Hey man, I’m having some trouble validating a collection element, if the categories had a Text as the type for the target element, how would I add a StringLength validator in the getInputFilterSpecification() method?
If you don’t mind looking there’s a questing on stackoverflow with code samples: http://stackoverflow.com/questions/16395315/how-to-validate-a-form-collection