reverseTransform in DataTransformer doesn't work - php

I created a custom form field type "duration", and 2 fields "hour" and "minutes"
class DurationType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('hours', new DurationSmallType(), [])
->add('minutes', new DurationSmallType(), [])
;
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
}
public function getName()
{
return 'duration';
}
}
DurationSmallType:
class DurationSmallType extends AbstractType
{
public function getName()
{
return 'duration_small';
}
}
Template for both types:
{% block duration_small_widget -%}
<div class="input-group" style="display: inline-block;width:100px;height: 34px;margin-right: 20px;">
<input type="text" {{ block('widget_attributes') }} class="form-control" style="width:50px;" {% if value is not empty %}value="{{ value }}" {% endif %}>
<span class="input-group-addon" style="height: 34px;"></span>
</div>
{%- endblock duration_small_widget %}
{% block duration_widget -%}
{{ form_widget(form.hours) }}
{{ form_widget(form.minutes) }}
{%- endblock duration_widget %}
In Entity duration saved in minutes (as integer) and I create a DataTransformer in form builder:
class EventType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$dataTransformer = new DurationToMinutesTransformer();
$builder
->add('name', NULL, array('label' => 'Название'))
->add('type', NULL, array('label' => 'Раздел'))
->add($builder
->create('duration', new DurationType(), array('label' => 'Продолжительность'))
->addModelTransformer($dataTransformer)
)
->add('enabled', NULL, array('label' => 'Включено'));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TourConstructor\MainBundle\Entity\Event',
'csrf_protection' => true,
'csrf_field_name' => '_token',
'intention' => 'events_token'
));
}
/**
* #return string
*/
public function getName()
{
return 'mybundle_event';
}
}
DurationToMinutesTransformer:
class DurationToMinutesTransformer implements DataTransformerInterface
{
public function transform($value)
{
if (!$value) {
return null;
}
$hours = ceil($value / 60);
$minutes = $value % 60;
return [
"hours" => $hours,
"minutes" => $minutes
];
}
public function reverseTransform($value)
{
if (!$value) {
return null;
}
return $value["hours"]*60 + $value["minutes"];
}
}
Transform - work, I have hours and minutes in edit field, but reverseTransform doesn't work, after submit I have duration field as Array.
Symfony error:
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[duration].children[hours] = 3
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Compound forms expect an array or NULL on submission.
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[duration].children[minutes] = 0
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Compound forms expect an array or NULL on submission.
Please, help me.

I find error, DurationSmallType need option compound=false, default is true and symfony try to use my 2 field as inside form.
And I remove modelTransformer from entity form and place it in DurationType.
Finaly code of my forms builders:
EventType:
class EventType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$dataTransformer = new DurationToMinutesTransformer();
$builder
->add('name', NULL, array('label' => 'Название'))
->add('type', NULL, array('label' => 'Раздел'))
->add($builder
->create('duration', new DurationType(), array('label' => 'Продолжительность'))
->addModelTransformer($dataTransformer)
)
->add('enabled', NULL, array('label' => 'Включено'));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TourConstructor\MainBundle\Entity\Event',
'csrf_protection' => true,
'csrf_field_name' => '_token',
'intention' => 'events_token'
));
}
/**
* #return string
*/
public function getName()
{
return 'mybundle_event';
}
}
DurationType:
class DurationType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'html5' => true,
'error_bubbling' => false,
'compound' => true,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('hours', new DurationSmallType(), [
"label"=>"ч."
])
->add('minutes', new DurationSmallType(), [
"label"=>"мин."
])
->addViewTransformer(new DurationToMinutesTransformer())
;
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
}
public function getName()
{
return 'duration';
}
}
DurationSmallType:
class DurationSmallType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'compound' => false,
));
}
public function getName()
{
return 'duration_small';
}
}

Related

Symfony FormType construct error

