Custom choices on form with data_class - php

I have created a FormType that get's a data_class set in the defaults in the configureOptions class. It looks a bit like this
<?php
namespace tzfrs\AppBundle\Form\Type;
use tzfrs\AppBundle\Model\Car;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class CarType
* #package tzfrs\AppBundle\Form\Type
*/
class CarType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('specification', ChoiceType::class, [
'choices' => [1 => 1, 2 => 2, 3 => 3]
]);
}
/**
* #inheritdoc
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Car::class
]);
}
}
When I now create the form like this
$carForm = $this->createForm(CarType::class, $car);
and want to use it I get the following error:
The value of type "object" cannot be converted to a valid array key.
I think the reason for this, is because my Car class has a property specification with getters/setters and therefore returns a Specification object. However, my Specification object has a __toString() method.
Car.php
/**
* #var Specification
*/
protected $specification;
public function getSpecification(): Specification
{
return $this->specification;
}
public function setSpecification(Specification $specification): Car
{
$this->specification = $specfification;
return $this;
}
Specification.php
/**
* Returns a string implementation of the current class
*
* #return string
*/
public function __toString(): string
{
return sprintf(
'HP: %s,CCM: %s',
$this->getHorsePower(),
$this->getCapacity()
);
}
How would I achieve overriding my choices for a form field which uses a custom Model? I can't use the EntityType because it's not a Doctrine Model.
When I build my form like this
$carForm = $this->createForm(Car::class);
then it works, but I need the Car object inside the Form, because of another field I'm adding in the buildForm method

Related

Symfony3 form: View transformer error suggests using view transfomer

I wanted to create a simple HiddenEntityType, so that I can have hidden form fields that represent an entity. The simplest solution by far looked to be to employ a (view) transformer.
However I cannot see to get it to work.
Based on documentation here
...the forward transform needs to return something you can put into HTML, a string(e.g. the id of my entity) and the reverseTransform must transform an id into an entity.
With that in mind here is the simple class I made:
namespace AppBundle\Form\Type;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class HiddenEntityType extends AbstractType
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
/**
* Constructor
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$repository = $this->entityManager->getRepository($builder->getDataClass());
$builder->addViewTransformer(new CallbackTransformer(
function ($entity) use($repository) { //Forward
dump('Forward');
dump($entity);
if (!is_null($entity)) return $entity->getId();
return $entity;
},
function ($id) use($repository) { //Reverse
dump('Reverse');
dump($id);
return $repository->find($id);
}
));
}
/**
* #inheritDoc
*/
public function getParent()
{
return 'hidden';
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}
I receive the following illogical error:
An exception has been thrown during the rendering of a template ("The form's view data is expected to be an instance of class AppBundle\Entity\MyEntity, but is a(n) integer. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) integer to an instance of AppBundle\Entity\MyEntity.").
When creating the form, based on the dumps you see above, the forward transform is called twice - once with null and once with the AppBundle\Entity\MyEntity as its argument.
What might I be doing wrong?
Sy 3.3.6
PHP 7.1.8

Symfony 3 FormType - Entity through options array

I would like to know how to pass my entity correctly and another variable in my createForm function ?
Let me explain, I would like to do this:
$repo = $em->getRepository('ProjectBundle:Organisation\Organisation');
$list = $repo->children($this->getUser()->getOrganisation());
$form = $this->createForm('ProjectBundle\Form\Robot\RobotType', array($entity,$list));
I need to pass to my FormType another variable, so I use a array directly.
My FormType:
<?php
namespace ProjectBundle\Form\Robot;
use ProjectBundle\Entity\Robot\Robot;
use ProjectBundle\Form\User\UserType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RobotType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('serial_number')
->add('shell_color',ChoiceType::class,array(
'choices' => Robot::getArrayShellColor()
))
->add('tray1Color',ChoiceType::class,array(
'choices' => Robot::getTrayColor()
))
->add('tray2Color',ChoiceType::class,array(
'choices' => Robot::getTrayColor()
))
->add('tray3Color',ChoiceType::class,array(
'choices' => Robot::getTrayColor()
))
->add('hasBattery')
->add('volume')
->add('organisation',null,array('choices' => $options['data'][1]));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'ProjectBundle\Entity\Robot\Robot'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'projectbundle_robot_new';
}
}
Here I need to get my variable $list, for the organisation field, so it's $options['data'][1].
But I have a mistake, which I understand but I do not know how to correct:
The form's view data is expected to be an instance of class ProjectBundle\Entity\Robot\Robot, but is a(n) array. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) array to an instance of ProjectBundle\Entity\Robot\Robot.
This is normal because now I pass it in an array, it's my variable $options['data'][0] that contains my object now.
How to do ?
Thank you
You should pass ProjectBundle\Entity\Robot\Robot entity to the createForm method as a second parameter and an options array as a third parameter.
$form = $this->createForm('ProjectBundle\Form\Robot\RobotType', $entity, array($list));

