Restricted values for `getBlockPrefix` in symfony 4 - forms

I'm building a simple CRUD using symfony 4.
One of my entities is called Color. Nothing fancy about it. It just has 4 properties: name, description, sortOrder and id (PK).
I also built a form class to be able to add/edit my entity instances.
The form looks like this:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
class Color extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('name', TextType::class)
->add('description', TextareaType::class, ['required' => false])
->add('sort_order', IntegerType::class);
}
}
When I try to render the form I get an error
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class App\Entity\Color could not be converted to string").
I have other forms that look similar and they work.
If I don't let symfony decide the block prefix based on the class name and add the method
public function getBlockPrefix()
{
return 'anything-else-but-color';
}
then the form is rendered.
I don't want to do that because my app is kind of "convention" based and I need a match between the entity class name and the form name.
I know I can change both the entity and the form class names (and I'm probably going to do that) but this bugs me because I don't know what happens and if there are any other reserved words that might screw up my app.
Additional info...
I can partially debug this.
I see that when the form is rendered something like this is generated
<input type="color" id="color" name="color" class="form-control" ....
(notice the type color).
For a different entity and form built in the same way (named group) I get this rendered in the same place as above.
<div id="group">...
So my questions are:
Are there any reserved words that cannot be used for getBlockPrefix in a form?
Can I use the word color as a block prefix without jumping through many hoops?

The documentation touches on this:
When the name of your form class matches any of the built-in field
types, your form might not be rendered correctly. A form type named
App\Form\PasswordType will have the same block name as the built-in
PasswordType and won't be rendered correctly. Override the
getBlockPrefix() method to return a unique block prefix (e.g.
app_password) to avoid collisions.
I would have expected this to trigger if your form type was called ColorType however, for completeness sake as per your comment:
I think it crashes for me because the Type at the end of the class
name is ignored.
StringUtil::fqcnToBlockPrefix('Symfony\Component\Form\Extension\Core\Type\ColorType');
and StringUtil::fqcnToBlockPrefix('Whatever\YouWant\Color'); return
the same thing: color

Related

TYPO3 8.7 / Extbase "No Properties to be mapped"

I have an ordninary model called "Mail":
Namespace ...
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Mail extends AbstractEntity
{
/**
* #var string
*/
protected $name;
protected $company;
.../**
* #var string
*/
protected $company;
...
I want to use it in a Form:
<f:form action="post" object="{mail}">
<f:form.textfield property="name"/>
...
</f:form>
First weird thing is, the html the viewhelper generates is:
<input name="tx_myext_offer[name]">
But in order to work it should be:
<input name="tx_myext_offer[mail][name]">
So I try to write the html of the input field manualy with the name attribute like "tx_myext_offer[mail][name]".
When I now send the form to the controller I get an error:
#1297759968: Exception while property mapping at property path "": It is not allowed to map property "name". You need to use $propertyMappingConfiguration->allowProperties('name') to enable mapping of this property.
When I debug the PropertyMappingConfiguration Object of the request I see that the "propertiesNotToBeMapped" Attribute is empty. There should be the attributes of the Mail model.
Somehow extbase does not map it automatically this time. Seems like I missed something somewhere. How can I tell extbase to map the the properties of the Model automatically?
#ThomasLöffler
in the controller Action which calls the form there is nothing exciting happening:
public function showAction()
{
$this->view->assignMultiple(
[
'mail' => $this->objectManager->get(Mail::class)
]
);
}
First thing first. You have missed the attribute objectName="mail" in your <f:form /> tag.
When you add this attribute the hidden field tx_myext_offer[__trustedProperties] and a bunch of others will changes and then your automatic property mapping should work.

Check if a specific validation_group is valid in Symfony Form?

I'm building a complex symfony form which is a bit long and contains other embedded forms. Thus the form is displayed in the UI in separate tabs to make it more readable and convenient for the user.
Because the form is long and separated in the UI there is a chance you've missed something while populating it or you just inserted something incorrect. That's when the validation would kick in and stop the form from being saved. The validation itself is configured and works flawlessly.
My problem here is I have a gigantic form, separated in tabs, which has an error somewhere and I need to browse each one of the tabs to see exactly what's wrong. I was thinking to make that specific tab, containing fields with errors, in another color so it could stand out and save you the time of wondering what's wrong and where it is located.
From what I could see, I have two options:
Check all fields per tab, manually, using something like:
{% if not form.children.FIELD_NAME.vars.valid %}
which would take forever to complete and I would do only if it's the only possible way.
Try using validation_groups => array('Default', 'my_tab_name') and logically group the fields for each tab.
I'm really hoping to use the second method, but I can't seem to figure out how to check if the validation group i.e. my_tab_1 contains any errors. I'm aware I can do something like this:
$validator = $this->get('validator');
$my_tab_1 = $validator->validate($entity, null, array('my_tab_1'));
$my_tab_2 = $validator->validate($entity, null, array('my_tab_2'));
$my_tab_3 = $validator->validate($entity, null, array('my_tab_3'));
// so on
But the form is already being validated with $form->validate() and using this approach would trigger N more unnecessary validations.
So the question here is how to check if a specific validation group is valid from a twig template? If that's not possible, can one get it from the Controller and pass it as a variable without doing yet another validation?
I don't think I need to post the FormTypes because they're long, nested and might only confuse you. However, this is an oversimplified version of the parent form:
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('address')
->add('representedBy')
->add('category')
->add('phone')
->add('member', new MemberType())
->add('contacts', new ContactType())
->add('notes', new NoteType())
// and a couple more embedded form types.
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\FooBundle\Entity\Company',
'cascade_validation' => true
));
}
/**
* #return string
*/
public function getName()
{
return 'app_company';
}
}
If anybody has a better idea or solution, I would really appreciate it.
First you can use tabs in two different ways:
a) With javascript. All the content of the tabs are loaded once and can be found in the source of the page. All tab-content is hidden except one.
b) With links and PHP. In this case every tab is another webpage with another URL.
(hopefully you understand the difference)
I always use the second method for my advanced forms. Thus for each page i only add a part of all the formfields in the formtype. For each page i use one validation group too. This is already enough to EDIT existing entities.
But a problem is a new Entity. You might want to avoid partly filled entities in your database, thus you need to validate and then store every 'step' in the session and after the user has finished last step (and validation was okay) you might want to store all the form-fields in one time into the database.
This method is used by the craueformflowbundle.
To get a part of your formfields simply use a switch in your formType or create a formType for each step.
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CompanyType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
switch ($options['flow_step']) {
case 1:
$builder
->add('company')
->add('origin')
;
break;
case 2:
$builder
->add('contactPerson', NULL, array('label' => 'Volledige naam'))
->add('email', 'email', array('label' => 'Email'))
->add('telephone', NULL, array('label' => 'Telefoonnummer'))
;
break;
}
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Company',
'flow_step' => 1
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_company';
}
}