My application show this error
Type error: Too few arguments to function AppBundle\Form\ActualiteType::__construct(), 0 passed in /Applications/MAMP/htdocs/SyndicNous/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php on line 90 and exactly 2 expected
My formType
class ActualiteType extends AbstractType
{
/**
* #var bool $admin
*/
private $admin;
/**
* #var User $user
*/
private $user;
/**
* ActualiteType constructor.
* #param bool|false $admin
*/
public function __construct($admin = false, $user)
{
$this->admin = $admin;
$this->user = $user;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$categories = array(
'Travaux' => 'Travaux',
'Voisinage' => 'Voisinage',
);
$builder
->add('title')
->add('category')
->add('content')
->add('datePublish')
->add('category', ChoiceType::class, array(
'choices' => $categories
)
);
if ($this->user->getResidence() != null) {
$builder->add('residence', EntityType::class, array(
'class' => 'AppBundle:Residence',
'choices' => $this->user->getResidence(),
));
} else {
$builder->add('residence', 'entity', array(
'class' => 'AppBundle:Residence',
'choice_label' => 'name'
));
};
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Actualite'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_actualite';
}
}
Do you have any idea where the problem would come from? Thank you
I do not understand what you're trying to do. You do not need to use the constructor to pass parameters to your formType. There is the second parameter of the buildForm method for this ($options).
In your controller, create your form like this :
$form = $this->createForm(ActualiteType::class, $actualite, [
'admin' => $admin,
'user' => $user
]);
And modify your formType like that :
class ActualiteType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$admin = $options['admin']; // Not used ?
$user = $options['user'];
$categories = array(
'Travaux' => 'Travaux',
'Voisinage' => 'Voisinage',
);
$builder->add('title')
->add('category')
->add('content')
->add('datePublish')
->add('category', ChoiceType::class, array(
'choices' => $categories
)
);
if ($user->getResidence() != null) {
$builder->add('residence', EntityType::class, array(
'class' => 'AppBundle:Residence',
'choices' => $user->getResidence(),
));
} else {
$builder->add('residence', 'entity', array(
'class' => 'AppBundle:Residence',
'choice_label' => 'name'
));
};
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Actualite',
'admin' => null, // Default
'user' => null // Default
));
}
}
Do not forget to put the default values of your options in configureOptions method.

Symfony buildForm builder setName

I'm working on a Symfony project, so here's is my problem:
I have got an Entity and for that Entity I have got two forms in the same page(one for insert and the other to update the inserted).
So I made that.
ActionMethod
public function adminTareasAction(Request $request) {
$newTareaForm = $this->createForm(TareaType::class, null, array("formType" => "newTarea"));
$editTareasForm = $this->createForm(TareaType::class, null, array("formType" => "editTareas"));
if($request->isMethod("POST")) {
if(!is_null($request->request->get('newTarea'))) {
$newTareaForm->handleRequest($request);
if($newTareaForm->isSubmitted() && $newTareaForm->isValid()) {
$newTarea = $newTareaForm->getData();
$dataManager = $this->getDoctrine()->getManager();
$dataManager->persist($newTarea);
$dataManager->flush();
return $this->redirectToRoute("admin_tareas");
}
}
elseif(!is_null($request->request->get('editTareas'))) {
$editTareasForm->handleRequest($request);
if($editTareasForm->isSubmitted() && $editTareasForm->isValid()) {
$newTarea = $newTareaForm->getData();
$dataManager = $this->getDoctrine()->getManager();
$dataManager->persist($newTarea);
$dataManager->flush();
return $this->redirectToRoute("admin_tareas");
}
}
}
$tareas = $this->getDoctrine()->getRepository('FabricacionBundle:Tarea')->findAll();
if(!$tareas) {
$tareas = "No hay Tareas";
}
return $this->render('UsersBundle:Admin:adminTareas.html.twig', array("newTareaForm" => $newTareaForm->createView(), "editTareasForm" => $editTareasForm->createView(), "tareas" => $tareas));
}
Type Class
class TareaType extends AbstractType {
private $formType;
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->formType = $options["formType"];
if($this->formType == "newTarea") {
//var_dump();
$builder
->add('tareaName', TextType::class)
->add('tareaOrden', IntegerType::class)
->add('submitNewTarea', SubmitType::class);
}
elseif($this->formType == "editTareas") {
$builder
->add('newName', CollectionType::class, array("entry_type" => TextType::class, "allow_add" => true))
->add('newOrden', CollectionType::class, array("entry_type" => IntegerType::class, "allow_add" => true))
->add('deleteTarea', CollectionType::class, array("entry_type" => CheckboxType::class, "allow_add" => true))
->add('submitTarea', SubmitType::class);
}
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'FabricacionBundle\Entity\Tarea',
"formType" => null
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix() {
/*if($this->formType == "newTarea") {
return $this->formType;
}
elseif($this->formType == "editTareas") {
return $this->formType;
}*/
return 'FabricacionBundle_tarea';
}
}
The only thing I need is to change the form name and then handle them in the controller by name.
Just remove getBlockPrefix().You are accessing you're field type by its FQCN :
$newTareaForm = $this->createForm(TareaType::class, null, array("formType" => "newTarea"));
so you don't need to have an alias for it (wich is deprecated).
Well, reading the Form classes I get what I have to do.
Just this to create the form:
$newTareaForm = $this->container->get('form.factory')->createNamedBuilder("newTarea", TareaType::class, null, array("formType" => "newTarea"))->getForm();
$editTareasForm = $this->container->get('form.factory')->createNamedBuilder("editTareas", TareaType::class, null, array("formType" => "editTareas"))->getForm();