Symfony2.8 form CollectionType Field manyTo many

In my model jai an Entity Piece and its Remplacement,the relation is defined like this:
/**
* #var string
*
* #ORM\Column(name="Reference", type="string", length=255)
*/
private $reference;
/**
* Bidirectional
*
* #ORM\ManyToMany(targetEntity="Remplacement", inversedBy="origine",cascade="all", orphanRemoval=true)
* #ORM\JoinTable(name="piece_remplace",
* joinColumns={#ORM\JoinColumn(name="id_org", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="id_gen", referencedColumnName="id")}
* )
*/
protected $generique;
/**
* #var string
*
* #ORM\Column(name="Reference_g", type="string", length=255)
*/
private $referenceG;
/**
* Bidirectional
*
* #ORM\ManyToMany(targetEntity="Piece", mappedBy="generique")
*/
protected $origine;
I work with CRUD symfony for Piece and its Replacement, from CollectionType
http://symfony.com/doc/current/cookbook/form/form_collections.html
Show Piece works well,And the remains are:
PieceType:
<?php
namespace STM\DevisBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class PieceType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('reference',TextType::class)
->add('type',TextType::class)
->add('marque',TextType::class)
->add('generique',CollectionType::class, array(
'entry_type' => RemplacementType::class,
'allow_add' => true,
'allow_delete' => true))
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'STM\DevisBundle\Entity\Piece'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'stm_devisbundle_piece';
}
}
RemplacementType:
<?php
namespace STM\DevisBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class RemplacementType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('referenceG',TextType::class)
->add('typeG',TextType::class)
->add('marqueG',TextType::class)
->add('origine',CollectionType::class, array(
'entry_type' => PieceType::class,
'allow_add' => true,
'allow_delete' => true));
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'STM\DevisBundle\Entity\Remplacement'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'stm_devisbundle_remplacement';
}
}
Controller is symfony which generates
The but is to do CRUD with Piece and its Replacement. And I'm lost on Collection Type
I need your help because it seems easy but I can not solve it
Thank you
Let me explain: you have 2 form types PieceType and RemplacementType
In the PieceType, you add a collection with entry type is RemplacementType.
In the RemplacementType, you add a collection with entry type is PieceType.
Both of them allow_add and build the prototype forms. So:
The form builder build PieceType (*)
PieceType has a collection field, it create the prototype
The prototype use RemplacementType
The form builder build RemplacementType
RemplacementType has a collection field, it create the prototype
The prototype use PieceType
The form builder build PieceType (*)
..and so on (circular calls -> infinity form level)
To avoid this case, one of them must disable adding the other one by add option: 'allow_add' => false, 'prototype' => false'

Create subform and append it in exisiting form in symfony 3