Symfony2 forms confirm password without repeated field type

Registration forms usually feature a confirmation field for passwords. We can achieve this in Symfony2 by using a repeated field type.
However, say that you are building you're registration form as follows:
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('user', new UserType());
$builder->add(
'terms',
'checkbox',
array('property_path' => 'termsAccepted')
);
$builder->add('Register', 'submit');
}
...
FWIW, this is actually taken from a Symfony tutorial.
The problem here is that we add a UserType that already contains a password field.
One solution is to use a repeated field type in UserType. However, I am wondering if there is a way to achieve this without modifying UserType?
I thought about adding a field in the Registration class:
/**
* #Assert\EqualTo($this->user.getPassword())
*/
protected $confirmPassword;
but the getPassword() method actually returns the hashed password so I am unsure whether I am on the right track with this..
How would you do it?
Not sure if I understood completely, but I'll give it a shot.
The fact that getPassword() returns hash value only tells you that you need to go in reverse: hash the plain text value and then compare it. But that is a bit weird/invalid to do within the entity, since you do not have access to container nor to any service.
I suggest the following approach:
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// the rest of your registration form, what you already have
$encoderFactory = $options['encoder_factory'];
$builder->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) use ($encoderFactory) {
// Instance of Registration
$data = $event->getData();
$encoder = $encoderFactory->getEncoder($data->getUser());
// Second argument should be the salt or null. Do you intend to use it?
$hash = $encoder->encodePassword($data->getConfirmPassword(), .... );
// Overwrite the palin text value with hash
$data->setConfirmPassword($hash);
});
}
The key points:
You will need to pass an encoder_factory to your registration form.
Later, after the setConfirmPassword() call has completed, validation will run and run positive if passwords match.
So, is this what you were trying to do, at all? :)
P.S. Pardon the potential errors, I am typing this right out of my head into the Notepad++...
I have solved a similar issue by applying the comparison with property plainPassword for those using FOS_user.
Taken from a Symfony tutorial.
<code>
/**
* #Assert\EqualTo(propertyPath="plainPassword")
*/
protected $confirmPassword;
</code>

Symfony Form Component Standalone Form Type Extension

