Symfony Form - Custom Button Type can't read property - symfony

I'm working with Symfony Form in Symfony 5.4 and I need the following:
I have a DTO with some properties. In our application, the default ButtonType has some special handling in the theme-twig (special container with special classes around the button).
Now I need another custom Button-Type to give this new tyoe his own special theme-handling.
I have built the following code for this:
Custom ButtonType-Class:
class FormAddButtonType extends AbstractType
{
public const BLOCK_PREFIX = 'formaddbutton';
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'attr' => [
'class' => 'button blue-button',
],
]);
}
public function getBlockPrefix(): string
{
return self::BLOCK_PREFIX;
}
public function getParent(): string
{
return ButtonType::class;
}
}
Now I add two buttons to my form:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('test_button_1', ButtonType::class, [
'label' => 'Test Button 1',
'attr' => [
'class' => 'button blue-button',
],
'row_attr' => ['class' => 'noborder'],
]);
$builder->add('test_button_2', FormButtonType::class, [
'label' => 'Test Button 2',
'attr' => [
'class' => 'button blue-button',
],
'row_attr' => ['class' => 'noborder'],
]);
}
The first Button will be rendered without any problem. But the second button (my custom button type) will cause the following error:
Can't get a way to read the property "test_button_2" in class "My\Name\Space\Dto\MyDataDto".
Yeah, this class / object doesn't have a property called "test_button_2". But "test_button_1" doesn't exist either and this button works just fine. Manually setting "'mapped' => false" doesn't work either.
If I add my FormAddButtonType inside of my custom Collection Type via POST_SET_DATA-listener, there is no problem. But if I try to use it in the "main form", it won't work.
Can you tell me what I'm doing wrong?

add
"mapped" => false
in the field option, it stands for "this field doesn't exist in the entity".
I think it does work with the default ButtonType as they probably set it on the
$resolver->setDefaults([ /* ... */ ]);
method
doc: https://symfony.com/doc/current/reference/forms/types/form.html#mapped

Related

Sonata admin send model_manager to custom field ModelAutocompleteType

Hello i am using symfony 5.4 with sonata admin. I created custom AppModelAutocopleteType
class AppModelAutocompleteType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'callback' => static function (AdminInterface $admin, array $property, $value) {
AutocompleteForIntegerAndString::execute($admin, $property, $value);
},
'to_string_callback' => function($entity, $property) {
return $entity->getLabel();
},
'minimum_input_length' => 1,
'delay' => 333,
]);
}
public function getParent()
{
return ModelAutocompleteType::class;
}
}
and in sonata admin class (Lead entity) in filter section i add this (networkProvider is relation to Lead entity)
->add('networkProvider', ModelFilter::class, [
'field_type' => AppModelAutocompleteType::class,
'field_options' => [
'property' => ['code', 'name'],
'class' => Lead::class,
'model_manager' => $this->getModelManager(),
],
])
thing is when i enable in frontend my filter it shows up correctly and when i type something i get error. In profiler (ajax request) i can see this problem
Could not find admin for code "".
Based on what i tried it looks like maybe something wrong with model_manager, i don't know if i sent it properly.
Any help how can i create custom ModelAutocompleteType which will work with sonata admin is really appreciate.

Symfony5 custom validator - pass multiple submitted fields (no Entity)

