Handling a complex Symfony2 form with multiple entities relationship - php

I have a form (still not finished and is missing many fields) that is handled as a wizard with steps, in which fields from multiple entities are handled. This is the form itself:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tipo_tramite', 'entity', array(
'class' => 'ComunBundle:TipoTramite',
'property' => 'nombre',
'required' => TRUE,
'label' => "Tipo de Trámite",
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('q')
->where('q.activo = :valorActivo')
->setParameter('valorActivo', TRUE);
}
))
->add('oficina_regional', 'entity', array(
'class' => 'ComunBundle:OficinaRegional',
'property' => 'nombre',
'required' => TRUE,
'label' => "Oficina Regional",
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('q')
->where('q.activo = :valorActivo')
->setParameter('valorActivo', TRUE);
}
))
->add('procedencia_producto', 'entity', array(
'class' => 'ComunBundle:ProcedenciaProducto',
'property' => 'nombre',
'required' => TRUE,
'label' => "Procedencia del Producto"
))
->add('finalidad_producto', 'entity', array(
'class' => 'ComunBundle:FinalidadProducto',
'property' => 'nombre',
'required' => TRUE,
'label' => "Finalidad del Producto"
))
->add('condicion_producto', 'entity', array(
'class' => 'ComunBundle:CondicionProducto',
'property' => 'nombre',
'required' => TRUE,
'label' => "Condición del Producto"
))
->add('lote', 'integer', array(
'required' => TRUE,
'label' => "Tamaño del Lote"
))
->add('observaciones', 'text', array(
'required' => FALSE,
'label' => "Observaciones"
));
}
I have a question regarding how to handle the parameter data_class in this case so I do not have to do magic in the controller. When I say magic I mean the following:
public function empresaAction()
{
$entity = new Empresa();
$form = $this->createForm(new EmpresaFormType(), $entity);
return array( 'entity' => $entity, 'form' => $form->createView() );
}
public function guardarEmpresaAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->container->get('fos_user.user_manager');
/** #var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
$dispatcher = $this->container->get('event_dispatcher');
/** #var $mailer FOS\UserBundle\Mailer\MailerInterface */
$mailer = $this->container->get('fos_user.mailer');
$request_empresa = $request->get('empresa');
$request_sucursal = $request->get('sucursal');
$request_chkRif = $request->get('chkRif');
$request_estado = $request_empresa[ 'estado' ];
$request_municipio = $request->get('municipio');
$request_ciudad = $request->get('ciudad');
$request_parroquia = $request->get('parroquia');
$user = $userManager->createUser();
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, $event);
if (null !== $event->getResponse())
{
return $event->getResponse();
}
$entity = new Empresa();
$form = $this->createForm(new EmpresaFormType(), $entity);
$form->handleRequest($request);
$success = $url = $errors = "";
if ($form->isValid())
{
if ($request_sucursal != NULL || $request_sucursal != "")
{
$padreEntity = $em->getRepository('UsuarioBundle:Empresa')->findOneBy(array( "padre" => $request_sucursal ));
if (!$padreEntity)
{
$padreEntity = $em->getRepository('UsuarioBundle:Empresa')->findOneBy(array( "id" => $request_sucursal ));
}
if ($request_chkRif != NULL || $request_chkRif != "")
{
$rifUsuario = $request_empresa[ 'tipo_identificacion' ] . $request_empresa[ 'rif' ];
}
else
{
$originalRif = $padreEntity->getRif();
$sliceRif = substr($originalRif, 10, 1);
$rifUsuario = $originalRif . ($sliceRif === false ? 1 : $sliceRif + 1);
}
$entity->setPadre($padreEntity);
}
else
{
$rifUsuario = $request_empresa[ 'tipo_identificacion' ] . $request_empresa[ 'rif' ];
}
$user->setUsername($rifUsuario);
$user->setRepresentativeName($request_empresa[ 'razon_social' ]);
$user->setEmail($request_empresa[ 'usuario' ][ 'email' ]);
$user->setPlainPassword($request_empresa[ 'usuario' ][ 'plainPassword' ][ 'first' ]);
$pais = $em->getRepository('ComunBundle:Pais')->findOneBy(array( "id" => 23 ));
$user->setPais($pais);
$estado_id = $request_estado ? $request_estado : 0;
$estado = $em->getRepository('ComunBundle:Estado')->findOneBy(array( "id" => $estado_id ));
$user->setEstado($estado);
$municipio_id = $request_municipio ? $request_municipio : 0;
$municipio = $em->getRepository('ComunBundle:Municipio')->findOneBy(array( "id" => $municipio_id ));
$user->setMunicipio($municipio);
$ciudad_id = $request_ciudad ? $request_ciudad : 0;
$ciudad = $em->getRepository('ComunBundle:Ciudad')->findOneBy(array( "id" => $ciudad_id ));
$user->setCiudad($ciudad);
$parroquia_id = $request_parroquia ? $request_parroquia : 0;
$parroquia = $em->getRepository('ComunBundle:Parroquia')->findOneBy(array( "id" => $parroquia_id ));
$user->setParroquia($parroquia);
...
}
else
{
$errors = $this->getFormErrors($form);
$success = FALSE;
}
return new JsonResponse(array( 'success' => $success, 'errors' => $errors, 'redirect_to' => $url ));
}
Since the 'data_classonEmpresaFormTypeis set toUsuarioBundle\Entity\Empresa` then I need to handle any extra parameter as example above show with getter/setter and that's a lot of work for complex/big forms.
In the sample form, the fields tipo_tramite will persist in the class ComunBundle\Entity\Producto but the field oficina_regional will persist in the class ComunBundle\Entity\SolicitudUsuario and so with others who are not placed even here but they are in the form, in total should persist as 3 or 4 entities including relationships in many cases, how do you handle this?
I know there is CraueFormFlowBundle that maybe cover this process/flow but not sure if it is the solution to go.
Any advice?

