SonataAdminBundle set NULL $formMapper - symfony

how to set field NULL if nothing was writen in the input ?
I would like to have a checkbox to NULLIFY the field. (even if the field is blank, need NULL in mysql field)

Let me suggest a valid way to accomplish that in symfony2 and sonata-admin
specifically used for a boolean field with nullable = true
(nb: mysql field boolean in symfony2 are actually tinyint:1)
once it is edited, such a field would normally pass to true but if you intercept the request and evaluate the value of a given field you can modify it like this:
in your admin class, assuming the property of the object is property
public function prePersist($object)
{
if (in_array($object->getProperty(), array(NULL, '', '3',)))
$object->setProperty(NULL);
}
public function preUpdate($object)
{
if (in_array($object->getProperty(), array(NULL, '', '3',)))
$object->setProperty(NULL);
}
the number 3 is what I implemented in my case for a select where null is needed
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('property', 'choice', array(
'choices' => array(
'3' => 'Yes and No',
'0' => 'No',
'1' => 'Yes',
),
'empty_value' => false,// unset this and empty would work also
'required' => false,
))
// ->add('property', null, array('required' => false))// checkbox if bool
;
}
Hope that helps!
Cheers,
Vince

Related

SonataAdmin sonata_type_model with lot of rows throw OutOfMemoryException