We have a simple form:
namespace App\Form;
...
class SimpleForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field_1', TextType::class, [
'required' => true,
'mapped' => false,
'constraints' => [
new NotBlank()
]
])
->add('field_2', TextType::class, [
'required' => true,
'mapped' => false,
'constraints' => [
new NotBlank()
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new CustomCheck()
]
]);
}
}
If my understanding is right, CustomCheck() can refer to a complex validation over the whole form data (for instance, to validate some combinations of inputs).
My next step is to create the App\Validator\CustomCheck and App\Validator\CustomCheckValidator classes, as per Symfony's manual.
However, I do not know how to pass the submitted field_1 and field_2 data to "new CustomCheck()". Or, how to access all submitted fields from within my custom validator.
I found it is possible if I were using an Entity (Class Constraint Validator, https://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator). But I want to know if it's possible without using an Entity.
Okay, so my findings on the matter is that there is no programmatically way to access and pass the form unmapped fields data as arguments at the level of CustomCheck() within:
$resolver->setDefaults([
'constraints' => [
new CustomCheck()
]
]);
In my case, with no mapped Entity and no mapped fields, I found two ways to have a custom validator that can access any form field data:
A custom in-form callback validator:
// custom callback validator
public function CustomCheck($data, ExecutionContextInterface $context){
// $data doesn't contain the unmapped fields, so I need to extract the form data differently
//var_dump($data['field_1']); // this works only for mapped fields (no Entity/DTO needed for this to work, only mapped fields is sufficient)
$field1_data = $context->getRoot()->get('field_1')->getData(); // this works
$field2_data = $context->getRoot()->get('field_2')->getData();
if(...something_not_good...) {
$context
->buildViolation('Custom error here')
->addViolation();
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new Callback([$this, 'CustomCheck'])
]
]);
}
A custom validator where form data needs to be extracted with $this->context:
// form builder
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new CustomCheck()
]
]);
}
// CustomCheck constraint
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
class CustomCheck extends Constraint
{
public string $message = 'Invalid blah blah.';
}
// CustomCheck validator
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CustomCheckValidator extends ConstraintValidator
{
// $value will always be null, because nothing gets passed in the first argument to this custom validator (no mapped entity, no mapped fields)
/**
* #param mixed $value
*/
public function validate($value, Constraint $constraint)
{
// extract unmapped form fields data manually
$values = [
'field_1' => $this->context->getRoot()->get('field_1')->getData(),
'field_2' => $this->context->getRoot()->get('field_2')->getData()
];
if(...something_not_good...) {
$this->context->buildViolation('Custom error here')->addViolation();
}
}
}

OroPlatform: Override core entity form builder

Context
I'm trying to change the form type of one field on one of the core entity: Business Unit
The default form field is TextField and I want to change it to ChoiceType.
Here is my custom field on Business Unit entity created with migration :
$table->addColumn('periodicite', 'string', [
'oro_options' => [
'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
'entity' => ['label' => 'Périodicité'],
],
]);
Issue
I've seen on the Oro documentation that entity_config.yml could solve my problem. I've tried to put these lines but it doesn't work :
entity_config:
business_unit:
entity:
items:
periodicite:
form:
type: Symfony\Component\Form\Extension\Core\Type\ChoiceType
options:
choices:
Mensuel: Mensuel
Trimestriel: Trimestriel
placeholder: false
required: true
label: "Périodicite"
I have also tried to create a new migration to change the field type on my custom field but it doesn't work
<?php
namespace Baltimore\Bundle\AppBundle\Migrations\Schema\v1_1;
use Doctrine\DBAL\Schema\Schema;
use Oro\Bundle\EntityConfigBundle\Migration\UpdateEntityConfigFieldValueQuery;
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;
use Oro\Bundle\MigrationBundle\Migration\Migration;
use Oro\Bundle\MigrationBundle\Migration\QueryBag;
use Oro\Bundle\OrganizationBundle\Entity\BusinessUnit;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class UpdateBusinessUnitField implements Migration, ExtendExtensionAwareInterface
{
/** #var ExtendExtension */
protected $extendExtension;
/**
* #inheritdoc
*/
public function setExtendExtension(ExtendExtension $extendExtension)
{
$this->extendExtension = $extendExtension;
}
public function up(Schema $schema, QueryBag $queries)
{
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
BusinessUnit::class,
'periodicite',
'form',
'form_type',
ChoiceType::class
)
);
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
BusinessUnit::class,
'periodicite',
'form',
'form_options',
[
'choices' => [
'Mensuel' => 'Mensuel',
'Trimestriel' => 'Trimestriel',
'Annuel' => 'Annuel',
],
]
)
);
}
}
I have found a solution with the changeColumn method in my migration file and it works like a charm.
By the way, these properties works also with the addColumn method.
public function up(Schema $schema, QueryBag $queries)
{
$table = $schema->getTable('oro_business_unit');
$table->changeColumn('periodicite', [
'oro_options' => [
'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
'entity' => ['label' => 'Périodicité'],
'form' => [
'form_type' => ChoiceType::class,
'form_options' => [
'choices' => [
'Mensuel' => 'Mensuel',
'Trimestriel' => 'Trimestriel',
'Semestriel' => 'Semestriel',
'Annuel' => 'Annuel'
]
]
],
],
]);
}
I don't know about the possibility to override entity config metadata using the YAML file. If there is - please share the documentation you used to implement it in the comments.
But for sure, you can manage the same using the schema migration, like in this example:
class UpdateOpportunityRelationFormType implements Migration
{
/**
* {#inheritdoc}
*/
public function up(Schema $schema, QueryBag $queries)
{
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
Quote::class,
'opportunity',
'form',
'form_type',
OpportunitySelectType::class
)
);
$queries->addQuery(
new UpdateEntityConfigFieldValueQuery(
Quote::class,
'opportunity',
'form',
'form_options',
['attr' => ['readonly' => true]]
)
);
}
}

