Overriding Fosuser profile form's label - php

I write a formtype extends the forsuser ProfileFormType, but everytime I rendered it in template, there should be always a label "User" appear in top of form. I figured out it comes form original fosuser ProfileFormType:
namespace FOS\UserBundle\Form\Type;
use .....
class ProfileFormType extends AbstractType
{
private $class;
/**
* #param string $class The User class name
*/
public function __construct($class)
{
$this->class = $class;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$child = $builder->create('user', 'form', array('data_class' => $this->class));
$this->buildUserForm($child, $options);
.......
if I add the attribute for this form field like:
$child = $builder->create('user', 'form', array('label'=>'some info','data_class' => $this->class));
it could be worked, but its bad for modify the original files, how can I modify it in my custom formtype or in template when rendering?

You need to extend the FOSUserBundle by creating your own UserBundle and within the Bundle class add:
public function getParent()
{
return 'FOSUserBundle';
}
Then create Form/Type directory and create a new form type called ProfileFormType and within that new form type place:
namespace Acme\UserBundle\Form\Type;
use Symfony\Component\Form\FormBuilderInterface;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
class ProfileFormType extends BaseType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
// add your custom field
$builder->add('name');
}
public function getName()
{
return 'acme_user_profile';
}
After that you need to add the new form to the service like:
<services>
<service id="acme_user.profile.form.type" class="Acme\UserBundle\Form\Type\ProfileFormType">
<tag name="form.type" alias="acme_user_profile" />
<argument>%fos_user.model.user.class%</argument>
</service>
</services>
Finally add this to your config.yml:
fos_user:
# ...
profile:
form:
type: acme_user_profile
Remember to replace acme with your current structure information.
More information can be found at: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/overriding_forms.md

If you want to override only labels, just add a FOSUserBundle.xx.yml file in app/Resources/FOSUserBundle/translations directory. No need to override the whole bundle, even if it could the proper for further overrides...
If you already have overrode FOSUserBundle, add the file in YourCustomFOSUserBundle/Resources/translations !
Copy it from the FOSUserBundle/Resources/translations/FOSUserBundle.xx.yml you want to override, and modify translations. You should keep only modified translations in this file.

Related

How could my custom Symfony ChoiceType allow extra items?

I'm working on a TagField for EasyAdmin 4 (and Symfony 6) that will rely on a TagType. This TagType will have the native ChoiceType as a parent.
This field will be rendered as a multiple select, with these attributes to allow adding tags on the fly:
[ 'data-ea-widget' => 'ea-autocomplete', 'data-ea-autocomplete-allow-item-create' => 'true' ]
To do so, I created a TagListener. Its main goal is to prefill the options with the already existing tags (on other entities) to support tag suggestion. After reading the docs and many articles, I chose to listen to the FormEvents::PRE_SET_DATA event.
Unfortunately there does not seem to be an easy way to "override" the default options, and we're left with having to override the entire field.
Here's what the TagListener looks like:
<?php
// src/Form/EventListener/TagListener.php
namespace eduMedia\TagBundle\Form\EventListener;
use eduMedia\TagBundle\Entity\TaggableInterface;
use eduMedia\TagBundle\Form\Type\TagType;
use eduMedia\TagBundle\Service\TagService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class TagListener implements EventSubscriberInterface
{
public function __construct(private TagService $tagService)
{
}
/**
* #inheritDoc
*/
public static function getSubscribedEvents(): array
{
return [
FormEvents::PRE_SET_DATA => 'onPreSetData',
];
}
public function onPreSetData(FormEvent $event): void
{
$form = $event->getForm();
$parentForm = $event->getForm()->getParent();
/** #var TaggableInterface $taggable */
$taggable = $parentForm->getData();
// We retrieve the existing options to override some of them
$options = $form->getConfig()->getOptions();
// if ($options['pre_set_data_called']) {
// return;
// }
// We prefill options with the existing tags for this resource type
$allTagNames = $this->tagService->getTypeTagNames($taggable->getTaggableType());
// They are our new choices
$options['choices'] = array_combine($allTagNames, $allTagNames);
// We also need to select the entity's tags
$options['data'] = $this->tagService->loadTagging($taggable)->getTagNames($taggable);
// We override the form field
// $options['pre_set_data_called'] = true;
$parentForm->add($form->getName(), TagType::class, $options);
}
}
Doing so seems to create an infinite loop, where onPreSetData is called when calling $parentForm->add(). Is that normal? Is PRE_SET_DATA dispatched again when adding a field in a listener? Is there a way to prevent this from happening?
I tried adding a pre_set_data_called form option, setting it to true when calling $parentForm->add() and exiting the listener when it is indeed true. It kind of works, but then I get this error:
An exception has been thrown during the rendering of a template ("Field "tags" has already been rendered, save the result of previous render call to a variable and output that instead.").
How can I manage to allow extra items in my custom field type?
For reference, here is my TagType class:
<?php
namespace eduMedia\TagBundle\Form\Type;
use eduMedia\TagBundle\Form\EventListener\TagListener;
use eduMedia\TagBundle\Service\TagService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TagType extends AbstractType
{
public function __construct(private TagService $tagService)
{
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addEventSubscriber(new TagListener($this->tagService));
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'mapped' => false,
'multiple' => true,
// 'pre_set_data_called' => false,
]);
}
public function getParent()
{
return ChoiceType::class;
}
}
And my TagField class:
<?php
namespace eduMedia\TagBundle\Admin\Field;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use eduMedia\TagBundle\Form\Type\TagType;
class TagField implements FieldInterface
{
use FieldTrait;
public static function new(string $propertyName, ?string $label = null)
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setFormType(TagType::class)
->setFormTypeOption('attr', [ 'data-ea-widget' => 'ea-autocomplete', 'data-ea-autocomplete-allow-item-create' => 'true' ])
->setTemplatePath('#eduMediaTag/fields/tag.html.twig')
;
}
}
I ended up not using the ChoiceType as the parent (<select> element), but rather the TextType (<input type=text> element), and splitting/exploding a simple string.
The actual bundle is live on GitHub and even though it might not be perfect (yet 😉), the implementation is way simpler and the end-user behaviour is exactly what I expected.

