Processing of non-existent collection fields in SonataAdminBundle forms - php

I have an entity class ContactsPage to store some information about emails, phones etc. The problem is to get all this information into one field "contacts" in json-format defined within the ContactsPage entity:
class ContactsPage
{
...
/**
* #var string
*
* #ORM\Column(name="contacts", type="text", nullable=true)
*/
private $contacts;
...
}
ContactsPageAdmin form constructing example for emails:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('emails', 'collection',
array(
'mapped' => false,
'required' => false,
'type' => 'text',
'allow_add' => true,
'allow_delete' => true,
)
);
}
How and where can I get this "emails" array (or collection)?
Where can I handle this array to make json and push it into "contacts" field before saving the ContactsPage entity?
Where and how I can handle "contacts" field and transfer all decoded from json information into editing form (into "emails" collection)?
Thanks.

Spent almost the day but found answers by myself.
First and second questions.
I have to define prePersist and preUpdate methods within the admin-class with my Entity as an argument, where i can get and handle "emails" array:
class ContactsPageAdmin extends Admin
{
...
public function prePersist($contactsPage)
{
$emails = $this->getForm()->get('emails')->getData();
$contactsPage->setContacts(json_encode($emails));
}
public function Update($contactsPage)
{
$emails = $this->getForm()->get('emails')->getData();
$contactsPage->setContacts(json_encode($emails));
}
...
}
What about third question. I just have to use $this->getSubject() to get original Entity and 'data' property to load needed data:
class ContactsPageAdmin extends Admin
{
...
protected function configureFormFields(FormMapper $formMapper)
$entity = $this->getSubject(); // getting original Entity
$json = $entity->getContacts(); // field with json data
$array = json_decode($json, true);
// preparing data to fulfill necessary form fields
$array['emails'] = is_array($array['emails']) ? $array['emails'] : array();
$formMapper
->add('emails', 'collection',
array(
'mapped' => false,
'required' => false,
'type' => 'text',
'allow_add' => true,
'allow_delete' => true,
'data' => $array['emails'] // embeding prepared data into collection
)
);
}
...
}

Related

Symfony form - Choicetype - Error "Array to string covnersion"

I'm running Symfony 3.4 with FosUserBundle.
I customized the registration form in order to get the Roles included :
<?php
$form->add('roles', ChoiceType::class, array(
'choices' => array(
'user' => 'ROLE_USER',
'admin' => 'ROLE_ADMIN'
),
'label' => 'Role :',
'expanded' => true,
'multiple' => true
));
Then roles are now selectable in the registration form through 2 new checkbox... but I'd like to display it with a <select> tag. When I set the expanded option to false I get a select, but if I set the multiple option to FALSE I get an error :
Array to string conversion
Any idea to fix my issue ?
EDIT : below is the stack trace :
Symfony\Component\Debug\Exception\ContextErrorException:
Notice: Array to string conversion
at vendor\symfony\symfony\src\Symfony\Component\Form\ChoiceList\ArrayChoiceList.php:73
at Symfony\Component\Form\ChoiceList\ArrayChoiceList->Symfony\Component\Form\ChoiceList\{closure}(array('ROLE_USER'))
at call_user_func(object(Closure), array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\ChoiceList\ArrayChoiceList.php:158)
at Symfony\Component\Form\ChoiceList\ArrayChoiceList->getValuesForChoices(array(array('ROLE_USER')))
(vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer.php:32)
at Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer->transform(array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:1104)
at Symfony\Component\Form\Form->normToView(array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:350)
at Symfony\Component\Form\Form->setData(array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper.php:49)
at Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper->mapDataToForms(object(User), object(RecursiveIteratorIterator))
(vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:383)
at Symfony\Component\Form\Form->setData(object(User))
(vendor\friendsofsymfony\user-bundle\Controller\RegistrationController.php:70)
at FOS\UserBundle\Controller\RegistrationController->registerAction(object(Request))
at call_user_func_array(array(object(RegistrationController), 'registerAction'), array(object(Request)))
(var\cache\dev\classes.php:4659)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(var\cache\dev\classes.php:4614)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Kernel.php:200)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(web\app_dev.php:29)
to solve your problem you need a data transformer, because the roles field is actually an array :
add the data transformer to your FormType class along with the roles field instead of adding it in the controller:
$builder->add('roles', ChoiceType::class, array(
'choices' => array(
'user' => 'ROLE_USER',
'admin' => 'ROLE_ADMIN'
),
'label' => 'Role :'
));
//roles field data transformer
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesArray) {
// transform the array to a string
return count($rolesArray)? $rolesArray[0]: null;
},
function ($rolesString) {
// transform the string back to an array
return [$rolesString];
}
));
2020 - Symfony 5.0.8:
in your FormType file:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('roles', ChoiceType::class, [
'required' => true,
'multiple' => false,
'expanded' => false,
'choices' => [
'User' => 'ROLE_USER',
'Admin' => 'ROLE_ADMIN',
],
]);
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesArray) {
// transform the array to a string
return count($rolesArray)? $rolesArray[0]: null;
},
function ($rolesString) {
// transform the string back to an array
return [$rolesString];
}
));
}
What is happening is normal : Your property roles is AN ARRAY of string. In your entity you got something like :
/**
* #var array
*/
private $roles = [];
And logically, when you put the option multiple => 'true', Symfony form will treat it like an array and convert each element of your array in string (which they already are).
On the other hand, if you put multiple => false, it will consider the array as a single choice which it is not.
Simply put, you have an inconsistency between your model where your roles property is an array of string, and the form you are trying to create, which considers roles as a single value.
In your case, You have to put multiple => true.