Symfony2.3, Relational entity in 1 form

I am trying to save entities of 2 classes in 1 form I have read this article about it. My code is :
class MeetingType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('meetingDay', 'text', array('label' => 'Dzień zbiórki'))
->add('meetingTime', 'text', array('label' => 'Godzina zbiórki'))
->add('congregation', 'entity', array(
'class' => 'ViszmanCongregationBundle:Congregation',
'property' => 'name', 'label' => 'Zbór'
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Viszman\CongregationBundle\Entity\Meeting'
));
}
/**
* #return string
*/
public function getName()
{
return 'viszman_congregationbundle_meeting';
}
}
And another TYPE:
class CongregationType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$plDays = array('day1', 'day1', 'day1', 'day1', 'day1', 'day1', 'day1');
$builder
->add('name', 'text', array('label' => 'Name'))
->add('meetingDay', 'choice', array('label' => 'meeting day', 'choices' => $plDays))
->add('meetings','collection', array('type' => new MeetingType(), 'allow_add' => true))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Viszman\CongregationBundle\Entity\Congregation'
));
}
/**
* #return string
*/
public function getName()
{
return 'viszman_congregationbundle_congregation';
}
}
And when I try do render this form I only get CongregationType and no MeetingType form. part of code responsible for rendering form is:
<h1>Congregation creation</h1>
{{ form_start(form) }}
{{ form_widget(form) }}
<h3>Meetings</h3>
<ul class="tags" data-prototype="{{ form_widget(form.meetings.vars.prototype)|e }}">
{{ form_widget(form.meetings) }}
</ul>
{{ form_end(form) }}
If I remember correctly the entity form field is used to reference an existing entity, not to create a new one.
This is not what you need, what you need is embedded forms, take a look at the symfony book.

Symfony2 pass option to abstract FormType

I've been stuck on this for quite some time now. I read quite some answers here on SO but I couldn't get it running.
My controller contains
$series = $this->getDoctrine()->getRepository('DemoDemoBundle:Series')->createQueryBuilder('series')
->where('series.type = 1')
->getQuery()->getResult();
$magazine = new Magazine();
$content = array(
'series' => $series
);
$form = $this->createForm(new MagazineType($this->get('translator'), $content), $magazine);
$form->handleRequest($request);
I would like to pass the variable $content to the form so that 'series' can be used in some selectboxes. MagazineType extends PublicationType. The same goes for the underlying entities. Here's how MagazineType looks:
class MagazineType extends PublicationType
{
function __construct($translator, array $series = null)
{
parent::__construct($translator, $series); //$series);
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// TODO: Refactor constant out
parent::buildForm($builder, $options)
// ->add('dateSubmitted')
// ->add('userSubmitted')
// ->add('file')
->add('issueNumber')
->add('save', 'submit');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Demo\DemoBundle\Entity\Magazine',
'series' => $this->translator->trans('addPublication.noSeries')
)
);
}
and PublicationType
class PublicationType extends AbstractType
{
protected $series, $translator;
public function __construct($translator, $series)
{
$this->translator = $translator;
$this->series = $series;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('name', 'text', array(
'label' => $this->translator->trans('addPublication.name')
))
// ->add('dateSubmitted')
// ->add('userSubmitted')
// ->add('file')
->add('series', 'choice', $this->series);//$formOptions['series']);
;
return $builder;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Demo\DemoBundle\Entity\Publication',
'series' => $this->translator->trans('addPublication.noSeries')
));
}
Maybe I'm accessing the series variable the wrong way, but I'm not even getting any further than constructing MagazineType (option series not allowed) even though I've set it in DefaultOptions as stated on http://symfony.com/doc/current/components/options_resolver.html#configure-allowed-types. I'm probably missing something here though similar questions, since I'm stuck on this the entire day.
Thx for any help!

