Symfony3 :: Handle ManyToMany relations - symfony

I'm trying to save my ManyToMany relations between users and categories. Actually I'm trying to save my category with given users, but this doesn't work.
Form
$builder->add('name')
->add('users', EntityType::class, array(
'label' => 'Benutzer',
'class' => 'AppBundle\Entity\User',
'multiple' => true,
'expanded' => true,
'required' => false,
'choice_label' => function (User $user) {
return $user->getUsername();
}
))
->add('submit', SubmitType::class, array(
'label' => 'Speichern'
));
Form Handler
public function onSuccess(Request $request)
{
// Get category from form
$category = $this->getForm()->getData();
// Redirect to parent category when setted
if ($this->parent) {
$category->setParent($this->parent);
$response = new RedirectResponse($this->router->generate('categories.view', [
'category' => $this->parent->getId()
]));
} else {
// Build new redirect response
$response = new RedirectResponse($this->router->generate('categories.view', [
'category' => $category->getId()
]));
}
try {
// Save category in database
$this->em->merge($category);
$this->em->flush();
} catch (ORMException $ex) {
throw $ex;
}
return $response;
}

Maybe you have to unserialize the Entity $category first?
$detachedCategory = unserialize($category);
$this->em->merge($detachedCategory);
$this->em->flush();
I found this link regarding that:
How to manage deserialized entities with entity manager?
Not sure if that's the answer, but you might want to do more research.

Related

Symfony4 - change single parameter in url after submitting form

I have a route that looks like this - route/{parameter} and I need to change the parameter after submitting a form.
I tried to use redirectToRoute but it created new URL together with some other parameters that the form passed which I don't want.
So I would love to ask you if there is some way to redirect to a new URL with the only parameter that I choose through select in the form.
Thank you very much for your responses.
EDIT:
I am going to share more actual information. This is how my controller for the form looks like:
$form = $this->createFormBuilder()
->setMethod("get")
->add('category', ChoiceType::class, [
'choices' => [
'Všechny kategorie' => 'vsechny-kategorie',
'Automobilový průmysl' => 'automobilovy-prumysl',
'Stavebnictví' => 'stavebnictvi',
'Elektronika a elektrotechnika' => 'elektronika-a-elektrotechnika',
'Gastronomie' => 'gastronomie',
'Lesnictví' => 'lesnictvi',
'Potravinářský průmysl' => 'potravinarsky-prumysl',
'IT technologie' => 'it-technologie',
'Logistika' => 'logistika',
'Strojírenství' => 'strojirenstvi',
'Zdravotnictví' => 'zdravotnictvi'
],
'label' => 'Kategorie:'
])
->add('send', SubmitType::class, ['label' => 'Test'])
->getForm();
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$category = $data['category'];
return $this->redirectToRoute('jobs', [
'jobs' => $pagination,
'categoryForm' => $form->createView(),
'category' => $category,
]);
}
You should be able to use the redirectToRoute, but be sure to pass the parameter you're trying to dynamically set as an array:
// in your controller action:
return $this->redirectToRoute('post_form_route', ['parameter' => $parameter]);
If that's not working for you, I would double check your route definitions and make sure your route's name & expected URL parameters are passed correctly.
Documentation on redirecting in the controller
you can try that :
if($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$category = $data['category'];
return $this->redirectToRoute('route', [
'parameter' => $form->getData()->getCategory()
]);
}
return $this->redirectToRoute('jobs', [
'jobs' => $pagination,
'categoryForm' => $form->createView(),
]);

Symfony 3: How to use two choices/dropdowns from two tables in one form