Multiple ChoiceType, array expected

I am trying to make a multiple-select ChoiceType in my Symfony form. However, I am getting the following error:
Unable to transform value for property path "[organisations]":
Expected an array.
Note: If I change the name of the component from organisations to anything else, it's rendering the form correctly.
As far as I can see, organisations is an array:
/**
* #var Organisation[]
* #ORM\ManyToMany(targetEntity="Booking\Entity\Organisation", inversedBy="users")
* #ORM\JoinTable(name="users_organisations")
*/
protected $organisations;
Here's the form:
$organisations = $doctrine->getRepository('Booking\Entity\Organisation')->findAll();
$builder
->add('organisations', ChoiceType::class, array(
'choices' => $organisations,
'choice_label' => function($organisation, $key, $index) {
return $organisation->getName();
},
'multiple' => true
))
What am I doing wrong?
Use EntityType instead.
$builder
->add('organisations', EntityType::class, array(
'choices' => $organisations,
'choice_label' => function($organisation, $key, $index) {
return $organisation->getName();
},
'multiple' => true
))
I posted this just because people (like me) don't usually read all the comments. Merit goes to Jakub.

How to set the last assigned value when it is in an intermediate table

I have a form with this field:
class OrdersType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Others form fields
if ($options['curr_action'] !== NULL)
{
$builder
->add('status', 'entity', array(
'class' => 'CommonBundle:OrderStatus',
'property' => 'name',
'required' => TRUE,
'label' => FALSE,
'mapped' => FALSE
));
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'register_type'
));
$resolver->setOptional(array(
'curr_action'
));
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\Orders',
'render_fieldset' => FALSE,
'show_legend' => FALSE,
'intention' => 'orders_form'
));
}
public function getName()
{
return 'orders';
}
}
And have this entity:
class OrderHasStatus {
use IdentifiedAutogeneratedEntityTrait;
/**
* Hook timestampable behavior
* updates createdAt, updatedAt fields
*/
use TimestampableEntity;
/**
* #ORM\ManyToOne(targetEntity="\Tanane\FrontendBundle\Entity\Orders")
* #ORM\JoinColumn(name="general_orders_id", referencedColumnName="id")
*/
protected $order;
/**
* #ORM\ManyToOne(targetEntity="\Tanane\CommonBundle\Entity\OrderStatus")
* #ORM\JoinColumn(name="status_id", referencedColumnName="id")
*/
protected $status;
public function setOrder(\Tanane\FrontendBundle\Entity\Orders $order)
{
$this->order = $order;
}
public function getOrder()
{
return $this->order;
}
public function setStatus(\Tanane\CommonBundle\Entity\OrderStatus $status)
{
$this->status = $status;
}
public function getStatus()
{
return $this->status;
}
}
Where I store the latest status asigned to the order. I need to set that status on the form when I'm editing or showing the order but don't know how to achieve this, can any give me some help?
So, this looks related to your other question concerning n:m relationships. Your existing solution might work if you had a Many-to-Many relationship, but won't with three Entities.
I think the solution is similar:
OrdersType
$builder->add('orderHasStatus', 'collection', array(
'type' => new OrderHasStatusType(),
'allow_add' => true,
'allow_delete' => true
));
OrderHasStatusType
$builder->add('status', 'collection', array(
'type' => new StatusType(),
'allow_add' => true,
'allow_delete' => true
));
StatusType
$builder->add('status', 'entity', array(
'class' => 'CommonBundle:Status'
));
As your schema stands^, your OrdersType needs to contain a Collection of OrderHasStatus types, which themselves have StatusTypes containing the statuses themselves.
^One thing I would query, and maybe should have in my other answer, is whether your schema is correct - do you need each Order to have multiple Statuses? If not, then what you really want is a Many-to-One between Order and Status, which would remove the intermediate OrderHasStatus layer. If you want the extra data about OrderHasStatus (the timestamp?) then you could put that info on the Order table. But if you need multiple Statuses, or want to keep the extra data separate from the Orders table (in which case Orders and OrderHasStatus are actually one-to-one), fine.