Symfony 2 Embedded Form Collection Many to Many

I have 2 Entities - User and Group. They have a many-to-many relationship and Group is used to store a users' roles.
I'm trying to make a User edit form by adding a collection, I want to be able to add a new role by selecting it from a dropdown (limited to what's already in the DB)
UserType.php:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('email')
->add('forename')
->add('surname')
->add('isActive')
->add('joinDate', 'date', array('input' => 'datetime', 'format' => 'dd-MM-yyyy'))
->add('lastActive', 'date', array('input' => 'datetime', 'format' => 'dd-MM-yyyy'))
->add('groups', 'collection', array(
'type' => new GroupType(),
'allow_add' => true,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Sfox\CoreBundle\Entity\User'
));
}
}
and GroupType.php:
class GroupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('role');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
"data_class" => 'Sfox\CoreBundle\Entity\Group'
));
}
}
This displays the roles in the form in basic text boxes, but if I add an entry to the form, it will cascade persist a new entry into Groups and if I were to edit an entry, it would change the underlying Group data.
I tried making a GroupSelectType.php:
class GroupSelectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('role', 'entity', array('class'=>'SfoxCoreBundle:Group', 'property'=>'name'));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
"data_class" => 'Sfox\CoreBundle\Entity\Group'
));
}
}
Adding the field as an "entity" type, this displays the correct select box (but with the default values) I cant seem to bind it to the UserType form!
All I want the form to do is modify the underlying 'groups' ArrayCollection in the User entity.
Does anyone know how I can achieve this?
Well I worked out a solution for anyone else struggling with similar problems...
I had to create a custom form type and declare it as a service so I could pass in the Entity Manager. I then needed to make a dataTransformer to change my group objects into an integer for the form
Custom GroupSelectType:
class GroupSelectType extends AbstractType
{
/**
* #var ObjectManager
*/
private $om;
private $choices;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
// Build our choices array from the database
$groups = $om->getRepository('SfoxCoreBundle:Group')->findAll();
foreach ($groups as $group)
{
// choices[key] = label
$this->choices[$group->getId()] = $group->getName() . " [". $group->getRole() ."]";
}
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new GroupToNumberTransformer($this->om);
$builder->addModelTransformer($transformer);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
"choices" => $this->choices,
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'group_select';
}
}
In the constructor I'm getting all available groups and putting them into a "choices" array which is passed to the select box as an option.
You'll also notice I'm using a custom data transformer, this is to change the groupId (which is used in the rendering of the form) to a Group entity. I made the GroupSelectType a service as well and passed in the [#doctrine.orm.entity_manager]
services.yml (bundle config):
services:
sfox_core.type.group_select:
class: Sfox\CoreBundle\Form\Type\GroupSelectType
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: form.type, alias: group_select }
GroupToNumberTranformer.php
class GroupToNumberTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* Transforms an object (group) to a string (number).
*
* #param Group|null $group
* #return string
*/
public function transform($group)
{
if (null === $group) {
return "";
}
return $group->getId();
}
/**
* Transforms a string (number) to an object (group).
*
* #param string $number
* #return Group|null
* #throws TransformationFailedException if object (group) is not found.
*/
public function reverseTransform($number)
{
if (!$number) {
return null;
}
$group = $this->om
->getRepository('SfoxCoreBundle:Group')
->findOneBy(array('id' => $number))
;
if (null === $group) {
throw new TransformationFailedException(sprintf(
'Group with ID "%s" does not exist!',
$number
));
}
return $group;
}
}
And my modified UserType.php - Notice I'm using my custom form type "group_select" now as it's running as a service:
class UserType extends AbstractType
{
private $entityManager;
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new GroupToNumberTransformer($this->entityManager);
$builder
->add('username')
->add('email')
->add('forename')
->add('surname')
->add('isActive')
->add('joinDate', 'date', array('input' => 'datetime', 'format' => 'dd-MM-yyyy'))
->add('lastActive', 'date', array('input' => 'datetime', 'format' => 'dd-MM-yyyy'));
$builder
->add(
$builder->create('groups', 'collection', array(
'type' => 'group_select',
'allow_add' => true,
'options' => array(
'multiple' => false,
'expanded' => false,
)
))
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Sfox\CoreBundle\Entity\User'
));
}
public function getName()
{
return 'sfox_corebundle_usertype';
}
}

Categories