Just worked with multi-step form few days ago. I think you need embedded forms here. Anyway can suggest you another nice bundle for creating wizard: SyliusFlowBundle. IMO it's more flexible and easier to understand than CraueFormFlowBundle.
BTW why do you want to store all in one form? We've used one form for each step, and I liked this approach.

Related

Get table names with Symfony+Doctrine excluding tables made from relations

I'd like to get all table names from my PSQL DB using Doctrine, but I don't want relation tables, e.g.:
Tables:
users
clients
users_clients <-- not this
I'm currently fetching them using
$em->getConnection()->getSchemaManager()->listTables();
Any way to do it without excluding results from array using strpos()?
The following code should work:
public class MyController {
public function listTables(EntityManagerInterface $em) {
$allMetadata = $em->getMetadataFactory()->getAllMetadata();
$tableNames = array_map(
function(ClassMetadata $meta) {
return $meta->getTableName();
},
$allMetadata);
// do something with the table names
}
}
Doctrine documentation: ClassMetadata#getTableName()
Here is how I would do it...
public function getTables(): bool|array {
// get connection any way you do
$connection = BasePDO::getInstance()->getEntityManager()->getConnection();
$query = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'";
$statement = $connection->prepare($query);
// First column is the table name
return $statement->executeQuery()->fetchFirstColumn();
}
I wrote a code that currently works for me, but it's not finished yet. If you got here to find an answer to the same question I had, feel free to analyse code.
But keep in mind that I'm not a pro (far from it).
This I have inside ReportController:
// Returns an array with my entities
$entities = $em->getMetadataFactory()->getAllMetadata();
//Generate my table names, according to Franz's answer
$tableNames = [
function(ClassMetadata $meta) {
return $meta->getTableName();
},
$entities
];
foreach ($tableNames[1] as $tableKey=>$tableName) {
$table[$tableKey] = $tableName->getTableName();
}
// Generate an array of columns from $table
// Code from generateColumnNames() below
$column[] = $this->getDoctrine()
->getRepository(Chamados::class)
->generateColumnNames($table);
//Since I'm displaying some Entity names in PT-BR, I need to treat them.
//I wish I had a better way to do this without having to treat them one by one
foreach ($entities as $entity) {
$e = explode('\\',$entity->getName());
if ($e[2] === "User") {
$e[2] = "Usuários";
}
else if ($e[2] === "Tramite") {
$e[2] = "Trâmites";
}
else if ($e[2] === "Clients") {
$e[2] = "Clientes";
}
$entKey[] = $e[2];
}
//Insert each name into each array value to display them in my form
$entities = array_combine($entKey, $entities);
//Generate the form
$form = $this->createFormBuilder(['aaa'])
->add('entity', ChoiceType::class, [
'required' => true,
'placeholder' => '',
'attr' => [
'style' => 'text-transform:capitalize',
'class' => 'entity-class general-control'
],
'choices' => $entities
])
->add('type', ChoiceType::class, [
'label' => 'Tipo de relatório',
'required' => true,
'placeholder' => '',
'attr' => [
'style' => 'display: none',
'class' => 'type-class general-control'
],
'choices' => [
'Campo' => 0,
'Quantidade' => 1,
'Tudo' => 2,
]
])
->add('select', ChoiceType::class, [
'label' => 'Campos',
'required' => false,
'multiple' => true,
'attr' => [
'style' => 'text-transform:capitalize; display: none',
'class' => 'select-class general-control'
],
'choices' => $column
])
->add('where', ChoiceType::class, [
'label' => 'Onde',
'placeholder' => '',
'attr' => [
'style' => 'text-transform:capitalize; display: none',
'class' => 'where-class general-control'
],
'choices' => $column
])
->add('operator', ChoiceType::class, [
'label' => false,
'attr' => [
'style' => 'display: none',
'class' => 'operator-class general-control'
],
'choices' => [
'Igual a' => '=',
'Diferente de' => '!=',
'Contém' => 'LIKE',
'Entre' => 'BETWEEN',
'Maior ou igual a' => '>=',
'Menor ou igual a' => '<=',
]
])
->add('parameter', TextType::class, [
'label' => false,
'attr' => [
'style' => 'display: none',
'placeholder' => 'Parâmetro',
'readonly' => 'true',
'class' => 'parameter-class general-control'
]
])
->add('submit', SubmitType::class, [
'label' => 'Gerar relatório',
'attr' => [
'class' => 'button-blue submit-class general-control',
'style' => 'display: none'
]
])
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//Get each form value to use it on generateReport() (code below)
$entity = $form->get('entity')->getData();
$type = $form->get('type')->getData();
$select = $form->get('select')->getData();
$where = $form->get('where')->getData();
$operator = $form->get('operator')->getData();
$parameter = $form->get('parameter')->getData();
//Generate the report, returning an array
$result = $this->getDoctrine()
->getRepository(Chamados::class)
->generateReport($entity, $type, $select, $where, $operator, $parameter);
return $this->redirectToRoute('generalResult', [
'result' => $result,
'entity' => $entity->getTableName()
]);
}
return [
'form' => $form->createView()
];
generateColumnNames() (Inside a Repository):
public function generateColumnNames($table){
$em = $this->getEntityManager();
foreach($table as $t) {
foreach ($em->getConnection()->getSchemaManager()->listTableColumns($t) as $v) {
$column[$t][$v->getName()] = $v->getName();
}
}
return $column;
}
generateReport() (Inside the same Repository):
public function generateReport($entity, int $type, array $selectField, string $whereField, string $operator, string $parameter): array
{
//0 = Fields / 1 = Count / * = Everything
if ($type === 0) {
foreach($selectField as $key=>$value) {
$select[$key] = "c." . $value;
}
$select = implode(", ", $select);
}
else if ($type === 1){
$select = "count('id')";
}
else {
$select = "c";
};
$query = $this->_em->createQueryBuilder()
->select($select)
->from($entity->getName(), 'c');
if ($operator === "LIKE") {
$parameter = "%" . $parameter . "%";
$query->andWhere("LOWER (c." . $whereField . ") " . $operator . " :" . $whereField);
}
else {
$query->andWhere("c." . $whereField . " " . $operator . " :" . $whereField);
}
$query->setParameter(":" . $whereField, $parameter);
$result = $query->getQuery()->getArrayResult();
return $result;
}

