Symfony Form Type - add options - forms

In my project I have 3 entities; User, Module, and UserModule.
User has properties such as id, username, email along with a UserModules property that is a collection of UserModules.
Module has properties id, name and description.
Each UserModule object has properties User(reference to corresponding User), Module (reference to corresponding module) and Access (boolean for whether or not access is permitted)
My issue is I do not know how to use Symfony's Form types to show all the modules when creating a User.
I have a number of modules made already (i.e. 'admin', 'ticket', 'help'). In my UserType class my buildForm method looks like:
{
$builder
->add('username')
->add('email')
->add('modules', CollectionType::class, [
'entry_type' => UserModuleType::class,
'entry_options' => [
'label' => false
],
]);
}
my UserModuleType class is
{
$builder
->add('access', ChoiceType::class, [
'choices' => [
'No Access' => false,
'Full Access' => true,
]
]);
}
This shows the UserModules assigned to the User, but when creating a User, I want it to already have UserModules already added for each module.
How can I set this up so that the form will use all possible modules to create default UserModules?

I hope you will find some help here :
https://symfony.com/doc/current/form/form_collections.html
in the example : use tags as your modules

In this case, as you are only using true and false for defining if the user has access to a module, I would use the EntityType with the multiple attribute instead of the CollectionType.
This way you only set which modules have access.
Link to documentation

Related

Laminas / ZF3: Add manually Error to a field

is it possible to add manually an Error Message to a Field after Field Validation and Input Filter ?
I would need it in case the Username and Password is wrong, to mark these Fields / Display the Error Messages.
obviously in ZF/ZF2 it was possible with $form->getElement('password')->addErrorMessage('The Entered Password is not Correct'); - but this doesnt work anymore in ZF3/Laminas
Without knowing how you do your validation (there are a few methods, actually), the cleanest solution is to set the error message while creating the inputFilter (and not to set it to the element after it has been added to the form).
Keep in mind that form configuration (elements, hydrators, filters, validators, messages) should be set on form creation and not in its usage.
Here the form is extended (with its inputfilter), as shown in the documentation:
use Laminas\Form\Form;
use Laminas\Form\Element;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Validator\NotEmpty;
class Password extends Form implements InputFilterProviderInterface {
public function __construct($name = null, $options = []) {
parent::__construct($name, $options);
}
public function init() {
parent::init();
$this->add([
'name' => 'password',
'type' => Element\Password::class,
'options' => [
'label' => 'Password',
]
]);
}
public function getInputFilterSpecification() {
$inputFilter[] = [
'name' => 'password',
'required' => true,
'validators' => [
[
'name' => NotEmpty::class,
'options' => [
// Here you define your custom messages
'messages' => [
// You must specify which validator error messageyou are overriding
NotEmpty::IS_EMPTY => 'Hey, you forgot to type your password!'
]
]
]
]
];
return $inputFilter;
}
}
There are other way to create the form, but the solution is the same.
I also suggest you to take a look at the laminas-validator's documentation, you'll find a lot of useful informations
The Laminas\Form\Element class has a method named setMessages() which expects an array as parameter, for example
$form->get('password')
->setMessages(['The Entered Password is not Correct']);
Note that this will clear all error messages your element may already have. If you want to add your messages as in the old addErrorMessage() method you can do like so:
$myMessages = [
'The Entered Password is not Correct',
'..maybe a 2nd custom message'
];
$allMessages = array_merge(
$form->get('password')->getMessages(),
$myMessages);
$form
->get('password')
->setMessages($allMessages);
You can also use the error-template-name Laminas uses for its error messages as key in your messages-array to override a specific error message:
$myMessages = [
'notSame' => 'The Entered Password is not Correct'
];

magento2 adding customer attributes

