How to conceive a Symfony entity? - forms

I create a Symfony app about "X-Files".
I have several entities among which "Episode" and "Charactor".
Episode has a ManyToMany relation with CharActor.
When I add an episode with the form created in the twig and managed by "EpisodeType", it does not properly update the charactor table. Well, logically, but not really as I wish.
Below is an extract of "_form.html.twig".
form.resume and others before properly update the table "episode".
form.charActors updates the table "charactor", but not properly.
<div class="formGroup">
{{ form_label(form.resume, "Résumé", {"label_attr": {"class": "formLabels"}}) }}
{{ form_widget(form.resume, {"attr": {"class": "formWidgets", "rows": "5"}}) }}
</div>
<div class="formGroup">
{{ form_label(form.charActors, "Personnages/Acteurs", {"label_attr": {"class": "formLabels"}}) }}
{{ form_widget(form.charActors, {"attr": {"class": "formWidgetsActors"}}) }}
</div>
Below is "EpisodeType".
<?php
namespace App\Form;
use App\Entity\CharActor;
use App\Entity\Episode;
use Symfony\Component\Form\AbstractType;
use App\Repository\CharActorRepository;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EpisodeType extends AbstractType
{
private $charActorRepository;
public function __construct(CharActorRepository $charActorRepository)
{
$this->charActorRepository = $charActorRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('numberSeason', IntegerType::class)
->add('numberEpisode', IntegerType::class)
->add('originalTitle', TextType::class)
->add('frenchTitle', TextType::class)
->add('frenchDiff', DateType::class, [
"widget" => "choice",
"format" => "d M y",
"years" => range(1993, 2019)])
->add('scenario', TextType::class)
->add('realisation', TextType::class)
->add('resume', TextareaType::class)
->add('charActors', EntityType::class, [
"multiple" => true, // Permet de faire plusieurs sélections.
"class" => CharActor::class, // Recherche les choix dans cette entité.
"choice_label" => "name", // Utilise cette propriété comme option visible.
])
->add('image', TextType::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Episode::class,
]);
}
}
What is wrong is that in my charactor table, which has the fields "name" (with a string such as "Agent Scully : Gillian Anderson") and "episodesNb" (with an array such as ["1-01","1-02","1-03"]), it creates a new line with these 2 fields empty.
But what I would like to obtain would be, if for example I select Scully in the EntityType, to update the array in "episodesNb" with the new episode number and have as a result ["1-01","1-02","1-03", "1-04"].
Thanks for your help.

If you have a coherent model for your need as mentioned #simon.ro , such as:
class Episode
/**
* #ORM\ManyToMany(targetEntity="App\Settings\Domain\Character")
* #ORM\JoinTable(name="episode_character",
* joinColumns={#ORM\JoinColumn(name="episode_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="character_id", referencedColumnName="id")}
* )
*/
private Collection $attachedCharacters;
Your type just need to look like that :
->add('attachedCharacters', EntityType::class, [
'choices' => $this->charatersRepository->findAll(),
'choice_value' => 'fullName',
'multiple' => true
])

Related

Can't get a way to read the property "user" in class "App\Entity\User"