Symfony2 form : how to populate dynamically added entity field with a query result after creation?

In a Symfony 2.7 project,
let's say we have a form composed of 2 fields 'date'(date) and 'group'(entity), both have their own EventListener attached to, for FormEvents::SUBMIT events.
After the first submit, I'd like to add a new field 'travels' to the form and populate it with the result of a query using the two previous fields as criterias.
How to prevent 'travels' entity field from fetching all 'travel' in the DB and populate it manually after the raised events ?
I certainly miss some comprehension, I'm new to Symfony.
I know I can pass data directly in 'choices' option when creating 'travels' in each event but it would make useless DB calls.
I might count the number of registered events (of interest) and create 'travels' field when last event happens but it seems kind of weird...
Is there a clean solution for this case ?
(excuse for English, not my native language)
<?php
namespace MyBundle\Form;
// use directives...
class TravelRequestsWorklistType extends AbstractType {
private $em;
private $travelRepository;
private $searchQueryBuilder;
public function __construct(EntityManager $em) {
$this->em = $em;
$this->travelRepository = $this->em->getRepository(Travel::class);
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$formFactory = $builder->getFormFactory();
$builder
->add('requestedDate', 'date', array(
'widget' => 'single_text',
'input' => 'datetime',
'format' => 'dd/MM/yyyy',
'attr' => array('class' => 'date'),
'data' => null,
'mapped' => false
))
->add('selectedGroup', 'entity', array(
'class' => 'MyBundle\Entity\Group',
'placeholder' => '',
'mapped' => false,
'multiple' => false,
))
->add('search', 'submit');
$builder->get('requestedDate')->addEventListener(FormEvents::SUBMIT,
$this->onDateCriteriaEvent($formFactory));
$builder->get('selectedGroup')->addEventListener(FormEvents::SUBMIT,
$this->onGroupCriteriaEvent($formFactory));
}
private function onDateCriteriaEvent(FormFactory $ff) {
return
function(FormEvent $event) use ($ff) {
$root = $event->getForm()->getParent();
$requestedDate = $event->getData();
$qb = $this->getQueryBuilder();
$qb->andWhere('r.requestedDate = :requestedDate')
->setParameter('requestedDate', $requestedDate);
if(!$this->searchHasResult($root)) {
$this->addTravels($ff, $root);
}
};
}
private function onGroupCriteriaEvent(FormFactory $ff) {
return
function(FormEvent $event) use ($ff) {
$root = $event->getForm()->getRoot();
$selectedGroup = $event->getData();
$qb = $this->getQueryBuilder();
$qb->andWhere('r.group = :group')
->setParameter('group', $selectedGroup);
if(!$this->searchHasResult($root)) {
$this->addTravels($ff, $root);
}
};
}
private function addTravels(FormFactory $ff, Form $rootForm) {
$travels = $ff->createNamedBuilder('travels', 'entity', null,
array(
'class' => 'MyBundle\Entity\Travel',
'mapped' => false,
'multiple' => true,
'expanded' => true,
'auto_initialize' => false
));
$submitButton = $ff->createNamedBuilder('validate', 'submit');
$travels->addEventListener(FormEvents::PRE_SUBMIT, $this->onSearchResult());
$form->add($travels->getForm())->add($submitButton->getForm());
}
// The method setData() shows "This form should not contain extra fields"
private function onSearchResult() {
return
function(FormEvent $e) {
$data = $this->searchResultQueryBuilder->getQuery()->getResult();
$e->setData($data);
};
}
private function getQueryBuilder() {
if(null === $this->searchQueryBuilder) {
$this->searchResultQueryBuilder = $this->travelRepository->createQueryBuilder('r');
// add dummy where clause here in order to call "andWhere" indistinctly later
$this->searchResultQueryBuilder->where("1 = 1");
}
return $this->searchQueryBuilder;
}
private function searchHasResult(Form $form) {
return $form->has('travels');
}
}
Finally I found a piece of solution.
On $formField->addEventListener() we can set a priority order as the third argument. So I can know which event will be triggered the last.
There must be another solution (like registering all the events I want to monitor in some member array and check if they have been executed in each callback). For the moment setting hardcoded events priority without checking callbacks execution is OK (for only 2 events).
The field is populated during creation and not after but anyway, it works :).
public function buildForm(FormBuilderInterface $builder, array $options) {
$formFactory = $builder->getFormFactory();
$builder
->add('requestedDate', 'date', array(
'widget' => 'single_text',
'input' => 'datetime',
'format' => 'dd/MM/yyyy',
'attr' => array('class' => 'date'),
'data' => null,
'mapped' => false
))
->add('selectedGroup', 'entity', array(
'class' => 'MyBundle\Entity\Group',
'placeholder' => '',
'mapped' => false,
'multiple' => false,
))
->add('search', 'submit');
// Here we could use a register method which would contain listener informations
$builder->get('requestedDate')
->addEventListener(FormEvents::SUBMIT,
$this->onDateCriteriaEvent($formFactory),
0); //Will be triggered first
$builder->get('selectedGroup')
->addEventListener(FormEvents::SUBMIT,
$this->onGroupCriteriaEvent($formFactory),
1); //Will be triggered after 1st
}
private function onDateCriteriaEvent(FormFactory $ff) {
return
function(FormEvent $event) use ($ff) {
$root = $event->getForm()->getParent();
$qb = $this->getQueryBuilder();
if(null !== $event->getData()) {
$requestedDate = $event->getData();
$qb->andWhere('r.requestedDate = :requestedDate')
->setParameter('requestedDate', $requestedDate);
// Here we could check for registered events not already raised
// and do the appropriate action (if it is the last or something else...)
}
};
}
private function onGroupCriteriaEvent(FormFactory $ff) {
return
function(FormEvent $event) use ($ff) {
$qb = $this->getQueryBuilder();
$root = $event->getForm()->getRoot();
if(null !== $event->getData()) {
// Check for events not already raised....
$selectedGroup = $event->getData();
$qb->andWhere('r.group = :group')
->setParameter('group', $selectedGroup);
}
$travels = $qb->getQuery()->getResult();
if($this->searchHasResult($root) {
// We know this event is the last raised so we can add 'travels' field
$this->addTravels($ff, $root);
}
};
}
private function addTravels(FormFactory $ff, Form $rootForm) {
$travels = $ff->createNamedBuilder('travels', 'entity', null,
array(
'class' => 'MyBundle\Entity\Travel',
'mapped' => false,
'multiple' => true,
'expanded' => true,
'auto_initialize' => false
));
$submitButton = $ff->createNamedBuilder('validate', 'submit');
$travels->addEventListener(FormEvents::PRE_SUBMIT, $this->onSearchResult());
$form->add($travels->getForm())->add($submitButton->getForm());
}

symfony2 lost form parameter with filter

I need do a dropdown menu with a filter, but if I add a query builder, the request ignore the value posted.
In my form type the diference is this.
->add('jugador', 'entity', array(
'class' => 'MSKLigaBundle:Jugadores',
'label' => 'Jugador',
'query_builder' => function( \Doctrine\ORM\EntityRepository $er) use($equipoId){
return $er->createQueryBuilder('j')
->where('j.equipo_id = :equipo')
->setParameter('equipo', $equipoId);
},
'property' => 'nombreCompleto',
'empty_value' => "el jugador...",
'required' => true))
and the other wat, with out filter...
->add('jugador', 'entity', array(
'class' => 'MSKLigaBundle:Jugadores',
'label' => 'Jugador',
'empty_value' => "Seleccionar Jugador",
'property' => 'nombreCompleto'))
thanks for any help.
Edit: this is the controller that processes the data
$em = $this->getDoctrine()->getManager();
$request = $this->getRequest();
if($request->getMethod() == 'GET')
{
$equipoId = $request->query->get('equipo');
$equipo = $em->getRepository('MSKLigaBundle:Equipos')->find($equipoId);
$pena = new Penas();
if($equipo != null)
$pena->setEquipo( $equipo );
else
return $this->redirect($this->generateUrl( 'admin_penas'));
$penasForm = new PenasType();
$penasForm->setEquipo($equipoId);
$form = $this->createForm( $penasForm, $pena);
}
else if($request->getMethod() == 'POST')
{
$pena = new Penas();
$form = $this->createForm( new PenasType(), $pena);
$form->bind($request);
$pena->setPartidosPendientes( $pena->getPartidosTotales() );
if( $form->isValid() ){
$em->persist($pena);
$em->flush();
}
}
return $this->render( 'MSKLigaBundle:Penas:new.html.twig', array('form' => $form->createView() ) );

Symfony2: Edit user without having password

In my application, only the admin user can create and, theoretically, edit users. So far, using only the Symfony security system (no FOSUserBundle management - its complexity is not required), creating users with varying roles is just fine. The challenge that totally escapes me is how to edit a user without knowing the user's password. I keep running into the expected validation error
Password cannot be empty
. How can editing be accomplished? I'm surely missing something very fundamental here.
Edit action:
public function editAction($id) {
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ManaClientBundle:User')->find($id);
$form = $this->createForm(new UserType(), $user);
return array(
'form' => $form->createView(),
'user' => $user,
'title' => 'Edit user',
);
}
Update action:
public function updateAction(Request $request, $id) {
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ManaClientBundle:User')->find($id);
$originalPassword = $user->getPassword();
$form = $this->createForm(new UserType(), $user);
$form->bind($request);
if ($form->isValid()) {
$plainPassword = $form->get('password')->getData();
if (!empty($plainPassword)) {
//encode the password
$encoder = $this->container->get('security.encoder_factory')->getEncoder($entity); //get encoder for hashing pwd later
$tempPassword = $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
$user->setPassword($tempPassword);
}
else {
$user->setPassword($originalPassword);
}
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('user_main', array()));
}
User form:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('enabled', 'choice', array(
'choices' => array('Yes' => 'Yes', 'No' => 'No'),
'expanded' => true,
'multiple' => false,
'label' => 'Enabled: ',
))
->add('fname')
->add('sname')
->add('email')
->add('username')
->add('password', 'repeated', array(
'type' => 'password',
'invalid_message' => 'Password fields do not match',
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
))
->add('role', 'choice', array(
'choices' => array('ROLE_USER' => 'User', 'ROLE_ADMIN' => 'Admin'),
'expanded' => true,
'multiple' => false,
'label' => 'Group: ',
))
;
}
Until I see a more elegant solution, here's what I came up with:
Create a UserEditType form class with all fields but the password field(s)
Assign UserEditType to a validation group other than Default
Configure the password length constraint to the validation group in 2.
Modify the edit and update actions to use UserEditType
And now users can be edited without having the password!
UserEditType:
class UserEditType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('enabled', 'choice', array(
'choices' => array('Yes' => 'Yes', 'No' => 'No'),
'expanded' => true,
'multiple' => false,
'label' => 'Enabled: ',
))
->add('fname')
->add('sname')
->add('email')
->add('username')
->add('role', 'choice', array(
'choices' => array('ROLE_USER' => 'User', 'ROLE_ADMIN' => 'Admin'),
'expanded' => true,
'multiple' => false,
'label' => 'Group: ',
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Mana\ClientBundle\Entity\User',
'validation_groups' => array('edit'),
));
}
Password in User entity:
* #ORM\Column(name="userpass", type="string", length=100, nullable=false)
* #Assert\NotBlank(message="Password may not be empty")
* #Assert\Length(
* min = "5",
* max = "12",
* minMessage = "Password must be at least 5 characters long",
* maxMessage = "Password cannot be longer than than 12 characters",
* groups = {"Default"}
* )
Update action:
public function updateAction(Request $request, $id) {
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('ManaClientBundle:User')->find($id);
$form = $this->createForm(new UserEditType(), $user);
$form->bind($request);
if ($form->isValid()) {
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('user_main', array()));
}
return array(
'form' => $form->createView(),
'user' => $user,
'title' => 'Edit user',
);
}
I've had the same problem here in my project.
I solved it by removing the password field from the form just for my edit action.
So, in my UserController, I changed the editAction:
//find the line where the form is created
$editForm = $this->createForm(new UserType($this->container), $entity)
->remove('password'); //add this to remove the password field
I do something like this (untested code)
My User entity has a password property mapped to DB
It also has a 'plainPassword' property, that is not mapped
class User {
// mapped
private string $username;
// mapped
private string $password;
// not mapped - simple php property
private string $plainPassword;
// getters/setters
...
}
The form, uses the plainPassword property, not the mapped password.
class UserType extends AbstractType {
...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class)
->add('plainPassword', PasswordType::class, ['required' => false])
}
...
}
And then somewhere, controller in this example, we check if the plainPassword is not empty - thus the password is trying to be changed.
public function updateUserAction(User $user, Request $request)
{
$form = $this->formFactory->createForm(UserType::class, $user);
if($request->getMethod() === 'POST') {
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
if(0 !== strlen($user->getPlainPassword()) {
$encoder = $this->encoderFactory->getPasswordHasher($user);
$salt = rtrim(str_replace('+', '.', base64_encode(random_bytes(32))), '=');
$user->setSalt($salt);
$hashedPassword = $encoder->hash($user->getPlainPassword(), $user->getSalt());
$user->setPassword($hashedPassword);
$user->setPlainPassword(null);
}
$this->em->persist($user);
$this->em->flush();
return $this->redirectToRoute('something');
}
}
}
If you want to use the remove() function then apply also at the form setting. At least in Symfony 3.3. In this way you will avoid the password confirmation stated by #pusle above:
$form = $this->formFactory->createForm()->remove("current_password");
$form->setData($user)->remove("current_password");
Here the whole method in the ProfileController of the FOSUserBundle. It works for me:
public function editDiffAction($id, Request $request)
{
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->findUserBy(['id' => $id]);
$event = new GetResponseUserEvent($user, $request);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
$form = $this->formFactory->createForm()->remove("current_password");
$form->setData($user)->remove("current_password");
$form->handleRequest($request);
if ($form->isValid()) {
$event = new FormEvent($form, $request);
$userManager = $this->get('fos_user.user_manager');
$userManager->updateUser($user);
$url = $this->generateUrl('fos_user_profile_show_diff', array('id' => $user->getId() ));
$response = new RedirectResponse($url);
return $response;
}
return $this->render('#FOSUser/Profile/edit_diff.html.twig', array(
'form' => $form->createView(),
'user_id' => $user->getId(),
));
}
Just add 'disabled' => 'disabled' and this field won't be taken into account.