I have a simple one to many relation Listing -> Booking with many thousands listings.
When i add the following SonataAdmin class :
class BookingAdmin extends Admin
{
...
$formMapper
->add(
'listing',
null,
array(
'disabled' => true,
)
),
...
An OutOfMemoryException is thrown due to the lot of numbers of listings.
I would like to know how to avoid this error by displaying the listing title in the form without using the choice list.
You could use a 'sonata_type_model_autocomplete' form type for these cases (Ref.):
class BookingAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
// the dropdown autocomplete list will show only Booking
// entities that contain specified text in "title" attribute
$formMapper->add('listing', 'sonata_type_model_autocomplete', array(
'property' => 'title'
));
}
}
This one avoids to query all rows to populate the widget.
I found an another solution than Yonel one.
In this solution we only get the current Listing of the persisted Booking entity in the choice widget. It is only useful if the Listing must not be changed.
class BookingAdmin extends Admin
{
...
protected function configureFormFields(FormMapper $formMapper)
{
$listing= $this->getSubject();
$formMapper
->add(
'listing',
'sonata_type_model',
array(
'query' => $this->modelManager
->getEntityManager('Bundle:Listing')
->getRepository('Bundle:Listing')
->find(
$listing->getId()
),
'disabled' => true,
)
);
...

Symfony2 DateTime null accept

So, I want to be able send a null option to my DOB field.
Here is my form builder:
->add('birthDate', DateType::class, array(
'widget' => 'single_text',
'format' => 'yyyy-MM-dd'))
And here is those field in my entity
/**
* #ORM\Column(
* type="date",
* nullable=true
* )
* #JMS\Groups("single")
*
* #var \DateTime
*/
protected $birthDate;
When I`m trying to send a null I got an error msg
Expected argument of type "DateTime", "NULL" given
any ideas?
CRITICAL - Uncaught PHP Exception Symfony\Component\PropertyAccess\Exception\InvalidArgumentException: "Expected argument of type "DateTime", "NULL" given" at /var/www/server.local/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php line 253
$type = $trace[$i]['args'][0];
$type = is_object($type) ? get_class($type) : gettype($type);
throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given', substr($message, $pos, strpos($message, ',', $pos) - $pos), $type));
}
}
In this case, the problem was caused by PHP type hinting.
If you use type hinting (for instance setBirthDate(\DateTime $value)) then PHP forces you that you actually provide a DateTime object. Obviously, null is not such an object. To resolve this problem, it is possible to give $value a default value like this: setBirthDate(\DateTime $value = null).
This is documented behavior and explained in the PHP Documentation (http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration).
Relevant passage:
To specify a type declaration, the type name should be added before the parameter name. The declaration can be made to accept NULL values if the default value of the parameter is set to NULL.
The problem occurs due type-hinted setter as it is mentioned in the comments. There are two solutions:
1. Use 'by_reference' => true on your form:
$builder->add(
'birthDate',
DateType::class,
[
'widget' => 'single_text',
'format' => 'yyyy-MM-dd',
'by_reference' => true,
]
);
2. Let your setter accept null:
public function setBirthDate(\DateTime $value = null)
{
.....
}
Don't pass any values to it. Make the field not required by doing this:
->add(
'birthDate',
DateType::class,
array(
'required' => false,
'widget' => 'single_text',
'format' => 'yyyy-MM-dd'
)
)
I have been using DOB field in my project. Try this.
My ORM file looks like this <field name="dob" type="date" column="dob" nullable="true"/>
->add('dob','birthday',array(
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
'required' => false,
'attr' => array('class' => 'datepicker',
'data-provide' => 'datepicker','data-date-format' => 'dd-mm-yyyy')
))

How to create two related radiobuttons?

The code below creates 2 radiobuttons, however they are not related to each other. One is rendered with a name description_form[friend] and the other one with the name - description_form[guide]. How can they be rendered with the same name? The documentation is not clear about this subject.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('friend', RadioType::class, array(
'label' => 'Friend',
'required' => false
))
->add('guide', RadioType::class, array(
'label' => 'Guide',
'required' => false
));
}
Using a list of RadioType is not quite easy, that's why everybody recommends you to use a ChoiceType which dynamically creates a radio list depending on an array of choice data.
When you create a FormTypeInterface, it has to represent (commonly) one field or one sub form in a global form, so each field name has to be unique to be mapped to the corresponding data.
The buildForm method allows to add some sub fields in you FormType, in your case the field holds two sub fields as radio button and each has a specific name, this is intended by default, but you should always keep in mind the global array data you want to deal with.
Here's your example :
class MyCustomFormType extends \Symfony\Component\Form\AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('friend', RadioType::class, array(
'label' => 'Friend',
'required' => false
))
->add('guide', RadioType::class, array(
'label' => 'Guide',
'required' => false
));
}
public function getBlockPrefix
{
return 'my_custom';
}
// ...
}
So this form type data should look like :
$myCustomFormData = array(
'friend' => $friendData,
'guide' => $guideData,
);
And nested in a global form it would be :
$formData = array(
'my_custom' => $myCustomFormData,
);
But you can name the field as you want :
// In a controller extending \Symfony\Bundle\FrameworkBundle\Controller\Controller
$form = $this->createFormBuilder()
->add('custom_field', MyCustomFormType::class)
->getForm();
// Then
$form->setData(array('custom_field' => $myCustomFormData));
Note that currently, since you map "friend" and "guide" data to RadioType they should hold a boolean value as :
$myCustomFormData = array(
'friend' => true, // checked
'guide' => false, // unchecked
);
But how would you unselect a value then ?
You should had a placeholder to do that, and handle it while submission...
Also, changing the name can be done using the finishView method of your type class, it takes the FormView (built view of your type), the form itself and the options as arguments :
public function finishView(FormView $view, FormInterface $form, array $options)
{
$childName = $view->vars['full_name']; // "my_custom" by default
foreach ($view as $childView) {
$childView->vars['full_name'] = $childName;
}
}
But you would also need to add a DataMapperInterface to get back the submitted value to the form type itself instead.
To do all that, you need to know how the Form Component works and it's not easy.
Easy way
So I agree with the other answers, you should use a ChoiceType to get it out-of-the-box.
I assume your custom form type is about choosing either a "friend" or a "guide", so it could look like this :
$builder
->add('fellow', ChoiceType::class, array(
'choices' => array(
'I have a friend' => 'friend',
'I\'d like a guide' => 'guide',
),
'expanded' => true, // use a radio list instead of a select input
// ...
Have a look at the official docs
Then your data will look like :
$form->add('custom_field', MyCustomFormType::class);
$form->setData(array(
'custom_field' => 'friend',
));
When rendered the "friend" choice will be selected and you will be able to change it to "guide".
An array of choices for the choices options takes labels as keys and choice values as values :
<div id="form_custom_field">
<input type="radio" name="form[custom_field]" value="friend" checked="checked">
<label>I have a friend</label>
<input type="radio" name="form[custom_field]" value="guide">
<label>I'd like a guide</label>
...
This is how I do radio buttons in Symfony 2.7 , hope it helps you.
$yes_no = array('1'=>'Yes','0'=>'No');
->add('myfieldname', 'choice',array(
'choices' => $yes_no,
'label'=>'YourLabelGoeshere',
'required'=>true,
'expanded'=>true,
'multiple'=>false,
'placeholder'=>false
))
Perhaps consider using the ChoiceType field.
See here: Documentation
This allows you to output the options as radio buttons if you choose.
RadioType is used internally by ChoiceType. In most use cases you want to use ChoiceType.

Symfony2, Sonata, FormMapper, add hidden field to be handled in PrePersist/PreUpdate

I actually did some tricks so i could be able to persist a user if its ID is passed by an url parameter. (Custom action from user list).
/admin/se/api/bundle/create?user=7
I actually could not find how to send the user entity returned by a findByOne(array('id' => $user_id)) so i guess i'll need to pass the $user_id through a hidden field and handle its value in a PrePersist
Otherwise passing the id that way
->add('user', 'hidden', array('data' => $user_id))
will return an error :
This value is not valid.
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[user] = 7
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Compound forms expect an array or NULL on submission.
This is my first attempt that is not working :
$container = $this->getConfigurationPool()->getContainer();
$request = $container->get('request');
$user_id = $request->get('user');
if(!empty($user_id)){
$em = $this->getModelManager()->getEntityManager($this->getClass());
$user = $em->getRepository('ApiBundle:User')->findOneBy(array('id' => $user_id));
if($user){
$formMapper
->with('User', array('description' => '<strong>User : </strong>'.$user->getDisplayName()))
->add('user', 'hidden', array('data' => $user_id))
// this of course doesn't work as explained above. How can i have my own hidden input not related to any property
->end();
}
So how would i do that? Any better solution is welcomed.
Well this is the best trick i found. I wish 'sonata_type_model_hidden' has more options. I guess i could do my own custom field to be able to do that. But i'm not sure how and anyway this solution is fast to implement.
$formMapper
->with('Guide', array('description' => '<strong>Guide : </strong>'.$guide->getDisplayName()))
->add('guide', 'sonata_type_model_autocomplete', array(
'property' => array('firstname', 'lastname', 'username', 'email'),
'data_class' => null, // IMPORTANT
'data' => $guide,
'attr' => array('class' => 'sonata-autocomplete-hidden'), // custom class
'label_attr' => array('class' => 'sonata-autocomplete-hidden'), // custom class
)
)
->end();
To hide the field :
.sonata-autocomplete-hidden{
display:none;
}
If you have any better solutions, you're welcome.

Symfony2 - Give a default filter in a list of elements of Sonata Admin

I have a list of elements of type Vehicle and I show these elements with Sonata Admin. I allow to filter these elements by the "status" field, but I want that, when the list is showed, only the active vehicles are showed, and if somebody wants to see the inactive vehicles, uses the filter and select the inactive status. I would like to know if somebody Knows the way to apply filters by default for a list of elements using Sonata Admin.
Here is my code:
public function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('name')
->add('status')
;
}
protected function configureDatagridFilters(DatagridMapper $mapper)
{
$mapper
->add('name')
->add('status')
;
}
Is there any option that can be added to the status field in configureDatagridFilters() to achieve this goal? Other options?
Thanks in advance.
You have to override $datagridValues property as following (for status > 0 if it's an integer) :
/**
* Default Datagrid values
*
* #var array
*/
protected $datagridValues = array (
'status' => array ('type' => 2, 'value' => 0), // type 2 : >
'_page' => 1, // Display the first page (default = 1)
'_sort_order' => 'DESC', // Descendant ordering (default = 'ASC')
'_sort_by' => 'id' // name of the ordered field (default = the model id field, if any)
// the '_sort_by' key can be of the form 'mySubModel.mySubSubModel.myField'.
);
source: Configure the default page and ordering in the list view
You can also use this method
public function getFilterParameters()
{
$this->datagridValues = array_merge(
array(
'status' => array (
'type' => 1,
'value' => 0
),
// exemple with date range
'updatedAt' => array(
'type' => 1,
'value' => array(
'start' => array(
'day' => date('j'),
'month' => date('m'),
'year' => date('Y')
),
'end' => array(
'day' => date('j'),
'month' => date('m'),
'year' => date('Y')
)
),
)
),
$this->datagridValues
);
return parent::getFilterParameters();
}
Using both above suggested approaches will break the filters "reset" behaviour since we are always forcing the filter to filter by a default value. To me, i think the best approach is to use the getFilterParameters function (since we can add logic in there instead of statically add the value) and check if the user clicked the "Reset button"
/**
* {#inheritdoc}
*/
public function getFilterParameters()
{
// build the values array
if ($this->hasRequest()) {
$reset = $this->request->query->get('filters') === 'reset';
if (!$reset) {
$this->datagridValues = array_merge(array(
'status' => array (
'type' => 1,
'value' => 0
),
),
$this->datagridValues
);
}
}
return parent::getFilterParameters();
}
Since sonata-admin 4.0, the function getFilterParameters() is tagged as final and the $datagridValues doesn't exist anymore.
So you need to override the configureDefaultFilterValues() function
protected function configureDefaultFilterValues(array &$filterValues): void
{
$filterValues['foo'] = [
'type' => ContainsOperatorType::TYPE_CONTAINS,
'value' => 'bar',
];
}
More details: https://symfony.com/bundles/SonataAdminBundle/current/reference/action_list.html#default-filters
Another approach is to use createQuery and getPersistentParameters to enforce invisible filter. This approach is best to have fully customizable filters. See my articles here:
http://www.theodo.fr/blog/2016/09/sonata-for-symfony-hide-your-filters/

Resources