I made a form so that when a user is selected, his role changes from ["ROLE_USER"] to ["ROLE_ADMIN"]. When form is submitted, I have the following error : Can't get a way to read the property "user" in class "App\Entity\User".
I understand it must come the fact there is no such field named user in the User class, but I don't know with which field I can replace user. I already tried name or roles, but it doesn't work either with them.
How can I select a user and simply change his role ?
AdminType.php
<?php
namespace App\Form\Admin;
use App\Entity\User;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class AdminType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('user', EntityType::class, [
'class' => User::class,
'choice_label' => 'name',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '["ROLE_USER"]')
->orderBy('u.firstname', 'ASC');
},
'placeholder' => 'J\'ajoute un administrateur',
])
->add('save', SubmitType::class, [
'attr' => ['class' => 'save'],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
'translation_domain' => 'forms'
]);
}
}
AdminController.php
<?php
namespace App\Controller\Admin;
use App\Form\Admin\AdminType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AdminController extends AbstractController
{
#[Route('/admin/list', name: 'admin')]
public function admin(
Request $request,
UserRepository $userRepository,
EntityManagerInterface $entityManagerInterface
){
$admins = $userRepository->admin();
$form = $this->createForm(AdminType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->get('user')->getData();
$user->setRoles(["ROLE_ADMIN"]);
$entityManagerInterface->persist($user);
$entityManagerInterface->flush();
return $this->redirectToRoute('admin');
}
return $this->renderForm('admin/user/admin.html.twig', [
'admins' => $admins,
'form' => $form
]);
}
Short answer
remove 'data_type' => User::class from configureOptions in your AdminType form.
Explanation
Setting the data_type of the form to the User entity will cause Symfony to try to create a User object and set its user Property to the value in the form's user field (which you get via $form->get('user')). That's why the error message tells you, that it can't find a way to read (to then overwrite) the property user on the User class.
Removing the data_type will then mean, that the form's data type is just an array (the default), you could also explicitly set null.
If the form's data type is an array, it'll just set the user key in that array.
Since your form's user field is of type EntityType, with a given class, it already ensures that the form's user field's value must be of that class (the User entity). And since you only want to select a user, to then add a role, I assume that the form doesn't need the User data_type.

Symfony return empty form after validation with errors

$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** do stuff */
}
return $this->render('security/register.twig', [
'registrationForm' => $form->createView()
]);
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nickname', TextType::class, [
'label' => 'asdasdasd',
])
->add('email', TextType::class, [
'label' => 'Email',
])->add->add end more add }
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'csrf_protection' => true,
// the name of the hidden HTML field that stores the token
'csrf_field_name' => '_token',
// an arbitrary string used to generate the value of the token
// using a different string for each form improves its security
'csrf_token_id' => 'registrationForm',
]);
}
}
after wrong validation render an empty form
formType
this is a registrationtype class
what i can add to formType or to controller for returning filled input fields
After viewing the comments under the question, it seems that the Twig part of the form is missing.
To render your form inside your Twig template, you should use Twig form functions.
In your case you can simply write:
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.nickname) }}
{{ form_row(form.email) }}
{{ ... }}
{{ form_row(form.submit, { 'label': 'Submit me' }) }}
{{ form_end(form) }}
You also can use all form types that Symfony provides. For example, you can use for email field: Email field type.
->add('email', EmailType::class, [
'label' => 'Email',
])
You can use many form field types, Twig can handle them: form types

how to show specific field of a form for 'create' and 'edit' action - Symfony3

I have a simple FormType attached to an entity called media wich I rendered in my view.
I have a newAction that lets me create my object, and a editAction that lets me edit it with my same form in my controller.
However I don't want some field appears in my editview` as I already entered them when I created it.
But even though I use form_row to specifically render my form line by line, when I add the form_end at the end, it renders all my fields, even the ones I didn't call.
My FormType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', FileType::class, array(
'data_class' => null,
'label' => "Add an image"
))
->add('context', ChoiceType::class, array(
'label' => 'image section',
'choices' => array(
'header' => 'header',
'middle' => 'middle',
)
))
->add('save', SubmitType::class, array(
'label' => "Add"
));
}
My view
{{ form_start(editForm) }}
{{ form_row(editForm.name) }}
{{ form_row(editForm.save) }}
{{ form_end(editForm) }}
But even if I use the form rows, it actually shows my context field in the view, which I didn't call.
So I tried some hack to get around it.
this one worked but when I click on submit form, it shows me an error that context field cannot be null, so this doesn't do the trick
{% do editForm.context.setRendered %}
And I found a way to do it with jQuery to hide the form like this
<script>
$(document).ready(function () {
$("#media_context").parent().hide();
});
</script>
the jQuery works and hide my row in my form. But I was wondering if I could do it without using jQuery and be able to render only specific field of my form in my view?
In Symfony 2, you could remove some fields from the builder when editing your entity. Your edit form must extends your create form in Symfony 2.
I think you can do the same in Symfony 3, try something like :
class EditType extends CreateType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->remove('context') //remove the fields that no longer needed
}
public function configureOptions(OptionsResolver $resolver)
{
/...
}
}
You don't need to change CreateType
class Createtype extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', FileType::class, array(
'data_class' => null,
'label' => "Add an image"
))
->add('context', ChoiceType::class, array(
'label' => 'image section',
'choices' => array(
'header' => 'header',
'middle' => 'middle',
)
))
->add('save', SubmitType::class, array(
'label' => "Add"
));
}
}
From the symfony docs:
This helper (form_end) also outputs form_rest() unless you set render_rest to false.
form_rest(view, variables)
This renders all fields that have not yet been rendered for the given form.
{# don't render unrendered fields #}
{{ form_end(form, {'render_rest': false}) }}
Try this
{{ form_start(editForm) }}
{{ form_row(editForm.name) }}
{{ form_row(editForm.save) }}
{{ form_end(editForm, {'render_rest': false}) }}

Symfony2 form class renders all rows even if I don't want it to

First of all, I am sorry if the question title is a bit odd, but I do not know what else to call it...
I have this form class, which I cannot change:
class ItemDetailType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text', array(
'label' => 'Název'))
->add('room', 'entity', array(
'class' => 'CvutFitIctCtuIdentityBundle:Room',
'label' => 'Místnost'))
->add('person', 'entity', array(
'class' => 'CvutFitIctCtuIdentityBundle:Person',
'label' => 'Osoba'))
->add('organizationalUnit', 'entity', array(
'class' => 'CvutFitIctCtuIdentityBundle:OrganizationalUnit',
'label' => 'Organizační jednotka'))
;
$builder->setAttribute('attr', array());
if (isset($options['render_submit']) && $options['render_submit'])
$builder
->add('submit', 'submit', array(
'label' => 'Uložit',
'attr' => array('class' => 'btn btn-success')))
->add('reset', 'reset', array(
'label' => 'Zrušit',
'attr' => array('class' => 'btn btn-danger')))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Cvut\Fit\BiWt2\InventoryBundle\Entity\Item',
'render_submit' => true,
'attr' => array(
'role' => 'form',
'class' => 'form-horizontal'
)
));
}
/**
* #return string
*/
public function getName() {
return 'cvut_fit_biwt2_inventory_form_item';
}
}
But in a template, I have to render only some of the rows (room, person, organizationalUnit and submit), and not render name and reset. This is in the conditions I am obliged to meet, so editing the class is not a valid option.
In controller I create the form like this:
$form = $this->createForm(
new ItemDetailType, $item, array(
'action' => $this->generateUrl('items_detail_form', array('id' => $id)),
'render_submit' => true
)
);
I tried to render only the desired rows this way, but it only makes the go on top of the form and the remaining two are still rendered under them...
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.room) }}
{{ form_row(form.person) }}
{{ form_row(form.organizationalUnit) }}
{{ form_row(form.submit) }}
{{ form_end(form) }}
So I am a bit confused now. Is this the correct behavior? If yes, than how do I achieve what I need? The documentation is somewhat brief about this...
Thank you very much!
In symfony2 the default behaviour of:
{{ form_end(form) }}
is render all (even not mentioned before) fields like
{{ form_rest(form) }}
If you want to prevent this behaviour the one option is use:
</form>
or the better way like in this document http://symfony.com/doc/current/reference/forms/twig_reference.html#form-end-view-variables
{{ form_end(form, {'render_rest': false}) }}
Remember to render CSRF token manualy if you do this in that way:
{{ form_widget(form._token) }}
what about using
{% do form.name.setRendered %}
{% do form.reset.setRendered %}
This tell twig that the fields are shown even if they aren't
I am confused as to what exactly do you want to achieve here. But here are some thoughts:
You can create a new form type that extends this one if you want.
class ShorterItemDetailType extends ItemDetailType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
// only add the fields you want
}
public function getParent() {
return 'cvut_fit_biwt2_inventory_form_item'
}
/**
* #return string
*/
public function getName() {
return 'cvut_fit_biwt2_inventory_form_item_shorter';
}
}
And in your controller use this one.
$form = $this->createForm(
new ShorterItemDetailType(), $item, array(
'action' => $this->generateUrl('items_detail_form', array('id' => $id))
)
);