I have this configuration :
ModuleModelType
| - title
| - created
| - subform
| - DevField1
| - DevField2
| - etc...
What I want to have is something like this :
ModuleModelType
| - title
| - created
| - subform
| | - DevField1
| | - DevField2
| | - etc...
But the problem is that i don't know what users will add to the form, as I'm creating a reusable bundle and I don't want name conflicts.
This is my code, how I can make this easily ?
I want a developer can create a class and append his own form in an existing form.The following existing code append the dev's form into the first one and can replace some existing keys if keys are the same.
Actually, I want to append all dev's fields into and subform to avoid this.
<?php
// Into a controller
/** #var ModuleInterface $instance */
$instance = ... ;
$form = $this->createForm(ModuleModelType::class);
$subform= $this->createForm(new FormType());
$instance->buildForm($subform);
$form->add('subform',$subform); // I want to make something like that
interface ModuleInterface
{
public function buildForm(Form &$form);
}
class Foo implements ModuleInterface
{
public function buildForm(Form &$builder)
{
$builder->add("DevField1", /* type */);
$builder->add("DevField2", /* type */);
}
}
class ModuleModelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('created')
->add('subform')
;
}
}
We will assume some facts :
You want to have an entity with "data" field
This "data" field must be an array
A form must be changed to handle this "data" field
Obviously, in the entity, we can load and save "data" informations, as an array
This behavior can be easily done passing the subform by constructor.
Your problem happen because you're fighting against Symfony, please considere a standard way :
Have an entity with "array" field type
Have two forms Type : First one to handle your entity, second one to handle the embedded form.
Use dependency injection to embed the right form. (Assume two services by Module : first one for the embedded form, second one for you.
Your entity : (Think to generate getters & setters with generate:doctrine:entities)
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Foo
*
* #ORM\Table(name="foo")
* #ORM\Entity(repositoryClass="AppBundle\Repository\FooRepository")
*/
class Foo
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="some_informations", type="string", length=255)
*/
private $someInformations;
/**
* #var string
*
* #ORM\Column(name="Data", type="string", length=255)
*/
private $data;
/**
* #var string
*
* #ORM\Column(name="data", type="array")
*/
private $data;
Your form :
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FooType extends AbstractType
{
protected $dataFormName;
public function __construct($dataFormName)
{
$this->dataFormName = $dataFormName;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('someInformations')
->add('data', $this->dataFormName)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Foo'
));
}
}
Your subform :
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SubformType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content')
->add('some_data')
;
}
}
At this point, you should be able to generate your form with :
$foo = new Foo();
$form = $this->createForm('service_name_for_the_form_with_second_embedded', $foo);
Old answer :
In this case, you should assume that ModuleInterface extends FormTypeInterface.
To be used as FormType in a $form->add, your class should look like a form.
At this point, use ModuleInterface seems to be useless.
I can purpose that way for you :
<?php
// Into a controller
$instance = new Foo();
$form = $this->createForm(new ModuleModelType($instance));
class Foo extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add("DevField1");
$builder->add("DevField2");
}
}
class ModuleModelType extends AbstractType
{
protected $subform;
function __construct(FormTypeInterface $subform)
{
$this->subform = $subform;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('created')
->add('subform', $this->subform)
;
}
}
You can also use dependency injection to inject $instance through __construct.
Take care about naming convention for "Foo" class, should be FooType ;)
If you do something like that, your expected form will be OK.
Have a nice day,
Gaƫl
I hope you already read this careful: http://symfony.com/doc/current/book/forms.html
In your case, you can do by:
use path/of/your/formType/to/embed;
class ModuleModelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('created')
->add('subform', Foo::class)
;
}
}
Don't worry about interface. Interface just must to implement, I hope you already know that.
I dont know why you want add in controller, because you have already form class, you just need modify to make clean your code.
I you want subform as add by user, you can do by collection form embed: http://symfony.com/doc/current/cookbook/form/form_collections.html
Or you want by data (show form if submitted) : http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
NOTE: Symfony ver 3.0 are different with 2.~.

Symfony 2 forms: array of entities

I am trying to build a form for multiple entities. Let me first introduce some sample classes:
For clarity I do not show all annotations or abbreviate them, and I do not show the use or namespace commands.
/**
* The Entity class
* #ORM ... mapping to ORM
*/
class EntityA {
/**
* #var ModelArray
* #ORM\Column(name="...", type="object")
*/
private $modelArray;
// Getters and Setters, default constructor
}
/**
* An array Wrapper, keeping the array always unique, sorting it by criteria etc.
* #ORM(...)
*/
class ModelArray {
/**
* #var array<A>
*/
private $array;
// Getter, Adder, Remover, Constructor, other private logic
}
Notice especially that the class ModelArray stores only objects of a given Type A:
/**
* One more Model
*/
class A {
/**
* #var boolean
*/
private $bool;
/**
* #var string
*/
private $string;
// Getters, Setters
}
I chose this data structure, because I never need objects of A to exist outside the EntityA class, and to keep the logic out of my Entity, I chose to implement the ModelArray class in between. The EntityA class is persisted to a database, and with it the child objects.
Now I want to create a form, where I can edit the $->bool attributes of all A instances of an Array of EntityA at once.
So what I provide is array<EntityA>.
I would then proceed as follows:
//In a controller action
$data = array(
'as' => $arrayOfEntityA,
);
$form = $this->createForm(new GridType(), $data);
// Create view, render
My form type would look like this:
class GridType extends AbstractType {
public function getName() {
return 'a_grid';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('as', 'collection', array(
'type' => new GridAType(),
));
}
}
And use this form type
class GridAType extends AbstractType {
public function getName() {
return 'a_grid_a';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('modelArray', new ModelArrayType());
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'EntityA',
);
}
}
With the model array type
class ModelArrayType extends AbstractType {
public function getName() {
return 'model_array';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
// ???
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'ModelArray',
);
}
}
Notice the // ???: I would like to only edit the boolean attribute of the As strored in the ModelArray, and I think the appropriate form type to continue would be a 'collection'. But I can't really find out how to use the array ($modelArray->array)here as a collection.
How should I do this?
An then, I'm not entirely sure if it is good practice to implement such a lot of form types just to achieve one usable form. Is there a different and better way?

Categories