Symfony version 3.1.3
I have created a dropdown using the entity called Classes and you can see the Controller below,
public function studentAddClassAction( $id, Request $request )
{
// get the student from the student table
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('PIE10Bundle:Users')->find($id);
// new class object and create the form
$classes= $em->getRepository('PIE10Bundle:Classes')->findAll();
$form = $this->createForm(ClassType::class, $classes);
$form->handleRequest($request);
if( $form->isSubmitted() && $form->isValid() )
{
// form submit operations
}
return $this->render(
'PIE10Bundle:student:layout_student_addclass.html.twig',
array(
'user' => $user,
'title' => 'Add Class',
'tables'=> 1,
'form' => $form->createView()
)
);
}
and the ClassType is below
class ClassType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('classes',
EntityType::class,
array('class' => 'PIE10Bundle:Classes',
'expanded' => false,
'multiple' => false,));
$builder->add('Add Class',
SubmitType::class,
array('attr' => array('class' => 'btn btn-primary',
'style' => 'margin:15px 0;')) );
}
}
And this works fine and it gives all the classes from the database. Also I have another entity called Users and it has a column called roles (DC2Type:array) and it has a role called ROLE_PARENT and I can retrieve all the parents using the following query
$query = $this->getDoctrine()->getEntityManager()
->createQuery('SELECT u FROM PIE10Bundle:Users u WHERE u.roles LIKE :role')
->setParameter('role', '%"ROLE_PARENT"%' );
$users = $query->getResult();
My Question is how to add these parents list as a choice list into same above form in the studentAddClassAction Controller.
Please let me know in any other information is needed for this.
To have a custom set of entities as a choice list you need to use a query_builder option
So it will look like
$builder->add('parent',
EntityType::class,
array('class' => 'PIE10Bundle:Users',
'expanded' => false,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.roles LIKE :role')
->setParameter('role', '%"ROLE_PARENT"%');
},
'multiple' => false
));

Symfony 2: Set field as read only after first save

I have a Symfony 2 entity. When I create a new record, I must fill all the values using a form, but after saving it, one of the values, $amount shouldn't be updatable when I update the others members.
How can I accomplish this? It's possible to mark a form member as a read-only, in runtime?
By using the validation_groups and name options when creating your form, you can change the form.
The name attribute sets the form creation, and the validation_groups takes care of the validation.
For example, in the create/new method of your controller;
public function createAction(Request $request)
{
// Instantiate new Foo object
$client = new Foo();
// create the form (setting validation group)
$form = $this->formFactory->create('foo', $foo, array(
'name' => 'create',
'validation_groups' => array('create')
)
);
// form has been submitted...
if ('POST' === $request->getMethod()) {
// submits the form
$form->handleRequest($request);
// do validation
if ($form->isValid()) {
// do whatever
}
}
// either GET or validation failed, so show the form
return $this->template->renderResponse('FooBundle:foo:add.html.twig', array(
'form' => $form->createView(),
'foo' => $foo
));
}
And in the edit/update function of your controller;
public function updateAction($id, Request $request)
{
// Instantiate Client object
$client = new Foo($id);
// create the form (setting validation group)
$form = $this->formFactory->create('foo', $foo, array(
'name' => 'update',
'validation_groups' => array('update')
));
// form has been submitted...
if ('POST' === $request->getMethod()) {
// submits the form
$form->handleRequest($request);
// do validation
if ($form->isValid()) {
// do whatever
}
}
// either GET or validation failed, so show the form
return $this->template->renderResponse('FooBundle:foo/edit:index.html.twig', array(
'form' => $form->createView(),
'foo' => $foo
));
}
And your Form Type will look something like;
class FooType extends BaseAbstractType
{
protected $options = array(
'data_class' => 'FooBundle\Model\Foo',
'name' => 'foo',
);
private $roleManager;
public function __construct($mergeOptions = null)
{
parent::__construct($mergeOptions);
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->$options['name']($builder, $options);
}
private function create(FormBuilderInterface $builder, array $options)
{
// ID
$builder->add('Id', 'text', array(
'required' => true,
'label' => 'ID',
'attr' => array(
'placeholder' => 'Format: 2 alphanumeric (e.g. A1)'
)
));
// Name - only show on create
$builder->add('Name', 'text', array(
'required' => true,
'label' => 'Name',
'attr' => array(
'placeholder' => 'Your name'
)
));
// add the submit form button
$builder->add('save', 'submit', array(
'label' => 'Save'
));
}
private function update(FormBuilderInterface $builder, array $options)
{
// ID
$builder->add('Id', 'text', array(
'required' => true,
'label' => 'ID',
'attr' => array(
'placeholder' => 'Format: 2 alphanumeric (e.g. A1)',
)
));
// Name - just for show
$builder->add('Name', 'text', array(
'required' => true,
'label' => 'Name',
'attr' => array(
'readonly' => 'true' // stops it being editable
)
));
// add the submit form button
$builder->add('save', 'submit', array(
'label' => 'Save'
));
}
}
P.S. All my classes are declared as services, so how you call create forms/views/etc may be different.