The form's view data is expected to be an instance of another class error creating form instance of another entity

I get the following error when trying to create a form from another entity to pass through to my view.
I have two entities in this context CourseGuide and CourseGuideRow and I would like to pass through a form view of CourseGuideRowType to my view - how can I do this?
The form's view data is expected to be an instance of class
CRMPicco\CourseBundle\Entity\CourseGuide, but is an instance of class
CRMPicco\CourseBundle\Entity\CourseGuideRow. You can avoid this error
by setting the "data_class" option to null or by adding a view
transformer that transforms an instance of class
CRMPicco\CourseBundle\Entity\CourseGuideRow to an instance of
CRMPicco\CourseBundle\Entity\CourseGuide.
This is my controller:
// CourseGuideController.php
public function viewAction(Request $request)
{
if (!$courseId = $request->get('id')) {
throw new NotFoundHttpException('No Course ID provided in ' . __METHOD__);
}
$resource = $this->get('crmpicco.repository.course_guide_row')->createNew();
$form = $this->getForm($resource);
// ...
}
My Symfony FormBuilder class:
// CourseGuideRowType.php
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\FormBuilderInterface;
class CourseGuideRowType extends AbstractResourceType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('channel', 'crmpicco_channel_choice', array('data_class' => null))
->add('name', 'text')
->add('courses', 'text')
;
}
/**
* #return string name
*/
public function getName()
{
return 'crmpicco_course_guide_row';
}
}
I have tried the data_class => null suggestion mentioned elsewhere, but this has no effect.
If I pass through the data_class like this:
$form = $this->getForm($resource, array('data_class' => 'CRMPicco\CourseBundle\Entity\CourseGuideRow'));
I then get this:
Neither the property "translations" nor one of the methods
"getTranslations()", "translations()", "isTranslations()",
"hasTranslations()", "__get()" exist and have public access in class
"CRMPicco\CourseBundle\Entity\CourseGuideRow".
Why is this? There are translations attached to the CourseGuide entity but not the CourseGuideRow.
try to add this function in your FormType:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'YourBundle\Entity\YourEntity',
));
}
And don't forget the specific use:
use Symfony\Component\OptionsResolver\OptionsResolver;
EDIT
In native Symfony (with the Form component):
public function showAction()
{
/.../
$entity = new YourEntity();
$form = $this->createForm('name_of_your_form_type', $entity);
# And the response:
return $this->render('your_template.html.twig', ['form' => $form->createView()]);
}

Access session from formType in Symfony2