Using Magento 2.3.0
Whenever trying to save customer I get errors that newly created attributes are required, even when I set their values.
etc/extend_attributes.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
<extension_attributes for="Magento\Customer\Api\Data\CustomerInterface">
<attribute code="customershipping_enabled" type="string" />
<attribute code="customershipping_price" type="string" />
</extension_attributes>
</config>
Setup/InstallData.php
<?php
namespace <vendor>\<module_name>\Setup;
use Magento\Eav\Model\Entity\Attribute\Source\Boolean;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
class InstallData implements InstallDataInterface {
private $customerSetupFactory;
public function __construct(
\Magento\Customer\Setup\CustomerSetupFactory $customerSetupFactory
) {
$this->customerSetupFactory = $customerSetupFactory;
}
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
$customerSetup =$this->customerSetupFactory->create(['setup'=>$setup]);
$setup->startSetup();
$customerSetup->addAttribute('customer', 'customershipping_enabled', [
'label'=>'Customer Shipping Enabled',
'type' => 'int',
'input' => 'select',
'source' => Boolean::class,
'required'=>true,
'visible'=>true,
'default' => 0,
'position' => 198,
]);
$customerSetup->addAttribute('customer', 'customershipping_price', [
'label'=>'Customer Shipping Price',
'type'=>'decimal',
'input' => 'text',
'required'=>true,
'visible'=>true,
'default' => 0,
'position' => 199,
]);
$enabledAttribute = $customerSetup->getEavConfig()->getAttribute('customer', 'customershipping_enabled');
$enabledAttribute->setData('used_in_forms', ['adminhtml_customer']);
$enabledAttribute->save();
$priceAttribute = $customerSetup->getEavConfig()->getAttribute('customer', 'customershipping_price');
$priceAttribute->setData('used_in_forms', ['adminhtml_customer']);
$priceAttribute->save();
$setup->endSetup();
}
}
I have read many tutorials and documentation on this, and I believe this should be working correctly, am I missing something?
Whenever I try to add new customer or update existing customer, it says that these 2 attributes are required values, save fails.
Also looks identical to this post:
mage2gen.com/snippets/customerattribute
I had similar issue recently, try to add this in 'used_in_forms'.
You might have to remove the attribute and reinstall it:
'used_in_forms' => ['adminhtml_customer', 'customer_account_edit', 'customer_account_create']
edit
Oh I think this should solve the issue, just checked my installData and upgradeData scripts and they all have system => 0. Just add it in.
$customerSetup->addAttribute('customer', 'customershipping_enabled', [
'label'=>'Customer Shipping Enabled',
'type' => 'int',
'input' => 'select',
'source' => Boolean::class,
'required'=>true,
'visible'=>true,
'default' => 0,
'position' => 198,
'system' => 0
]);
It'll be related to this issue:
https://apiworks.net/magento2/magento-2-is-not-saving-the-customer-attribute/
The function getCustomAttributesMetadata is looping through all EAV
attributes and checking if the attribute is marked as “is_system”
inside the “customer_eav_attribute” table, which was the case with my
custom attribute.
Solution:
By default, Magento flagged my custom attribute as is_system = 1, so I
just needed to add ‘system’ => false in my upgrade script and execute
it again (after I removed the original attribute directly from the
database. ).
Root cause of this issue is design behavior of magento 2.
If custom attribute is set as a required one than it must be configured to be shown on storefront and to be shown in all the forms.
If you wants a custom attribute to be required only on some certain forms, then an extension attribute should be used instead with 'required'=>false.
Extension attributes are used to extend functionality of custom attributes.
You just need to replace
'required'=>true,
with
'required'=>false,
For more details please refer the link:
Click here

How to use the ChoiceListInterface in Symfony 2?

I'd like to display a dynamic list of checkboxes in a form.
So far, I built a form embedding a static list of checkboxes, and I created a Tag entity for different values in different languages and populated the database. I'd like to replace the static checkboxes by a dynamic list based on the Tag entity.
The documentation says I should use the ChoiceListInterface. But it is really poorly documented. Would you have an example or a global logic explanation to help me ?
You can extend LazyChoiceList abstract class and implement loadChoiceList() method, create a service of it, inject it to the form and set it as choice_list option.
Finally, I used an entity field type :
->add('tags', 'entity', array(
'class' => 'bndMyBundle:Tag',
'query_builder' => function(EntityRepository $er){
return $er->createQueryBuilder('t')
->orderBy('t.en', 'ASC');
},
'expanded' => true,
'multiple' => true,
'property' => 'en',
))
Then, I just need to replace the 'en' value by the user's current locale to choose the right language.

Symfony2 Doctrine2 Many To Many Form not Saving Entities

