I have a problem using forms in Symfony 2 with mongoDB documents.
I'm trying to have a form that will represent my first document (Post) with a relation oneToMany to Tags (reference)
The relation is declared like this :
/**
* #Assert\Collection
* #MongoDB\ReferenceMany(targetDocument="Acme\ManagerBundle\Document\Tags")
*/
protected $tags;
A tag has an Id and a Name.
I have tried a lot of things to make it work
$form = $this->createFormBuilder($tag)->add('tags', 'choice', array('choices' => $tags, 'multiple' => true, 'expanded' => true, 'empty_value' => true, ))
The form show the choices but once its submited the form is not valid and keep showing thhis error :
"The fields "0", "1", "2" were not expected"
I've also tried this : symfony2 form choice and mongodb
But the use of it is kinda confusing
UPDATE
This is what i get after the post is submited :
object(Doctrine\Common\Collections\ArrayCollection)#795 (1) {
["_elements":"Doctrine\Common\Collections\ArrayCollection":private]=>
array(2) {
[0]=>
object(Acme\ManagerBundle\Document\Tags)#723 (2) {
["id":protected]=>
string(24) "4f7a0eb1ecd111b99c3d2f25"
["name":protected]=>
string(6) "Fruits"
}
[1]=>
object(Acme\ManagerBundle\Document\Tags)#720 (2) {
["id":protected]=>
string(24) "4f7a0ec7ecd111b99c3d2f26"
["name":protected]=>
string(10) "Vegetables"
}
}
}
So now i understand why i have "The fields "0", "1", "2" were not expected" but i dont understand why Symfony doesn't process it.
I've been looking a the possible bundles but nothing
I have no idea how to have a nice form that will hydrate my object and the related objects,
does anyone has a solution for this issue or other idea to solve this?
Thanks a bunch !
Without seeing the data involved I can only make a best guess here.
It feels like your line of code should look something like.
$tags = $post->getTags();
$fixedTags = array();
foreach ($tags as $tag) {
$fixedTags[$tag->getId()] = $tag->getName();
}
$form = $this->createFormBuilder($post)
->add(
'tags',
'choice',
array(
'choices' => $fixedTags,
'multiple' => true,
'expanded' => true,
'empty_value' => true
)
);
Now I think whats happening is you are getting your $tags data in a form like this.
array(0 => (Object)Tag, 1 => (Object)Tag, 2 => (Object)Tag)
Where as what you really want is probably like this.
array('topic1' => 'Topic 1', 'topic2' => 'Topic 2', 'topic3' => 'Topic 3')
If you this isn't the case, reply with some data output and I'm sure we'll be able to help some more.
A choice field won't save by default (although you could manually do it on form submission). You need to look into the document type which is admittedly not documented well but it's essentially the same type as entity here.
I didn't see this was from 3 years ago! Well, it's here in case others find this page I guess.
Related
Situation
We are running Symfony 2.8 and the latest version of Sonata Admin along with Mongo as a data store. Please consider the following object which has been simplified for the sake of this question; it does work.
class Entry
{
/* #ID */
protected $id;
/* #String */
protected $type;
/* #String */
protected $content;
}
With the above, there will be lots of entries and from the Admin itself, we would like to filter by type.
Here's an example of the dataset
Problem
We can't create a set of selectable filters in the dataGrid function which are UNIQUE for type.
Attempts
Note that where needed, the EntryRepository is included as a namespace at the start of the file
NUMBER 1
With the below, we get the type duplicated many times
->add('type', null, array(), 'document', array(
'expanded' => true,
'class' => 'Application:Entry',
'query_builder' => function(EntryRepository $dr) {
return $dr->createQueryBuilder();
}
))
NUMBER 2
With the below, we get a 500 error with only the message "string". I think this is because when using distinct, Mongo prepares a set of arrays instead of unexecuted QueryBuilder object?
->add('type', null, array(), 'document', array(
'expanded' => true,
'class' => 'Application:Entry',
'query_builder' => function(Entryepository $dr) {
return $dr->createQueryBuilder()
->distinct('type');
}
))
NUMBER 3
The attempt below is to use Map reduce to perform the equivalent of an SQL "GROUP BY" however, the same STRING error as above is provided.
->add('type', '', array(), 'document', array(
'expanded' => true,
'class' => 'Application:Entry',
'query_builder' => function(EntryRepository $dr) {
return $dr->createQueryBuilder()
->group(array(), array('type'))
->reduce('function (obj, prev) { prev.type; }');
}
))
CRUDE WORKAROUND...discouraged
The below is a demonstration using the filter (as listed in the Sonata documentation) and it DOES work...for one type at a time.
->add('type', 'doctrine_mongo_callback', array(
'callback' => function($queryBuilder, $alias, $field, $value) {
if (!$value || $value['value'] == false) {
return true;
}
$queryBuilder
->field('type')->equals('fds');
return true;
},
'field_type' => 'checkbox'
))
Taken this approach, I think I'd have to go ahead and query the whole dataset getting the distinct values for type and then loop around each constructing the filter. This would work but be horribly messy.
QUESTION
What is the "best practise" way of performing this without turning the code in to a ruddy mess? Putting the query in a repository will still go and create a similar effect?
Thanks for reading
I'm posting the answer for anyone else who may be facing the same issue. I realised I was approaching the situation from the wrong angle and instead of any of the above, did the following
/* Prepare the options to be used in the filter */
$tagRepo = $this->getConfigurationPool()->getContainer()->get('repository.tag');
$types = $tagRepo->findTypes();
$choices = array();
/* Create a simple choice compatible array */
foreach ($types as $type) { $choices[$type] = $type; }
/* In the actial filter itself */
$datagridMapper
->add('type', 'doctrine_mongo_choice', array(), 'choice',
array('choices' => $choices))
I'm stumbling upon a really simple question and I can't find out what I'm doing wrong :
I have an entity Post which can have a type, in my class declaration :
/**
* #ORM\Column(name="type", type="text", nullable=true)
*/
private $type;
Then I want a form to create Posts :
In my PostType::buildForm() function :
$builder->add('type', 'choice', array(
'empty_data' => null,
'empty_value' => 'No type',
'multiple' => false,
'expanded' => true,
'choices' => \MyBundle\Entity\Application\Post::getTypes(), /* returns array('TYPE1' => 'TYPE_1', 'TYPE2' => 'TYPE_2', ...) */
'required' => true,))
The plan is to have a radio list with :
Type 1
Type 2
Type 3
No type
But it seems like if I choose the option 'No type', the form won't validate, without giving any explicit error. Same thing happens with 'required' => false, with 'placeholder' instead of 'empty_value', ...
Can you spot my mistake ?
What am I doing wrong ?
Thanks :)
My mistake didn't come from the Type or the Entity, it came from the twig widgets overloading.
I was not displaying the value="" if {{ value }} was empty.
Make sure value="" is inside your "None" input radio tag, and in your other input radio tags aswell, of course !
Hope it helps someone ;)
You can't have an empty value on a choice field type and have it required.
You are saying it must exist but may be empty, a contradiction.
It will work if you drop the 'required' => true
I'm trying to implement a ManyToMany relation in a form between 2 entities (say, Product and Category to make simpe) and use the method described in the docs with prototype and javascript (http://symfony.com/doc/current/cookbook/form/form_collections.html).
Here is the line from ProductType that create the category collection :
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category',
'required' => false),
'allow_add' => true,
'allow_delete' => true,
));
When I had a new item, a new select appear set to the empty value 'Select a category'. The problem is that if I don't change the empty value, it is sent to the server and after a $form->bind() my Product object get some null values in the $category ArrayCollection.
I first though to test the value in the setter in Product entity, and add 'by_reference'=>false in the ProductType, but in this case I get an exception stating that null is not an instance of Category.
How can I make sure the empty values are ignored ?
Citing the documentation on 'delete_empty':
If you want to explicitly remove entirely empty collection entries from your form you have to set this option to true
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category'),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true
));
Since you use embedded forms, you could run in some issues such as Warning: spl_object_hash() expects parameter 1 to be object, null given when passing empty collections.
Removing required=>false as explained on this answer did not work for me.
A similar issue is referenced here on github and resolved by the PR 9773
I finally found a way to handle that with Event listeners.
This discussion give the meaning of all FormEvents.
In this case, PRE_BIND (replaced by PRE_SUBMIT in 2.1 and later) will allow us to modify the data before it is bind to the Entity.
Looking at the implementation of Form in Symfony source is the only source of information I found on how to use those Events. For PRE_BIND, we see that the form data will be updated by the event data, so we can alter it with $event->setData(...). The following snippet will loop through the data, unset all null values and set it back.
$builder->addEventListener(FormEvents::PRE_BIND, function(FormEvent $event){
$data = $event->getData();
if(isset($data["categories"])) {
foreach($data as $key=>$value) {
if(!isset($value) || $value == "")
unset($data[$key]);
}
$event->setData($data);
});
Hope this can help others !
Since Symfony 3.4 you can pass a closure to delete_empty:
$builder
->add('authors', CollectionType::class, [
'delete_empty' => function ($author) {
return empty($author['firstName']);
},
]);
https://github.com/symfony/symfony/commit/c0d99d13c023f9a5c87338581c2a4a674b78f85f
I am building a form class in Symfony2. In my class, I have a choice field. I built a function to return my choice array:
public function getCardTypes() {
return array('visa' => 'Visa', 'mc' => 'MasterCard', 'amex' => 'American Express');
}
Later, I add a choice field to my form with this array:
$builder->add('PaymentCCType', 'choice', array('choices' => $this->getCardTypes()));
And then in getDefaultOptions function I have a choice constraint for this field:
'PaymentCCType' => new Choice(array('choices' => $this->getCardTypes())),
I seem to be having a problem with this validator. When I submit this form, I get the following error underneath my select box: "The value you selected is not a valid choice". Of course, I am using one of the choices in my array.
What am I doing wrong?
/* edit */
I have noticed that of the 4 fields I have like this, I only get the error on 3 of them. the one where the choice is month (simple 1-12), validation works.
/* edit 2 */
the issue appears to occur when the array key does not match the value. i switched my array to array('Visa' => 'Visa', 'MasterCard' => 'MasterCard', 'American Express' => 'American Express') and now it works.
Is there any way around this? I feel like I can't be the only one with this issue. it occurs even when you have a regular (non-associative) array like array('Visa', 'MasterCard', 'American Express')
IMHO you should do it in different way, create class with ChoiceListInterface with methods:
public function getChoices()
{
return self::$choices;
}
public static function getTypeChoicesKeys()
{
return array_keys(self::$choices);
}
in form class:
$builder->add('type', 'choice',
array(
'expanded' => true,
'multiple' => false,
'choice_list' => new TypeChoices(),
'required' => true,
)
)
in validation.yml
type:
- NotNull: ~
- Choice: { callback: [TypeChoices, getTypeChoicesKeys] }
edit
In response to my issue, the Symfony team pointed out the choice validator accepts an array of possible values (not possible choices like the choice field). the easiest way to do this is to use the array_keys function:
'PaymentCCType' => new Choice(array('choices' => array_keys($this->getCardTypes()))),
I usualy use Zend_Validate_Db_RecordExists to update or insert a record. This works fine with one field to check against. How to do it if you have two fields to check?
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector,day_of_week'
)
);
if ($validator->isValid($fields_values['id_sector'],$fields_values['day_of_week'])){
//true
}
I tried it with an array and comma separated list, nothing works... Any help is welcome.
Regards
Andrea
To do this you would have to extend the Zend_Validate_Db_RecordExists class.
It doesn't currently know how to check for the existence of more than one field.
You could just use two different validator instances to check the two fields separately. This is the only work around that I can see right now besides extending it.
If you choose to extend it then you'll have to find some way of passing in all the fields to the constructor ( array seems like a good choice ), and then you'll have to dig into the method that creates the sql query. In this method you'll have to loop over the array of fields that were passed in to the constructor.
You should look into using the exclude parameter. Something like this should do what you want:
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector',
'exclude' => array(
'field' => 'day_of_week',
'value' => $fields_values['day_of_week']
)
);
The exclude field will effectively add to the automatically generated WHERE part to create something equivalent to this:
WHERE `id_sector` = $fields_values['id_sector'] AND `day_of_week` = $fields_values['day_of_week']
Its kind of a hack in that we're using it for the opposite of what it was intended, but its working for me similar to this (I'm using it with Db_NoRecordExists).
Source: Zend_Validate_Db_NoRecordExists example
Sorry for the late reply.
The best option that worked for me is this:
// create an instance of the Zend_Validate_Db_RecordExists class
// pass in the database table name and the first field (as usual)...
$validator = new Zend_Validate_Db_RecordExists(array(
'table' => 'tablename',
'field' => 'first_field'
));
// reset the where clause used by Zend_Validate_Db_RecordExists
$validator->getSelect()->reset('where');
// set again the first field and the second field.
// :value is a named parameter that will be substituted
// by the value passed to the isValid method
$validator->getSelect()->where('first_field = ?', $first_field);
$validator->getSelect()->where('second_field = :value', $second_field);
// add your new record exist based on 2 fields validator to your element.
$element = new Zend_Form_Element_Text('element');
$element->addValidator($validator);
// add the validated element to the form.
$form->addElement($element);
I hope that will help someone :)
Although, I would strongly recommend a neater solution which would be to extend the Zend_Validate_Db_RecordExists class with the above code.
Enjoy!!
Rosario
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
'validators' => array('EmailAddress', $obj= new Zend_Validate_Db_NoRecordExists(array('adapter'=>$dbAdapter,
'field'=>'email',
'table'=>'user',
'exclude'=>array('field'=>'email','value'=>$this->_options['email'], 'field'=>'is_deleted', 'value'=>'1')
))),
For those using Zend 2, If you want to check if user with given id and email exists in table users, It is possible this way.
First, you create the select object that will be use as parameter for the Zend\Validator\Db\RecordExists object
$select = new Zend\Db\Sql\Select();
$select->from('users')
->where->equalTo('id', $user_id)
->where->equalTo('email', $email);
Now, create RecordExists object and check the existence this way
$validator = new Zend\Validator\Db\RecordExists($select);
$validator->setAdapter($dbAdapter);
if ($validator->isValid($username)) {
echo 'This user is valid';
} else {
//get and display errors
$messages = $validator->getMessages();
foreach ($messages as $message) {
echo "$message\n";
}
}
This sample is from ZF2 official doc
You can use the 'exclude' in this parameter pass the second clause that you want to filter through.
$clause = 'table.field2 = value';
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => 'table',
'field' => 'field1',
'exclude' => $clause
)
);
if ($validator->isValid('value') {
true;
}
I am using zend framework v.3 and validation via InputFilter(), it uses same validation rules as zend framework 2.
In my case I need to check, if location exists in db (by 'id' field) and has needed company's id ('company_id' field).
I implemented it in next way:
$clause = new Operator('company_id', Operator::OP_EQ, $companyId);
$inputFilter->add([
'name' => 'location_id',
'required' => false,
'filters' => [
['name' => 'StringTrim'],
['name' => 'ToInt'],
],
'validators' => [
[
'name' => 'Int',
],
[
'name' => 'dbRecordExists',
'options' => [
'adapter' => $dbAdapterCore,
'table' => 'locations',
'field' => 'id',
'exclude' => $clause,
'messages' => [
'noRecordFound' => "Location does not exist.",
],
]
],
],
]);
In this case validation will pass, only if 'locations' table has item with columns id == $value and company_id == $companyId, like next:
select * from location where id = ? AND company_id = ?