I'm trying to get session data into my forms but i don't know how to do.
I could pass it to the constructor of my FormType but the FormType that actually use the session is nested 3 levels deeper in the main form.
So i think that it is dirty to pass the session object in each constructor of each form type like this :
->add('name', new NestedFormType($this->session))
I also thought about using formsType as service. So i would have a parent for each of my formsType that should be injected with session.
But how can i do that without defininf all my forms as services ?
Futhermore, i can't access to the DIC inside of my FormTypes. So, it's ok for the creation of the first formType object (which is created in the controller which can access to DIC) but the nested FormTypes cannot be instianciated from their parent.
Is there a clean solution?
You need to define this parent form as a service and pass the session as the argument.
look at this question: Create a form as a service in Symfony2
You don't need to define services for your higher level form types as long as you refer to the inner, injected form type by its alias:
NestedFormType service definition:
nested.form.type:
class: Type\NestedFormType
tags:
- { name: form.type, alias: nested_form }
arguments: [ #security.context ]
NestedFormType:
class NestedFormType extends AbstractType
{
private $security_context;
public function __construct($security_context)
{
$this->security_context = $security_context;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// do something with $this->security_context
}
public function getName()
{
return 'nested_form';
}
}
ParentFormType:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name', 'nested_form');
// 'nested_form' matches service definition and NestedFormType::getName()
}

Symfony2 Form folder

I need to create a form class,I'm following the symfony book in http://symfony.com/doc/current/book/forms.html
I am trying to create a form in src/Acme/TaskBundle/Form/Type/TaskType.php, but when I look at the folder structure on my project there is no "Form" folder.
I try to create the Form folder manually, in src/Acme/TaskBundle/, I get an error in the the Form Folder and in the TaskType.php files ( namespace Acme\TaskBundle\Form\Type Expected:Identifier).
Is there a way to create the Form folder in an automatic way? Or how can I create in manually?
The Form folder is just a convention — you can put your forms wherever you want. The convention extends to:
Form\Type for form types,
Form\Model for form models,
Form\Handler for form handlers,
etc.
Have you tried the command, might be what you're looking for
php app/console generate:doctrine:form
I just created it manually.
form: (my case: LL\NameBundle\Form)
FormName.php
<?php
namespace LL\NameBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class FormName extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('add')
->add('your')
->add('fields')
;
}
public function getName()
{
return 'form-name';
}
}
Controller:
use LL\NameBundle\Form\FormName;
public function()
{
$form = new FormName();
$form = $this->form( $form, $entity );
return array(
'form' => $form->createView()
);
}
This Works for me, if it doesn't work please post some code.

How to get instance of entity repository in the Form (Type) class in Symfony 2?

Let's say I have ordinary *Type class:
class LocationType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add(...)
...
}
}
and one of the fields is a choice type. The values that need to be used as choice items are supposed to be retrieved from the database (from some particular entity repository).
So the question is: how to get the repository in the LocationType class? Is passing it through the constructor the only way to get it?
UPD:
I know about entity type but unfortunately I cannot use it, because my property is not and cannot be defined as one-to-one relation due to very complex relation conditions that Doctrine doesn't support (yet?). See How to specify several join conditions for 1:1 relationship in Doctrine 2 for additional details
You can specify an entity field type as an option like so:
$builder
->add('foo', 'entity', array(
'class' => 'FooBarBundle:Foo',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) {
return $er->createQueryBuilder('q')->orderBy('q.name', 'ASC');
},
));
EDIT:
Actually the 'class' option is the only required field option. You can read a bit more about the entity field type here: http://symfony.com/doc/2.0/reference/forms/types/entity.html
Hope this helps.
EDIT:
Further to discussion below, here's an example
In the controller:
$entity = new Foo();
$type = new FooType();
$er = $this->getDoctrine()
->getEntityManager()
->getRepository('FooBarBundle:Foo');
$form = $this->createForm($type, $entity, array(
'foo_repository' => $er
));
The $options array is passed to the FooType::buildForm() method, so foo_repository should then be available in this method like so:
$er = $options['foo_repository'];
Symfony 4 and 5:
Symfony Form Types are services so you can use dependency injection:
class FooType extends AbstractType
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
private function getFooRepository(): FooRepository
{
return $this->entityManager->getRepository(Foo::class);
}
...
}
or inject specific repository:
class FooType extends AbstractType
{
private $fooRepository;
public function __construct(FooRepository $fooRepository)
{
$this->fooRepository = $fooRepository;
}
...
}

Categories