Avoid empty password field when edit a specific user with SonataAdminBundle

I have a problem when I want to edit an existing user from the Backend (using SonataAdminBundle and FOSUserBundle). In configureFormFields method of my UserAdmin class, the password field appears empty and this is a problem when I need to edit another fields (for example the lastname) keeping the same user password. This field (and the password verification field) must be filled again! (I do not want modify the user password)
In my UserAdmin class, I have:
public function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('User Data')
->add('username')
->add('plainPassword', 'repeated', array(
'type' => 'password',
'options' => array('translation_domain' => 'FOSUserBundle'),
'first_options' => array('label' => 'form.password'),
'second_options' => array('label' => 'form.password_confirmation'),
'invalid_message' => 'fos_user.password.mismatch',
))
->add('firstname')
->add('lastname')
->add('email')
->add('user_roles')
->add('enabled', 'checkbox', array(
'label' => 'Enable Account',
'required' => false,
))
->end()
;
}
I tried to overwrite prePersist and preUpdate methods in my UserAdmin class, but these do not work. The password is encripted in the database following the FOS standard (with salt and sha512).
Any solution?
Many thanks!
You can override your the preUpdate function in your admin class this is how i have done
public function preUpdate($object)
{
$DM = $this->getConfigurationPool()->getContainer()->get('Doctrine')->getManager();
$repository = $DM->getRepository('Namespace\YourBundle\Entity\User')->find($object->getId());
$Password = $object->getPassword();
if (!empty($Password)) {
$salt = md5(time());
$encoderservice = $this->getConfigurationPool()->getContainer()->get('security.encoder_factory');
$encoder = $encoderservice->getEncoder($object);
$encoded_pass = $encoder->encodePassword($object->getPassword(), $salt);
$object->setSalt($salt);
$object->setPassword($encoded_pass);
} else {
$object->setPassword($repository->getPassword());
}
}
And my configureFormFields function
protected function configureFormFields(FormMapper $formMapper)
{
$passwordoptions=array('type' => 'password','invalid_message' => 'The password fields must match.',
'options' => array('attr' => array('class' => 'password-field')),'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Confirm password'),'translation_domain' => 'FOSUserBundle'
);
$this->record_id = $this->request->get($this->getIdParameter());
if (!empty($this->record_id)) {
$passwordoptions['required'] = false;
$passwordoptions['constraints'] = array(new Assert\Length(array('min' => 10))
,new Assert\Regex(array('pattern' => '/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{10,}$/','match'=>true,'message'=>'Password must contain atleast 1 special character 1 upper case letter 1 number and 1 lower case letter !'))
);
} else {
$passwordoptions['required'] = true;
$passwordoptions['constraints'] = array(new Assert\Length(array('min' => 10))
,new Assert\Regex(array('pattern' => '/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{10,}$/','match'=>true,'message'=>'Password must contain atleast 1 special character 1 upper case letter 1 number and 1 lower case letter !'))
);
}
$formMapper->add('password', 'repeated', $passwordoptions); /*you can add your other fields*/
}
I have the same problem than you but without SonataAdminBundle.
In a classic user admin management (Symfony3), this works :
// src/AppBundle/Form/UserType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// By default, password is required (create user case)
$passwordOptions = array(
'type' => PasswordType::class,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat password'),
'required' => true
);
// If edit user : password is optional
// User object is stored in $options['data']
$recordId = $options['data']->getId();
if (!empty($recordId)) {
$passwordOptions['required'] = false;
}
$builder
->add('prenom', TextType::class, array('label' => 'First name'))
->add('nom', TextType::class, array('label' => 'Last name'))
->add('email', EmailType::class)
->add('username', TextType::class)
->add('plainPassword', RepeatedType::class, $passwordOptions)
->add('roleList', ChoiceType::class, array(
'label' => 'Role',
'choices' => array(
'Admin' => 'ROLE_ADMIN',
'User' => 'ROLE_USER'
),
))
->add('save', SubmitType::class, array(
'attr' => array('class' => 'button-link save'),
'label' => 'Validate'
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User',
));
}
}
Don't forget to remove #Assert\NotBlank() on plainPassword in your User Entity :
/**
* #Assert\Length(max=4096)
*/
private $plainPassword;
My editAction() in UserController.php :
/**
* #Route("/users/edit/{id}", name="user_edit")
*/
public function editAction($id, Request $request) {
// get user from database
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('AppBundle:User')->find($id);
// user doesn't exist
if (!$user) {
throw $this->createNotFoundException('No user found for id '. $id);
}
// build the form with user data
$originalPassword = $user->getPassword(); # encoded password
$form = $this->createForm(UserType::class, $user);
// form POST
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Encode the password if needed
# password has changed
if (!empty($form->get('plainPassword')->getData())) {
$password = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
# password not changed
} else {
$user->setPassword($originalPassword);
}
$role = $form->get('roleList')->getData();
$user->setRoles(array($role));
// update the User
$em->flush();
// success message
$this->addFlash('notice', 'User has been updated successfully !');
// redirection
return $this->redirectToRoute('user');
}
// show form
return $this->render('users/form.html.twig', array(
'h1_title' => 'Edit user : ' . $user->getPrenom() . " " . $user->getNom(),
'form' => $form->createView()
));
}
Now, password is required when you create a new user, but it isn't when you edit one.
Maybe too late but useful for others.
With thanks to other posts.
So simple. Just use :
class UserAdmin extends AbstractAdmin
{
protected $formOptions = array(
'validation_groups' => array('Profile')
);
//..
}
It uses the profile validation group defined in:
friendsofsymfony/user-bundle/Resources/config/validation.xml
There are some ways to do that
Change configureFormField behavoir
You could get the current object ($this->subject or $this->getSubject();) inside the configureFormFields and check if its a new Object or an existing one and change the fields behaviour (validation for example)
Using saveHooks and FOSUser
here is an example
http://sonata-project.org/bundles/admin/master/doc/reference/saving_hooks.html
this will show the hashed password inside the password field but should update it when entering a new plain one ( if i remember correctly)
Combine both and implement your own logic
inside of the hook you can get the FOSUserManager
and handle the user update with that
container : $this->getConfigurationPool()->getContainer();
fosusermanager: $userManager = $container->get('fos_user.user_manager');
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/user_manager.md