Using ChoiceType with Integers as choice values

Running into a minor problem on Symfony 2.8. I have a couple of db fields, one is an integer and one is decimal. When I build my form, these fields are dropdowns so I'm using ChoiceType instead of IntegerType or NumberType.
The form actually works fine, the difference internally between the two apparently doesn't cause an issue, and I can pick a value and it properly saves to the db.
The issue now is in a Listener. When certain fields are changed, I need to kick off an extra process, so I have an event listener and am using the getEntityChangeSet() command.
What I'm noticing is that it's reporting back these fields as changed, because it's recognizing a difference between 1000 and "1000" which I can see on a Vardump output:
"baths" => array:2 [▼
0 => 1.5
1 => "1.5"
]
This is causing the listener to always trigger my hook even when the value really hasn't changed. If I change the form type to Integer, that's just a text entry and I lose my dropdown. How do you force a dropdown ChoiceType to treat a number as a number?
In my entity, this is properly defined:
/**
* #var float
*
* #ORM\Column(name="baths", type="decimal", precision=10, scale=1, nullable=true)
*/
private $baths;
In my regular form:
->add('baths', BathsType::class)
which pulls in:
class BathsType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'choices' => array_combine(range(1,10,0.5),range(1,10,0.5)),
'label' => 'Bathrooms:',
'required' => false,
'placeholder' => 'N/A',
'choices_as_values' => true,
]);
}
public function getParent()
{
return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
}
}
You should only pass values to your choices option, they will be indexed by numeric keys used as strings for "hidden" html input values which will do the mapping behind the scene.
Then use choice_label to set the labels (visible values) as the choices casted to string :
class BathsType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'choices' => range(1,10,0.5),
'label' => 'Bathrooms:',
'required' => false,
'placeholder' => 'N/A',
'choices_as_values' => true,
'choice_label' => function ($choice) {
return $choice;
},
]);
}
public function getParent()
{
return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
}
}
In symfony 4 choices_as_values does not exist, so the solution would be the same as Heah answer but without that option:
class BathsType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'choices' => range(1,10,0.5),
'label' => 'Bathrooms:',
'required' => false,
'placeholder' => 'N/A',
'choice_label' => function ($choice) {
return $choice;
},
]);
}
public function getParent()
{
return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
}
}

Symfony2 - validating additional form elements

How can I validate additional form fields that do not exist in my entity and are not even related to them?
For example: A user needs to accept the rules so I can add an additional checkbox with mapping set to false but how can I add a constraint which validates this field?
Or even more advanced: A user needs to repeat his e-mail AND password in the form correctly. How can I validate that they're the same?
I want to avoid adding these fields in my entity because it's not related in any way.
I use Symfony 2.3.
One way is to hang constraints directly on the form element. For example:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$notBlank = new NotBlank();
$builder->add('personFirstName', 'text', array('label' => 'AYSO First Name', 'constraints' => $notBlank));
$builder->add('personLastName', 'text', array('label' => 'AYSO Last Name', 'constraints' => $notBlank));
For the repeating stuff, look at the repeated element: http://symfony.com/doc/current/reference/forms/types/repeated.html
Another approach to validation would be to create a wrapper object for you entity. The wrapper object would contain the additional unrelated properties. You could then set your constraints in validation.yml instead of directly on the form.
Finally, you could build a form type just for one property and add the constraints to it:
class EmailFormType extends AbstractType
{
public function getParent() { return 'text'; }
public function getName() { return 'cerad_person_email'; }
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'label' => 'Email',
'attr' => array('size' => 30),
'required' => true,
'constraints' => array(
new Email(array('message' => 'Invalid Email')),
)
));
}
}

Resources