I need to remove email field from th registration form.
My solution was to override the registration FormType:
<?php
// src/AppBundle/Form/RegistrationType.php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->remove('email');
}
The filed is removed successfully but, the validation processus is fired "Please enter an email." Any idea about how to disable the validation for email field or even how to make the trick with the right way.
Hack the entity setter:
public function setUsername($username) {
$username = is_null($username) ? '' : $username;
parent::setUsername($username);
$this->setEmail($username);
return $this;
}
Remove the field from the FormType:
public function buildForm(FormBuilder $builder, array $options) {
parent::buildForm($builder, $options);
$builder->remove('email');
}
BUT before hack it, you should have a look to this presentation from jolicode.
If you are currently doing this kind of modifications, it is because FosUserBundle is not adapted to your project. I think you shouldn't use it. (Personnaly, I think this is not a good bundle, read the complete presentation above to make your own opinion)
If you want to replace it, I advice you to use this excellent tutorial to create your own security system. (Time to code/paste/understand it : 2 or 3 hours)
you can give it the value of username for example
User.php
* #ORM\HasLifecycleCallbacks
/**
*
* #ORM\PrePersist()
*/
public function setEmailUser(){
$this->email = $this->username;
}
Related
I'm trying to add custom type to JSONB field as described in documentation:
form:
fields:
- { property: 'attr', type: 'App\Form\Type\AttrType'}
And class realization:
class AttrType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title',TextType::class, array('label' => 'title'));
}
}
And it's work fine.
But I have JSONB column and I don't know how many fields are stored and their types.
QUESTION: How to get Entity in buildForm for acess attr. Needed data exist in $builder and $options I can see in var_dump().
Simplified desired result:
public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach($builder->getData()->getAttr() as $key=>$value){
$builder->add($key,TextType::class, array('data' => $value));
}
}
I myself have been searching for a solution and waiting for a long time and we are not alone. It appears EasyCorp/EasyAdminBundle gave up on this.
While this may not be the answer you're looking for, so far the only solution I've found is to read the request in your custom form type; same as you would in the controller. This necessitates that the data is in the request URI somehow otherwise it won't work.
Example URI: /path/to/action/[ID] or /path/to/action/99 where '99' is the ID of the entity you're looking for.
use App\Repository\SomeRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class AttrType extends AbstractType
{
private ?Request $request = null;
private SomeRepository $repository;
public function __construct(RequestStack $requestStack, SomeRepository $repository)
{
$this->repository = $repository;
if ($req = $requestStack->getCurrentRequest()) {
$this->request = $req;
}
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
if ($this->request && $id = (int) $this->request->get('id')) {
$parentFormData = $this->repository->find($id);
}
}
}
This of course requires a second lookup of the same data so it's far from elegant but it has worked in some situations for me and allowed me to keep my parent form type clean.
Hope it helps.
EDIT:
You can get the parent form data in buildView() as well but that usually never sufficed for me, hence the above solution. For anyone who is unaware of this, here it is:
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class AttrType extends AbstractType
{
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$parentData = $form->getParent()->getData();
}
}
Yes, I know this has been asked before and discouraged, but I have a good use case for that. I am interested in learning the view-oriented supplementary approach.
The use case:
I have an entity, say Venue (id, name, capacity) which I use as collection in EasyAdmin. To render choices, I require this entity to have string representation.
I want the display to say %name% (%capacity% places).
As you've correctly guessed, I require the word "places" translated.
I could want to do it
directly in the entity's __toString() method
in form view by properly rendering __toString() output
I have no idea how to implement either but I agree that the first approach violates the MVC pattern.
Please advise.
Displaying it as %name% (%capacity% places) is just a "possible" representation in your form view so I would shift this very specific representation to your Form Type.
What can belong in the __toString() method of your Venue entity:
class Venue
{
private $name;
... setter & getter method
public function __toString()
{
return $this->getName();
}
}
messages.en.yml:
my_translation: %name% (%capacity% places)
Next your Form Type using choice_label (also worth knowing: choice_translation_domain) :
use Symfony\Component\Translation\TranslatorInterface;
class YourFormType extends AbstractType
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'venue',
EntityType::class,
array(
'choice_label' => function (Venue $venue, $key, $index) {
// Translatable choice labels
return $this->translator->trans('my_translation', array(
'%name%' => $venue->getName(),
'%capacity%' => $venue->getCapacity(),
));
}
)
);
}
}
& also register your form type as a service in services.yml:
your_form_type:
class: Your\Bundle\Namespace\Form\YourFormType
arguments: ["#translator"]
tags:
- { name: form.type }
I implemented a more or less complex solution for that problem, see my answer on this related question: https://stackoverflow.com/a/54038948/2564552
I have an App that requires a complex access control. And the Voters is what I need to make decisions on Controller-level.
However, I need to build form for different users by different way.
Example: There are Admin(ROLE_ADMIN) and User(ROLE_USER). There is a Post that contains fields:
published
moderated
author
body
timestamps
Admin must be able to edit all fields of any Post.
User - only particular fields: published, body. (bay the way, only if this is an author of this post, but this is decided by voters).
Possible solution i found is dynamic form modification. But if we need more complexity, for example posts belongs to Blog, Blog belongs to author. And Post can be edited by direct author and author of the blog.
And Author of the Blog can also edit postedAt field, but it can't be done by direct author of the post.
I need to write some login in PRE_BIND listener.
Maybe there is some kind of common practice for that situation, or someone can show their own examples of.
You can do this creating a form type extension
Imagine a form type where you want to display a field only if ROLE_ADMIN is granted. For that you can simply add a new property to the field ('author' in this example)
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('published', 'text')
->add('moderated', 'text')
->add('author', 'text', [
'is_granted' => 'ROLE_ADMIN',
])
;
}
For this parameter to be interpreted, you must create a form type extension by injecting the SecurityContext Symfony to ensure the rights of the logged on user.
<?php
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\SecurityContextInterface;
class SecurityTypeExtension extends AbstractTypeExtension
{
/**
* The security context
* #var SecurityContextInterface
*/
private $securityContext;
/**
* Object constructor
*/
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$grant = $options['is_granted'];
if (null === $grant || $this->securityContext->isGranted($grant)) {
return;
}
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
if ($form->isRoot()) {
return;
}
$form->getParent()->remove($form->getName());
});
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined(array('is_granted' => null));
}
/**
* {#inheritdoc}
*/
public function getExtendedType()
{
return 'form';
}
}
Finally, you just have to save the extension as a service :
services:
yourbundle.security_type_extension:
class: YourProject\Bundle\ForumBundle\Form\Extension\SecurityTypeExtension
arguments:
- #security.context
tags:
- { name: form.type_extension, alias: form }
Dynamic form modification seems unnecessary. Once the user is logged in the roles should not change.
You could inject the security.authorization_checker service in your form type and use that in the buildForm method to conditionally add fields to your form. Depending on how much the forms differ, this might become messy with too many if-statements. In that case I would suggest writing different form types altogether (possibly extending a base form type for repeated things).
this is my very first question :S
I'm using Symfony2 and i'm having the following trouble
I have two entities related in a ManyToOne relation, I want to make a form for the followin entity
/**
* #ORM\Entity
* #ORM\Table(name="product")
*/
class Product
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=100)
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Acme\ProductsBundle\Entity\ProductCategory", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $productCategory;
}
So i did the following "ProductType"
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('productCategory')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\ProductsBundle\Entity\Product'
));
}
public function getName()
{
return 'acme_hellobundle_producttype';
}
}
And all works great when i render the form, but now i want to change the widget of the "productCategory" to a text widget, because the user need to write the number that is the primary key of the productCategory.
But when i do it, and complete the form, i got the following error.
Warning: spl_object_hash() expects parameter 1 to be object, string
given in
C:\xampp\htdocs\sym2\Symfony\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php
line 1358
Seems like the ORM fails reading a string of the PK, anyone have any little idea of what i must see to fix it. Thanks in advice :)
Your product entity has a relation to product category. So your form expects the category to be an entity and not a string. This is why you get the error expects parameter 1 to be object, string given.
To avoid this you can remove setDefaultOptions method. If you do so the form class will not know anymore that it is associated to a certain entity class. The pitfall of this is, that when you pass the entity to the form class it will not set the fields automatically.
However now you can enter the category id and handle it.
E.g.
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('productCategory');
}
public function getName()
{
return 'acme_hellobundle_producttype';
}
}
Now productCategory will be text widget automatically. However you will need to persist it on your own in the controller. But for this you might ask another question.
Notice, when you create the form, don't pass the product object. Have it like this
$form = $this->createForm(new ProductType(), array());
Im playing around with Symfony2 and Im abit unsure how Symfony2 handles Polymorphic collections in the View component. It seems that i can create an entity with a collection of AbstractChildren, but not sure how to what to do with it inside a Form Type class.
For example, I have the following entity relationship.
/**
* #ORM\Entity
*/
class Order
{
/**
* #ORM\OneToMany(targetEntity="AbstractOrderItem", mappedBy="order", cascade={"all"}, orphanRemoval=true)
*
* #var AbstractOrderItem $items;
*/
$orderItems;
...
}
/**
* Base class for order items to be added to an Order
*
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({
* "ProductOrderItem" = "ProductOrderItem",
* "SubscriptionOrderItem " = "SubscriptionOrderItem "
* })
*/
class AbstractOrderItem
{
$id;
...
}
/**
* #ORM\Entity
*/
class ProductOrderItem extends AbstractOrderItem
{
$productName;
}
/**
* #ORM\Entity
*/
class SubscriptionOrderItem extends AbstractOrderItem
{
$duration;
$startDate;
...
}
Simple enough, but when im create a form for my order class
class OrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('items', 'collection', array('type' => AbstractOrderItemType()));
}
}
I am unsure how to handle this situation where you effectively need a different Form Type for each class of item in the collection?
I recently tackled a similar problem - Symfony itself makes no concessions for polymorphic collections, but it's easy to provide support for them using an EventListener to extend the form.
Below is the content of my EventListener, which uses a similar approach to Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener, the event listener which provides the collection form type's normal functionality:
namespace Acme\VariedCollectionBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class VariedCollectionSubscriber implements EventSubscriberInterface
{
protected $factory;
protected $type;
protected $typeCb;
protected $options;
public function __construct(FormFactoryInterface $factory, $type, $typeCb)
{
$this->factory = $factory;
$this->type = $type;
$this->typeCb = $typeCb;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'fixChildTypes'
);
}
public function fixChildTypes(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
// Go with defaults if we have no data
if($data === null || '' === $data)
{
return;
}
// It's possible to use array access/addChild, but it's not a part of the interface
// Instead, we have to remove all children and re-add them to maintain the order
$toAdd = array();
foreach($form as $name => $child)
{
// Store our own copy of the original form order, in case any are missing from the data
$toAdd[$name] = $child->getConfig()->getOptions();
$form->remove($name);
}
// Now that the form is empty, build it up again
foreach($toAdd as $name => $origOptions)
{
// Decide whether to use the default form type or some extension
$datum = $data[$name] ?: null;
$type = $this->type;
if($datum)
{
$calculatedType = call_user_func($this->typeCb, $datum);
if($calculatedType)
{
$type = $calculatedType;
}
}
// And recreate the form field
$form->add($this->factory->createNamed($name, $type, null, $origOptions));
}
}
}
The downside to using this approach is that for it to recognize the types of your polymorphic entities on submit, you must set the data on your form with the relevant entities before binding it, otherwise the listener has no way of ascertaining what type the data really is. You could potentially work around this working with the FormTypeGuesser system, but that was beyond the scope of my solution.
Similarly, while a collection using this system still supports adding/removing rows, it will assume that all new rows are of the base type - if you try to set them up as extended entities, it'll give you an error about the form containing extra fields.
For simplicity's sake, I use a convenience type to encapsulate this functionality - see below for that and an example:
namespace Acme\VariedCollectionBundle\Form\Type;
use Acme\VariedCollectionBundle\EventListener\VariedCollectionSubscriber;
use JMS\DiExtraBundle\Annotation\FormType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
/**
* #FormType()
*/
class VariedCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Tack on our event subscriber
$builder->addEventSubscriber(new VariedCollectionSubscriber($builder->getFormFactory(), $options['type'], $options['type_cb']));
}
public function getParent()
{
return "collection";
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array('type_cb'));
}
public function getName()
{
return "varied_collection";
}
}
Example:
namespace Acme\VariedCollectionBundle\Form;
use Acme\VariedCollectionBundle\Entity\TestModelWithDate;
use Acme\VariedCollectionBundle\Entity\TestModelWithInt;
use JMS\DiExtraBundle\Annotation\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
/**
* #FormType()
*/
class TestForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$typeCb = function($datum) {
if($datum instanceof TestModelWithInt)
{
return "test_with_int_type";
}
elseif($datum instanceof TestModelWithDate)
{
return "test_with_date_type";
}
else
{
return null; // Returning null tells the varied collection to use the default type - can be omitted, but included here for clarity
}
};
$builder->add('demoCollection', 'varied_collection', array('type_cb' => $typeCb, /* Used for determining the per-item type */
'type' => 'test_type', /* Used as a fallback and for prototypes */
'allow_add' => true,
'allow_remove' => true));
}
public function getName()
{
return "test_form";
}
}
In the example you have give, you would have to create different form class for those ProductOrder and SubscriptionOrder
class ProductOrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
//Form elements related to Product Order here
}
}
and
class SubsciptionOrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
//Form elements related SubscriptionOrder here
}
}
In your OrderType form class you add both these forms, like this
class OrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('product',new ProductOrderType())
$builder->add('subscription',new SubsciptionOrderType())
//Form elements related to order here
}
}
Now this adds the two forms SubsciptionOrderType,ProductOrderType to the main form OrderType . So later in the controller if you initialize this form you will get all the fields of the subscription and product forms with that of the OrderType.
I hope this answers your questions if still not clear please go through the documentation for embedding multiple forms here. http://symfony.com/doc/current/cookbook/form/form_collections.html