Symfony2 : Sort / Order a translated entity form field?

I am trying to order an entity form field witch is translated.
I am using the symfony translation tool, so i can't order values with a SQL statement.
Is there a way to sort values after there are loaded and translated ?
Maybe using a form event ?
$builder
->add('country', 'entity',
array(
'class' => 'MyBundle:Country',
'translation_domain' => 'countries',
'property' => 'name',
'empty_value' => '---',
)
)
I found the solution to sort my field values in my Form Type.
We have to use the finishView() method which is called when the form view is created :
<?php
namespace My\Namespace\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
class MyFormType extends AbstractType
{
protected $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
// Order translated countries
$collator = new \Collator($this->translator->getLocale());
usort(
$view->children['country']->vars['choices'],
function ($a, $b) use ($collator) {
return $collator->compare(
$this->translator->trans($a->label, array(), 'countries'),
$this->translator->trans($b->label, array(), 'countries')
);
}
);
}
// ...
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', 'entity',
array(
'class' => 'MyBundle:Country',
'translation_domain' => 'countries',
'property' => 'name',
'empty_value' => '---',
)
)
;
}
}
OLD ANSWER
I found a solution for my problem, I can sort them in my controller after creating the view :
$fview = $form->createView();
usort(
$fview->children['country']->vars['choices'],
function($a, $b) use ($translator){
return strcoll($translator->trans($a->label, array(), 'countries'), $translator->trans($b->label, array(), 'countries'));
}
);
Maybe I can do that in a better way ?
Originally I wished to do directly in my form builder instead of adding extra code in controllers where I use this form.
I think it's impossible. You need to use PHP sorting, but if you use Symfony Form Type, I would advise to sort it with JavaScript after page is loaded.
If your countries are in an array, just use the sort() function, with the SORT_STRING flag. You will do some gymnastic to have it in my opinion.
Check this doc : http://php.net/manual/fr/function.sort.php