i want to use more than one entity to create form in createformbuilder .
forexample i want to have a form with many fields from many entity
and i want to check condition for view fields
userEntity -> email , password
resselerEntity - > (userEntity fields) + managerName , managerFamily
leaderEntity - > (userEntity fields) + credit
and if i want to show resseler fields , must show all fields of userEntity and resselerEntity
if want to show userEntity , must show all fields of userEntity
and etc,
so how can i solve this solution ?
Thanks in advance!
Most common solution is to create single forms for (in your case):
userEntity
ressellerEntity
Then, create a new form that have two fields of userEntityFormType and ressellerEntityFormType.
In that way you can:
Separate your constraints
Use elsewhere single form
Something like that
class UserEntityType extends AbstractType
{
public function BuildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstField')
->add('secondField')
[...]
->lastField;
}
public function getName()
{
return 'UserEntityType ';
}
}
class RessellerEntityType extends AbstractType
{
public function BuildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstField')
->add('secondField')
[...]
->lastField;
}
public function getName()
{
return 'RessellersEntityType ';
}
}
class AggregateEntityType extends AbstractType
{
public function BuildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('userEntityField',UserEntityType,array('multiple'=>true)
->add('ressellersEntityField',RessellersEntityType,array('multiple'=>true));
}
public function getName()
{
return 'AggregateEntityType ';
}
}
I think Don got you most of the way there. Add a construct argument to your UserType
public function __construct($otherEntityType) // Reseller, Leader etc.
Then use otherEntityType to determine which fields are created for UserType.
As far as I understand your problem, you should use type inheritance:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('password')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '\Hamid\User',
));
}
public function getName()
{
return 'hamid_user';
}
}
class ResellerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('managerName')
->add('managerFamily')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '\Hamid\Reseller',
));
}
public function getName()
{
return 'hamid_reseller';
}
public function getParent()
{
return 'hamid_user';
}
}
Then use the right form for each entity. If, for whatever reason, you need a single form that adapts to the class of the entity set to the form, you need to use form events, as explained in the documentation.
Related
STARTING SITUATION
I'm trying to create, with Symfony 6, a FormTypeExtension to extend my custom types.
Class hierarchy is the following:
AbstractType <-- indeed this class is abstract
+--MyBaseType
| +--MySubType1
| +--MySubType2
+--MyOtherType
My FormTypeExtension:
class TranslatableTypeExtension extends AbstractTypeExtension
{
/**
* Should apply to MyBaseType plus MySubType1 and
* MySubType2 by inheritance
*/
public static function getExtendedTypes(): iterable
{
return [
MyBaseType::class,
];
}
/**
* Special option for these custom form types
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'translate_only' => false,
]);
$resolver->setDefined(['translate_only']);
}
}
My custom type classes:
class MyBaseType extends AbstractType
{
}
class MySubType1 extends MyBaseType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('blahblahblah', TextType::class, [
'label' => 'blahblahblah',
])
(etc.)
;
}
}
(same for MySubType2)
In my controller:
class MySub1Controller extends AbstractController
{
#[Route('/MySub1/{id}/translate', name: 'app_mysub1_translate')]
public function translateMuSub1(MySub1Repository $mySub1Repository, EntityManagerInterface $entityManager, Request $request, int $id): Response
{
$mySub1 = $mySub1Repository->find($id);
$form = $this->createForm(MySubType1Type::class, $mySub1, [
'translate_only' => true,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$mySub1 = $form->getData();
$entityManager->persist($mySub1);
$entityManager->flush();
return $this->redirectToRoute('app_mysub1', ['id' => $id]);
}
return $this->renderForm('public/form/mysub1.html.twig', [
'form' => $form,
]);
}
}
This results is:
An error has occurred resolving the options of the form "App\Form\Type\MySub1Type": The option "translate_only" does not exist. Defined options are: (...)
CONCLUSION
This error message means that the FormTypeExtension does not apply to MySubType1: the option translate_only is not reckognized as allowed.
QUESTIONS
I see in the Symfony Form Type Extension doc that Form Extensions can be used for the classic Form Types inheriting FormType. But I see nowhere written that we cannot use it the same way for customer types inheriting AbstractType.
Is it possible or not?
If possible, what am I doing wrong?
Thanks in advance for your help, buddies.
FormType inheritance is built on method getParent(). Probably, you should not extend MyBaseType but return it in getParent()
getParent()
When returning a (fully-qualified) class name here, Symfony will call each method of that type (i.e. buildForm(), buildView(), etc.)
and all its type extensions, before calling the corresponding method
of your custom type.
https://symfony.com/doc/current/form/create_custom_field_type.html#creating-form-types-created-from-scratch
class MyBaseType extends AbstractType
{
}
class MySubType1 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('blahblahblah', TextType::class, [
'label' => 'blahblahblah',
])
(etc.)
;
}
public function getParent()
{
return MyBaseType::class;
}
}
I am building my special choice type.
class MyType extends AbstractType {
public function getParent() { return ChoiceType::class; }
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->addModelTransformer(new CallbackTransformer(); // <-- impossible
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setRequired('currentDataCategory');
//....
}
}
The thing is I dont want to add a FormField in buildForm, because then I would have to know the name of the field. I just want to define my special ChoiceType.
And I need to add a ModelTransformer. If I did add a field, it would be easy:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('unkonwnname', ChoiceType::class);
$builder->get('unkonwnname')->addModelTransformer(new CallbackTransformer(); // <-- should work
}
So I think I am missing a general understanding of custom form types. All examples on the web show custom compound form types, where the name is known in advance.
addModelTransformer is a method of the FormBuilderInterface. In your custom type, it should be called on the $builder, and not on $this (the custom type itself).
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();
}
}
I want to create form with composition pattern like this:
https://symfony.com/doc/current/form/inherit_data_option.html
I use Symfony 3.
and it's working. I have each element like single object and add this.
but finally my form elements names have name like
form[subform][element]
How to make flat structure without subform in name attribute?
use AppBundle\Base\Form\NickType;
use AppBundle\Base\Form\MailType;
use AppBundle\Base\Form\PassType;
use AppBundle\Base\Form\UserType;
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nick', NickType::class)
->add('mail', MailType::class)
->add('password', PassType::class)
->add('repeat_password', PassType::class)
(etc...)
and SINGLE ELEMENT
class NickType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nick', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'inherit_data' => true
));
}
}
You don't need to define a NickType if it only inherits a TextType. You can remove NickType, MailType, etc.
You can just do:
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nick', TextType::class)
;
(etc...)
If you want to reuse a form field, you have to create a Custom Form Field Type:
class NickType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
//...
));
}
public function getParent()
{
return TextType::class;
}
}
You can remove form[] from the element name, but removing this is not really recommended, because when you read the request to populate form data you can identify the form by its form name. (via)
You can set the name of the root form to empty, then your field name
will be just form. Do so via
// the first argument to createNamedBuilder() is the name
$form = $this->get('form.factory')->createNamedBuilder(null, 'form', $defaultData)
->add('from', 'date', array(
'required' => false,
'widget' => 'single_text',
'format' => 'dd.MM.yyyy'
));
(via)
I have a 1 to n relationship so one Brand has many Cars. What I want to do is to create only one web form where all the fields from both of the entities get displayed. To do that I created a form type but I think I'm doing something wrong because I' getting error below when trying to print the form fields in twig. Could anyone tell me where am I doing wrong?
Error:
Method "brand" for object "Symfony\Component\Form\FormView" does not exist in CarBrandBundle:Default:both.html.twig at line 1
Entities:
class BrandEntity
{
protected $name;
protected $origin;
//Followed by getters and setters
/**
* #ORM\ManyToOne(targetEntity="BrandEntity", inversedBy="car")
* #ORM\JoinColumn(name="brand_id", referencedColumnName="id", nullable=false)
* #var object $brand
*/
protected $brand;
}
class CarEntity
{
protected $model;
protected $price;
//Followed by getters and setters
/**
* #ORM\OneToMany(targetEntity = "CarEntity", mappedBy = "brand")
* #var object $car
*/
protected $car;
public function __construct()
{
$this->car = new ArrayCollection();
}
public function addCar(\Car\BrandBundle\Entity\CarEntity $car)
{
$this->car[] = $car;
return $this;
}
public function removeCar(\Car\BrandBundle\Entity\CarEntity $car)
{
$this->car->removeElement($car);
}
}
Form Type:
namespace Car\BrandBundle\Form\Type;
use Car\BrandBundle\Entity\BrandEntity;
use Car\BrandBundle\Entity\CarEntity;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Test\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BothType extends AbstractType
{
public function builder(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('brand', new BrandEntity())
->add('car', new CarEntity())
->add('button', 'submit', array('label' => 'Add'))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
//'data_class' => 'Car\BrandBundle\Entity\CarEntity',
'cascade_validation' => true
));
}
public function getName()
{
return 'both';
}
}
Controller:
namespace Car\BrandBundle\Controller;
use Car\BrandBundle\Form\Type\BothType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BothController extends Controller
{
public function indexAction()
{
$form = $this->getFrom();
return $this->render('CarBrandBundle:Default:both.html.twig',
array('page' => 'Both', 'form' => $form->createView()));
}
private function getFrom()
{
return $this->createForm(new BothType(), null,
array('action' => $this->generateUrl('bothCreate')));
}
}
Twig:
{{ form_row(form.brand.name) }}
{{ form_row(form.brand.origin) }}
{{ form_row(form.car.model) }}
{{ form_row(form.car.price) }}
If you want a "car form" in which you need to choose a brand, then the other answer will be ok.
If what you want is a "brand form" in which you can add /edit/delete several cars, then you need to embed a Collection of Forms.
The cookbook answer is here: http://symfony.com/doc/current/cookbook/form/form_collections.html
To render a brand form containing a collection of car forms (1-n relationship):
The form types will look like this:
The brand type
// src/Acme/TaskBundle/Form/Type/BrandType.php
//...
class BrandType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('cars', 'collection', array('type' => new CarType()));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\BrandBundle\Entity\Brand',
));
}
public function getName()
{
return 'brand';
}
}
The car type:
namespace Acme\CarBundle\Form\Car;
//...
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\CarBundle\Entity\Car',
));
}
public function getName()
{
return 'car';
}
}
Then the controller and the views just like in the cookbook. It's very powerful and easy in the end.
To add entities to a form you must use the entity field type: from the docs
$builder->add('users', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'property' => 'username',
));
In this case, all User objects will be loaded from the database and rendered as either a select tag, a set or radio buttons or a series of checkboxes (this depends on the multiple and expanded values). If the entity object does not have a __toString() method the property option is needed.
Also please post the relationships.