I have a project using some components from Symfony. Namely Twig, Doctrine, and Form.
I would like to modify all form field types to be able to take a new 'suffix' argument when they are created. It seems like this is usually simple in the full Symfony stack and could be done by extending the existing form type. However I'm not sure how to get the form component to load my custom extension when using the Form Component standalone.
Any help would be great, thank you!
Okay, if I understood your question correctly, what you basically want to do, is add new option to your form builder for given type.
index.php - Basically here, we will just create a FormBuilder instance and add one field for testing purpose.
<?php
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
$formFactory = Forms::createFormFactoryBuilder()
->getFormFactory()
->createBuilder()
->add('test', 'text', array('customAttribute' => true))
->getForm()
->createView();
If we open the browser right now, we will get nice and big error, telling us that "customAttribute" is unknown option.
So, let's create custom form type! As you saw I named it TextCustomType since I will extends "text" form type.
The Type class:
<?php
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class TextCustomType extends AbstractTypeExtension {
public function getName() {
return "text";
}
public function getExtendedType() {
return "text";
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setOptional( array('customAttribute') );
$resolver->setDefaults( array('customAttribute' => true) );
}
public function buildView(FormView $view, FormInterface $form, array $options) {
$view->vars['customAttribute'] = $options['customAttribute'];
}
}
Now, we created our custom type, so lets add it to the form factory:
$formFactory = Forms::createFormFactoryBuilder()
->addTypeExtension( new TextCustomType() ) // once the class is loaded simply pass fresh instance to ->addTypeExtension() method.
->getFormFactory()
->createBuilder()
->add('test', 'text', array('customAttribute' => true))
->getForm()
->createView();
Refresh your browser, and you should be good to go! Hope you got the idea.
Updated as per OP's suggestion.
The answer is pretty simple! It was just a matter of looking in the right place. The FormFactoryBuilder is the key:
use Symfony\Form\Component\Form\Forms;
$form = Forms::createFormFactoryBuilder()
->addTypeExtension(new MyExtension())
->getFormFactory()
->create();
This $form variable now knows about my new 'suffix' property.

Symfony 2 : bind request to form object containing objects

I am trying to do something that I am not sure it is possible to do.
Here is my form object :
class DeclarationForm {
private $string1;
private $paramObject;
}
Here is the Param Object :
class Param {
private $id;
private $name;
}
I wanted the form to display a select for the 'ParamObject' field in the creation phase
public function buildForm(FormBuilderInterface $builder, array $options) {
$phase = intval($this->options['phase']);
if($phase === 0) {
$params_qualities = $this->options['params_qualities'] // this is an array of Param Objects;
$qualities = new ObjectChoiceList($params_qualities, 'name', array(), null, 'id');
$builder->add('paramObject', 'choice', array(
'required' => true,
'choice_list' => $qualities
));
}
...
}
it works fine and of course, when I submit the form only the id of the selected option is put in the request.
The problem is when I use $form->handleRequest($request); in the controller, it tries to put a string (the id value) in a Param Object of my DeclarationForm.
Is it even possible to get the label of the selected option in the request to populate the Param Object when handleRequest tries to bind the request to the object ?
How to do that ?
Thank you
It is possible, but not with just one Form. In Symfony, each individual Form has a single backing data object (if at all), and each HTML field corresponds to a single member of that object (apart from special cases like dates and entities). However, one of the Field Types you can use is Form, representing a child Form with its own data object.
In your case, that means doing something like this:
class DecorationFormType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$phase = intval($this->options['phase']);
if($phase === 0) {
//Add child form for param; pass options (with dropdown info) in
//By default fieldname needs to match object member
$builder->add('paramObject', new ParamType(), ['options' => $options] );
}
...
}
}
class ParamType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$params_qualities = $this->options['params_qualities'] // this is an array of Param Objects;
$qualities = new ObjectChoiceList($params_qualities, 'name', array(), null, 'id');
//By default fieldname needs to match object member
$builder->add('id', 'choice', array(
'required' => true,
'choice_list' => $qualities
));
...
}
}
Then in the Controller
public parameterAction() {
//Get param options somehow and stick in $options
$paramForm = $this->createForm(new DecorationFormType(), $decorationForm, $options);
}
So basically you create your main Form as normal, and in that Form one of the Fields it adds is the subform. The name of that Field needs to match a public property or getter on the DeclarationForm class so that it can find the data object (you can override this in the options). You set data_class on each Form appropriately.
Apologies if that's not quite right, I've not tested it and I'm more used to using Collections (which is where you have potentially several of a given subform, depending on the data).
Generally the advice is to use Form Events to manipulate what Fields get added to Forms (your Param one only gets added if phase==0 for example), but I don't think it matters if you're only going to use the Form once.
I wouldn't normally have used the Form Options to pass dropdown info in to the Form, but that might just be me, not sure what best practice is - on the one hand you'd be mixing your own stuff up with a whole load of fixed Symfony keys, but on the other it's a handy place to put it! I've used members of my data objects for that kind of thing in the past.
As for your question about getting the label back from the HTML form - you can't do that as is, because as you've seen the only thing the Request contains is the ID. I can imagine solving this several ways:
Use the labels as the dropdown keys (if they're unique)
Remap IDs to labels in the Param object somehow, give it the list of options so when you go getName() or whatever it can magically give you what you want
Just accept that you get an ID back and look up what it means when you use it!