Symfony2 Extra fields FormError on validating form with hidden field

Here is listing of my form:
$builder = $this->createFormBuilder($project)
->add('name','text')
->add('type','choice', array(
'choices' => $enumtype
))
->add('begindate','date')
->add('expecteddate','date')
->add('events', 'collection', array(
'type' => new EventType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
))
->add('financial', 'file', array(
'property_path' => false,
'required' => false
))
->add('investition', 'file', array(
'property_path' => false,
'required' => false
));
if ($defaults) {
$builder->add('id','hidden',array('data' => $defaults['id'], 'property_path' => false));
$form = $builder->getForm();
$form->setData($defaults);
}
else
$form = $builder->getForm();
When i try to validate this form, i receive FormError object:
Array (
[0] => Symfony\Component\Form\FormError Object (
[messageTemplate:protected] => This form should not contain extra fields.
[messageParameters:protected] => Array (
[{{ extra_fields }}] => id
)
[messagePluralization:protected] =>
)
)
If i exclude "id" field - all works ok.
How can i use hidden type and make validation?
This issue comes from the fact that the hidden parameter is optionnal.
A common mistake is not to set the associated type when submitting the form.
Example of mistake:
public function addOrEditAction($id=null)
{
$request = $this->getRequest();
if (!$id) {
$model = new Actu();
$type = new ActuType(); /* I do not set the default id on submit */
} else {
$em = $this->getDoctrine()->getEntityManager();
$model = $em->getRepository("MyBundle:Actu")
->find($id);
if (!$model) {
return $this->redirect($this->generateUrl('admAddNew'));
} else {
$type = new ActuType($model->getId());
}
}
$form = $this->createForm($type,$model);
if ('POST' == $request->getMethod()) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($model);
$em->flush();
return $this->redirect($this->generateUrl('admNews'));
}
}
$data = array('form'=>$form->createView());
return $this->render('MyBundle:Page:news-add.html.twig',$data);
}
When calling the controller
ActuType() contains:
'name', 'content', 'date', 'id'
When Submitting the form
ActuType() contains:
'name', 'content', 'date'
They do not match.
This actually returns an error because there's an extra field with hidden id containing the row to edit when submitting the form.
All you need to do is to check the request before initializing FormType
if (!$id && null === $id = $request->request->get('newsType[id]',null,true)) {
With this, you can set the same FormType that you did when asking for the page

Categories