I am having some trouble with a many to many relationship. I have Users and Assets. I would like to be able to assign users to an asset on the asset page.
The code below displays a list of users when creating/editing an asset, however changes made to the user checkboxes do not save, while the rest of the data is persisted.
If I add an entry to users_assets through the mysql client, these changes are shown in the asset list.
User
class User extends BaseUser
{
/**
* #ORM\ManyToMany(targetEntity="Asset", inversedBy="users")
*/
private $assets;
}
Asset
class Asset
{
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="assets")
*/
private $users;
}
AssetType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$form = $builder
->add('users', null, array(
'expanded' => true,
'multiple' => true
))
->getForm();
return $form;
}
For some reason I had to switch the doctrine mappings to get this to work:
Asset:
/**
* #ORM\ManyToMany(targetEntity="Adaptive\UserBundle\Entity\User", inversedBy="assets")
* #ORM\JoinTable(name="user_assets")
*/
private $users;
User:
/**
* #ORM\ManyToMany(targetEntity="Splash\SiteBundle\Entity\Asset", mappedBy="users")
*/
private $assets;
Now when I save the asset it saves the users associated. I did not need to define builder->add as an entity or collection. I simply pass it null and it uses the mapping info to fill in the entity info:
AssetType:
->add('users', null, array('expanded' => "true", "multiple" => "true"))
Not exactly sure why I needed to have the inversedBy and JoinTable info on the Asset vs The User but it seems to be working now!
Thanks For The Suggestions!!!
Weird enough I faced the same problem in 2016 and still had hard time finding the solution. I will share it for future googlers:
The problem is that what symfony essentially does when you save the form is this:
$asset->getUsers()->add($user)
And because you're on the inverse side of the relation it won't persist your changes.
What you really need is to make so that it calls this:
$asset->addUser($user)
Where addUser() is defined the following way on the Asset entity:
public function addUser(User $user)
{
//add to the inverse side
$this->users->add($user);
//add on the owning side (only this is persisted)
$user->addAsset($this); //$user->assets->add($asset);
}
So in order to make symfony use that $asset->addUser() method, you should set
'by_reference' => false
on your users field for AssetType form.
More about this setting here http://symfony.com/doc/current/reference/forms/types/form.html#by-reference
Remember you also need to define removeUser() method in the same way (so that it removes entity from the owning relation)
Not exactly sure why I needed to have the inversedBy and
JoinTable info on the Asset vs The User but it
seems to be working now!
The reason why your changes has been ignored is that doctrine persists only changes by the owning side of a relation (like #Florian said).
This is the link to Doctrine's documentation where this behaviour is explained: http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html
At first you should drop backslash prefix in annotations (see notice here).
And you need to use entity field type:
$builder->add('users', 'entity', array(
'class' => 'AdaptiveUserBundle:User',
'expanded' => true,
'multiple' => true,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.username', 'ASC');
},
));
You need to use 'collection' field type in your form.
$builder->add('users', 'collection', array(
'type' => new UserType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
));
You need to create the UserType() form first obviously.
Here is all the info you will need, including code samples:
http://symfony.com/doc/current/cookbook/form/form_collections.html
http://symfony.com/doc/current/reference/forms/types/collection.html

Translate select options in Symfony2 class forms

I'm using a class form in Symfony2 Beta3 as follows:
namespace Partners\FrontendBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ConfigForm extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('no_containers', 'choice', array('choices' => array(1 => 'yes', 0 => 'no')));
...
I want to translate the 'yes' and 'no' options, but I don't know how to use the translator here.
You can use the translation resources as usual. This worked for me:
$builder->add('sex', 'choice', array(
'choices' => array(
1 => 'profile.show.sex.male',
2 => 'profile.show.sex.female',
),
'required' => false,
'label' => 'profile.show.sex.label',
'translation_domain' => 'AcmeUserBundle'
));
And then add your translations to the Resources->translations directory of your Bundle.
Update from #CptSadface:
In symfony 2.7, using the choice_label argument, you can specify the translation domain like this:
'choice_label' => 'typeName',
'choice_translation_domain' => 'messages',
Without specifying the domain, options are not translated.
I searched a while to find an answer, but finally I found out how Symfony translates form content. The easiest way in your case seems to be to just add a translation for "yes" and "no" by adding a YAML or XLIFF translation file to your application (e.g. app/Resources/translations/messages.de.yml) or your bundle. This is described here:
http://symfony.com/doc/current/book/translation.html
The problem - in my opinion - is that you don't seem to be able to use custom translation keys. The guys from FOSUserBundle solve this (or a similar) problem with "Form Themes" (http://symfony.com/doc/2.0/cookbook/form/form_customization.html). Here are two significant lines of code to achieve the usage of the form element id as translation key:
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/views/Registration/register_content.html.twig#L1 / https://github.com/FriendsOfSymfony/FOSUserBundle/blob/50ab4d8fdfd324c1e722cb982e685abdc111be0b/Resources/views/form.html.twig#L4
By adding a Form Theme you're able to modify pretty much everything of the forms in the templates - this seems to be the right way of doing this.
(Sorry, I had to split two of the links b/c I don't have enough reputation to post more than two links. Sad.)
In symfony 2.7, using the choice_label argument, you can specify the translation domain like this:
'choice_label' => 'typeName',
'choice_translation_domain' => 'messages',
Without specifying the domain, options are not translated.
CptSadface's answer was what helped me with translating my entity choices.
$builder
->add(
'authorizationRoles',
null,
[
'label' => 'app.user.fields.authorization_roles',
'multiple' => true,
'choice_label' => 'name', // entity field storing your translation key
'choice_translation_domain' => 'messages',
]
);