Validating dynamically loaded choices in Symfony 2

I have a choice field type named *sub_choice* in my form whose choices will be dynamically loaded through AJAX depending on the selected value of the parent choice field, named *parent_choice*. Loading the choices works perfectly but I'm encountering a problem when validating the value of the sub_choice upon submission. It gives a "This value is not valid" validation error since the submitted value is not in the choices of the sub_choice field when it was built. So is there a way I can properly validate the submitted value of the sub_choice field? Below is the code for building my form. I'm using Symfony 2.1.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('parent_choice', 'entity', array(
'label' => 'Parent Choice',
'class' => 'Acme\TestBundle\Entity\ParentChoice'
));
$builder->add('sub_choice', 'choice', array(
'label' => 'Sub Choice',
'choices' => array(),
'virtual' => true
));
}
To do the trick you need to overwrite the sub_choice field before submitting the form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$parentChoice = $event->getData();
$subChoices = $this->getValidChoicesFor($parentChoice);
$event->getForm()->add('sub_choice', 'choice', [
'label' => 'Sub Choice',
'choices' => $subChoices,
]);
});
}
this accept any value
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if(is_array($data['tags']))$data=array_flip($data['tags']);
else $data = array();
$event->getForm()->add('tags', 'tag', [
'label' => 'Sub Choice',
'choices' => $data,
'mapped'=>false,
'required'=>false,
'multiple'=>true,
]);
});
Adding an alternate approach for future readers since I had to do a lot of investigation to get my form working. Here is the breakdown:
Adding a "New" option to a dropdown via jquery
If "New" is selected display new form field "Custom Option"
Submit Form
Validate data
Save to database
jquery code for twig:
$(function(){
$(document).ready(function() {
$("[name*='[custom_option]']").parent().parent().hide(); // hide on load
$("[name*='[options]']").append('<option value="new">New</option>'); // add "New" option
$("[name*='[options]']").trigger("chosen:updated");
});
$("[name*='[options]']").change(function() {
var companyGroup = $("[name*='[options]']").val();
if (companyGroup == 'new') { // when new option is selected display text box to enter custom option
$("[name*='[custom_option]']").parent().parent().show();
} else {
$("[name*='[custom_option]']").parent().parent().hide();
}
});
});
// Here's my Symfony 2.6 form code:
->add('options', 'entity', [
'class' => 'Acme\TestBundle\Entity\Options',
'property' => 'display',
'empty_value' => 'Select an Option',
'mapped' => true,
'property_path' => 'options.optionGroup',
'required' => true,
])
->add('custom_option', 'text', [
'required' => false,
'mapped' => false,
])
To handle the form data we need to use the PRE_SUBMIT form event.
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (isset($data['options']) && $data['options'] === 'new') {
$customOption = $data['custom_option'];
// todo: handle this better on your own
if (empty($customOption)) {
$form->addError(new FormError('Please provide a custom option'));
return;
}
// Check for a duplicate option
$matches = $this->doctrine->getRepository('Acme\TestBundle\Entity\Options')->matchByName([$customOption]);
if (count($matches) > 0) {
$form->addError(new FormError('Duplicate option found'));
return;
}
// More validation can be added here
// Creates new option in DB
$newOption = $this->optionsService->createOption($customOption); // return object after persist and flush in service
$data['options'] = $newOption->getOptionId();
$event->setData($data);
}
});
Let me know if ya'll have any questions or concerns. I know this might not be the best solution but it works. Thanks!
you cannot not build the sub_choice validation because during you config its validator you don't know which values are valid (values depend on value of parent_choice).
What you can do is to resolve parent_choice into entity before you make new YourFormType() in your controller.
Then you can get all the possible values for sub_choice and provide them over the form constructor - new YourFormType($subChoice).
In YourFormType you have to add __construct method like this one:
/**
* #var array
*/
protected $subChoice = array();
public function __construct(array $subChoice)
{
$this->subChoice = $subChoice;
}
and use provided values in form add:
$builder->add('sub_choice', 'choice', array(
'label' => 'Sub Choice',
'choices' => $this->subChoice,
'virtual' => true
));
Suppose for sub choices you have id's right ?
Create and empty array with a certain number of values and give it as a choice
$indexedArray = [];
for ($i=0; $i<999; $i++){
$indexedArray[$i]= '';
}
then 'choices' => $indexedArray, :)

Resources