Set Default value of choice field Symfony FormType

I want from the user to select a type of questionnaire, so I set a select that contains questionnaires types.
Types are loaded from a an entity QuestionType .
$builder
->add('questionType', 'entity', array(
'class' => 'QuizmooQuestionnaireBundle:QuestionType',
'property' => 'questionTypeName',
'multiple' => false,
'label' => 'Question Type'))
->add('type', 'hidden')
;
What am not able to achieve is to set a default value to the resulted select.
I have googled a lot but I got only preferred_choice solution which works only with arrays
I made it by setting a type in the newAction of my Controller I will get the seted type as default value.
public function newAction($id)
{
$entity = new RankingQuestion();
//getting values form database
$em = $this->getDoctrine()->getManager();
$type = $em->getRepository('QuizmooQuestionnaireBundle:QuestionType')->findBy(array('name'=>'Ranking Question'));
$entity->setQuestionType($type); // <- default value is set here
// Now in this form the default value for the select input will be 'Ranking Question'
$form = $this->createForm(new RankingQuestionType(), $entity);
return $this->render('QuizmooQuestionnaireBundle:RankingQuestion:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
'id_questionnaire' =>$id
));
}
You can use data attribute if you have a constant default value (http://symfony.com/doc/current/reference/forms/types/form.html)
but it wont be helpful if you are using the form to edit the entity ( not to create a new one )
If you are using the entity results to create a select menu then you can use preferred_choices.
The preferred choice(s) will be rendered at the top of the list as it says on the docs and so the first will technically be the default providing you don't add an empty value.
class MyFormType extends AbstractType{
public function __construct($foo){
$this->foo = $foo;
}
$builder
->add('questionType', 'entity', array(
'class' => 'QuizmooQuestionnaireBundle:QuestionType',
'property' => 'questionTypeName',
'multiple' => false,
'label' => 'Question Type'
'data' => $this->foo))
->add('type', 'hidden')
;
}
In controller
$this->createForm(new MyFormType($foo));
The accepted answer of setting in the model beforehand is a good one. However, I had a situation where I needed a default value for a certain field of each object in a collection type. The collection has the allow_add and allow_remove options enabled, so I can't pre-instantiate the values in the collection because I don't know how many objects the client will request. So I used the empty_data option with the primary key of the desired default object, like so:
class MyChildType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('optionalField', 'entity', array(
'class' => 'MyBundle:MyEntity',
// Symfony appears to convert this ID into the entity correctly!
'empty_data' => MyEntity::DEFAULT_ID,
'required' => false,
));
}
}
class MyParentType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('children', 'collection', array(
'type' => new MyChildType(),
'allow_add' => true
'allow_delete' => true,
'prototype' => true, // client can add as many as it wants
));
}
}
Set a default value on the member variable inside your entity (QuestionType), e.g.
/**
* default the numOfCourses to 10
*
* #var integer
*/
private $numCourses = 10;

Doctrine 2: Understand how entities works with doctrine 2

I have 3 Entities: Person, Affiliation and PersonAffiliation.
In my form, I will display each affiliation as a checked checkbox.
Nowm when the user uncecks the checkbox and click submit, this affiliation should be removed from the PersonAffiliation table.
The problem is that when I submit without unchecking, my data are duplicated.
Example: aff1 and aff2. When both checked and submit, I will then get aff1 aff1 aff2 aff2.
The same, If I uncheck aff2, I will then have aff1 aff1.
The error is probably somewhere in using the doctrine:
Here is how I am managing that:
Entity Persom
#ORM\OneToMany(targetEntity="PersonAffiliation", mappedBy="person", cascade={"persist", "remove"})
protected $affiliations;
Entity Affiliation:
#ORM\OneToMany(targetEntity="PersonAffiliation", mappedBy="affiliation")
protected $person_affiliations;
Entity PersonAffiliation
#ORM\ManyToOne(targetEntity="Person", inversedBy="affiliations")
#ORM\JoinColumn(name="person_id", referencedColumnName="id")
protected $person;
#ORM\ManyToOne(targetEntity="Affiliation", inversedBy="person_affiliations")
#ORM\JoinColumn(name="affiliation_id", referencedColumnName="id")
protected $affiliation;
An idea on how to resolve that?
Thank you.
EDIT:
Cotroller part:
foreach( $enquiry->getAffiliations() as $aff )
{
$pAff = new PersonAffiliation();
$pAff->setPersonId( $person->getId() );
$pAff->setAffiliationId( $aff->getAffiliation()->getId() );
$pAff->setPerson( $person );
$pAff->setAffiliation( $aff->getAffiliation() );
$em->persist($pAff);
$em->flush();
}
Form Part:
public function buildForm(FormBuilder $builder, array $options)
{
$person = $this->person;
$user = $this->user;
$builder->add('firstname', 'text');
$builder->add('middlename', 'text', array('required'=>false));
$builder->add('lastname', 'text');
$builder->add('sex', 'choice', array( 'choices' => array('m' => 'Male', 'f' => 'Female'),
'required' => true,
'multiple' => false,
'expanded' => true));
$builder->add('email', 'text', array('required'=>false));
if( $this->addAffiliations ) {
$builder->add('affiliations', 'entity', array(
'label' => 'Athor\'s affiliations',
'class' => 'SciForumVersion2Bundle:PersonAffiliation',
'query_builder' => function($em) use ($person, $user){
return $em->createQueryBuilder('pa')
->where('pa.person_id = :pid')
->setParameter('pid', $person->getId());
},
'property' => 'affiliation',
'multiple' => true,
'expanded' => true,
));
}
}
In the Person entity :
/**
* #ORM\ManyToMany(targetEntity="Affiliation", inversedBy="people")
* #ORM\JoinTable(name="PersonAffiliation")
*/
protected $affiliations;
And in the Affiliation entity :
/**
* #ORM\ManyToMany(targetEntity="Person", mappedBy="affiliations")
*/
protected $people;
You made a $em->flush() on each iteration of the foreach. It should be done after the end of foreach isnt'it ?
Moreover, you may use a ManyToMany relation.

Categories