I'm trying to create a custom choice list field.
And almost all seems working, except the preselected values on the edit part.
Basically i'm creating a mixed list field with multiple object type (the backend is mongodb), i know that is a dirty way to operate but i didn't find a better solution (keeping things simple).
The process is working, i have a mixed objects in the backend and i can choose which one in the edit form, but the form doesn't show the preselected (with the values extracted from mongo)
<?php
namespace www\DefaultBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use www\DefaultBundle\Form\DataTransformer\AccessorioTransformer;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class AccessorioType extends AbstractType
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new AccessorioTransformer($this->om);
$builder->addModelTransformer($transformer);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$choices = array();
$data = array();
$documents = array(
'Document1',
'Document2',
'Document3',
);
foreach ($documents as $document)
{
$objects = $this->om->getRepository('wwwDefaultBundle:' . $document)->findAll();
foreach ($objects as $object)
{
if (#!$object->getId()) print_r($object);
$key = sprintf("%s_%s", $object->getId(), basename(str_replace('\\', '/', get_class($object))));
$value = sprintf("%s (%s)", $object, basename(str_replace('\\', '/', get_class($object))));
$choices[$key] = $value;
}
}
$resolver->setDefaults(array(
'choices' => $choices,
'expanded' => false,
'multiple' => true,
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'accessorio';
}
}
the datatransformer:
<?php
namespace www\DefaultBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Extension\Core\ObjectChoiceList;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\TaskBundle\Entity\Issue;
class AccessorioTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function transform($values)
{
return array();
// i tried everything here but none working
}
public function reverseTransform($values)
{
if (!$values) return null;
$array = array();
foreach ($values as $value)
{
list($id, $type) = explode("_", $value);
$array[] = $this->om->getRepository('wwwDefaultBundle:' . $type)->find($id);
}
return $array;
}
}
the form builder:
<?php
namespace www\DefaultBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ValvolaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// [snip]
->add('ref',
'accessorio',
array(
'label_attr' => array('class' => 'control-label col-sm-2'),
'attr' => array('class' => 'form-control '),
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'www\DefaultBundle\Document\Valvola',
'attr' => array('class' => 'press form-horizontal'),
));
}
public function getName()
{
return 'www_defaultbundle_valvolatype';
}
}
Did someone faced the same problem? How "choice" field should be transformed? How managed mixed object in the same field?
Someone can enlighten me?
Regards
I finally found the solution. The culprit was the datatransformer (and mine, of course :-))
In this way the form shows the preselected values:
<?php
namespace www\DefaultBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Extension\Core\ObjectChoiceList;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\TaskBundle\Entity\Issue;
class AccessorioTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function transform($values)
{
if ($values === null) return array();
$choices = array();
foreach ($values as $object)
{
$choices[] = sprintf("%s_%s", $object->getId(), basename(str_replace('\\', '/', get_class($object))));
}
return $choices;
}
public function reverseTransform($values)
{
if (!$values) return array();
$array = array();
foreach ($values as $value)
{
list($id, $type) = explode("_", $value);
$array[] = $this->om->getRepository('wwwDefaultBundle:' . $type)->find($id);
}
return $array;
}
}
In most cases using ChoiceType the array returned by the transform function should contain the ID's only. For example:
public function transform($objectsArray)
{
$choices = array();
foreach ($objectsArray as $object)
{
$choices[] = $object->getId();
}
return $choices;
}
Although it might not be the answer to the original post, I am pretty sure that googlers get here and look for this hint.
Related
I want to update a form class to Symfony2.8 (and later to Symfony3). Now the form is converted except one attribute, the choice_list, that is not supported anymore. And I don't know how to do this.
I have the following form type that is defined also as a service:
class ExampleType extends AbstractType
{
/** #var Delegate */
private $delegate;
public function __construct(Delegate $delegate)
{
$this->delegate = $delegate;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('list', ChoiceType::class, array(
'choice_list' => new ExampleChoiceList($this->delegate),
'required'=>false)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'ExampleClass',
));
}
}
And I have the following class for the choice list:
class ExampleChoiceList extends LazyChoiceList
{
/** #var Delegate */
private $delegate;
public function __construct(Delegate $delegate)
{
$this->delegate = $delegate;
}
/**
* Loads the choice list
* Should be implemented by child classes.
*
* #return ChoiceListInterface The loaded choice list
*/
protected function loadChoiceList()
{
$persons = $this->delegate->getAllPersonsFromDatabase();
$personsList = array();
foreach ($persons as $person) {
$id = $person->getId();
$personsList[$id] = (string) $person->getLastname().', '.$person->getFirstname();
}
return new ArrayChoiceList($personsList);
}
}
The class ExampleChoiceList generate the choice list how I want to have it, and until now it worked. But the attribute choice_list is not supported anymore, and my question is "how do I get this converted without too much work?". I read that I should use simple choice but how I get that what I want (the specific label from the database) in Symfony 2.8. I hope somebody can help me.
By using a ChoiceListInterface you are almost there.
I suggest you change the ExampleChoiceList to implement the Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface instead, it requires you implement 3 methods:
<?php
// src/AppBundle/Form/ChoiceList/Loader/ExampleChoiceLoader.php
namespace AppBundle\Form\ChoiceList\Loader;
use Acme\SomeBundle\Delegate;
use Symfony\Component\Form\ArrayChoiceList;
use Symfony\Component\Form\Loader\ChoiceLoaderInterface;
class ExampleChoiceLoader implements ChoiceLoaderInterface
{
/** $var ArrayChoiceList */
private $choiceList;
/** #var Delegate */
private $delegate;
public function __construct(Delegate $delegate)
{
$this->delegate = $delegate;
}
/**
* Loads the choice list
*
* $value is a callable set by "choice_name" option
*
* #return ArrayChoiceList The loaded choice list
*/
public function loadChoiceList($value = null)
{
if (null !== $this->choiceList) {
return $this->choiceList;
}
$persons = $this->delegate->getAllPersonsFromDatabase();
$personsList = array();
foreach ($persons as $person) {
$label = (string) $person->getLastname().', '.$person->getFirstname();
$personsList[$label] = (string) $person->getId();
// So $label will be displayed and the id will be used as data
// "value" will be ids as strings and used for post
// this is just a suggestion though
}
return $this->choiceList = new ArrayChoiceList($personsList);
}
/**
* {#inheritdoc}
*
* $choices are entities or the underlying data you use in the field
*/
public function loadValuesForChoices(array $choices, $value = null)
{
// optimize when no data is preset
if (empty($choices)) {
return array();
}
$values = array();
foreach ($choices as $person) {
$values[] = (string) $person->getId();
}
return $values;
}
/**
* {#inheritdoc}
*
* $values are the submitted string ids
*
*/
public function loadChoicesForValues(array $values, $value)
{
// optimize when nothing is submitted
if (empty($values)) {
return array();
}
// get the entities from ids and return whatever data you need.
// e.g return $this->delegate->getPersonsByIds($values);
}
}
register both the loader and the type as services so they are injected:
# app/config/services.yml
services:
# ...
app.delegate:
class: Acme\SomeBundle\Delegate
app.form.choice_loader.example:
class: AppBundle\Form\ChoiceList\Loader\ExampleChoiceLoader
arguments: ["#app.delegate"]
app.form.type.example:
class: AppBundle\Form\Type\ExampleType
arguments: ["#app.form.choice_loader.example"]
Then change the form type to use the loader:
<?php
// src/AppBundle/Form/Type/ExampleType.php
namespace AppBundle\Form\Type;
use AppBundle\Form\ChoiceList\Loader\ExampleChoiceLoader;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ExampleType extends AbstractType
{
/** #var ExampleChoiceLoader */
private $loader;
public function __construct(ExampleChoiceLoader $loader)
{
$this->loader = $loader;
}
public function buildForm(FormBuilderInterface $builder, array $options = array())
{
$builder->add('list', ChoiceType::class, array(
'choice_loader' => $this->loader,
'required' => false,
));
}
// ...
}
Yes, 'choice_list' is deprecated with SYmfony 2.8, but you can use 'choices' instead, which accepts an array as well. From documentation:
The choices option is an array, where the array key is the item's
label and the array value is the item's value.
You must just pay attention than in Symfony 3.0, keys and values are inversed, and in Symfony 2.8, the recommended way is to use the new inversed order, and to specify 'choices_as_values' => true.
So, in the form type:
$builder->add('list', ChoiceType::class, array(
'choices' => new ExampleChoiceList($this->delegate),
'choices_as_values' => true,
'required'=>false));
And in ExampleChoiceList:
protected function loadChoiceList()
{
$persons = $this->delegate->getAllPersonsFromDatabase();
$personsList = array();
foreach ($persons as $person) {
$id = $person->getId();
$personsList[(string) $person->getLastname().', '.$person->getFirstname()] = $id; // <== here
}
return new ArrayChoiceList($personsList);
}
UPDATE:
Ok, so I suggest you don't use a ChoiceType at all, but a EntityType, as you seem to fetch all "Persons" from database. And to display "Last Name, First Name" as a label, use the 'choice_label' option. Assuming your entity is called 'Person':
$builder->add('list', EntityType::class, array(
'class' => 'AppBundle:Person',
'choice_label' => function ($person) {
return $person->getLastName() . ', ' . $person->getFirstName();
}
));
I have an API and I am sending a reference of an entity, I'm using a DataTransformer to get my entity but the DataTransformer is always called before the $form->handleRequest($request) the value is always null and it could't works
My Controller
public function newAction(Request $request)
{
$orderNewFormType = $this->get('competitive_bo.api_bundle.form.type.order_new');
$card = new Card();
try {
$form = $this->createForm($orderNewFormType, $card);
$form->handleRequest($request);
} catch (TransformationFailedException $e) {
return $this->notFoundErrorResponse(
'Business not found'
);
}
if ($form->isValid()) {
return $this->okResponse(array());
}
$validatorErrorFormatter = $this->get('competitive_bo.api_bundle.formatter.validator_error');
$errors = $validatorErrorFormatter->formatFromFormError($form->getErrors(true));
return $this->badRequestErrorResponse(
'Invalid data',
$errors
);
}
The form type
class OrderNewFormType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('customer', 'entity', array(
'class' => 'CompetitiveBOBusinessBundle:Customer',
'property' => 'id'
))
->add('business', 'business', array(
'mapped' => false,
))
;
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
'data_class' => Card::class
));
}
public function getName()
{
return null;
}
}
The Business form type
class BusinessReferenceFormType extends AbstractType
{
/**
* #var ReferenceToBusinessTransformer
*/
private $referenceToBusinessTransformer;
public function __construct(ReferenceToBusinessTransformer $referenceToBusinessTransformer)
{
$this->referenceToBusinessTransformer = $referenceToBusinessTransformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addViewTransformer($this->referenceToBusinessTransformer);
}
public function getName()
{
return 'business';
}
public function getParent()
{
return 'text';
}
}
And the DataTransformer
/**
* Class ReferenceToBusinessTransformer
*/
class ReferenceToBusinessTransformer implements DataTransformerInterface
{
/**
* #var BusinessRepository
*/
private $businessRepository;
public function __construct(BusinessRepository $businessRepository)
{
$this->businessRepository = $businessRepository;
}
/**
* {#inheritdoc}
*/
public function transform($reference)
{
var_dump($reference);
$business = $this->businessRepository->findOneBy(array(
'reference' => $reference
));
if (null === $business) {
throw new TransformationFailedException;
}
return $business;
}
/**
* {#inheritdoc}
*/
public function reverseTransform($value)
{
if (!($value instanceof Business)) {
throw new TransformationFailedException;
}
return $value->getReference();
}
}
The var_dump($reference) is always null
And I have my test
public function testNewAction($getParams, $postParam, $responseCode)
{
$client = static::createClient();
$router = $client->getContainer()->get('router');
$route = $router->generate('competitivebo_api_order_new',$getParams);
$client->request('POST', $route, $postParam);
$response = $client->getResponse();
$this->assertJsonResponse($response, $responseCode);
}
With the post params
'customer' => 1,
'business' => LoadBusinessData::REFERENCE_1,
'name' => 'Test',
The exception TransformationFailedException is always thrown during the $this->createForm(...) so the request is not handled
According with the documentation,
When null is passed to the transform() method, your transformer should return an equivalent value of the type it is transforming to (e.g. an empty string, 0 for integers or 0.0 for floats).
i have a form definition which uses the so-far great field type entity. With the option query_builder I select my values and the are displayed.
The sad part is, I am required to display a null default value, like all (it's a filter form). I don't like the choices option of entity because I have database values and a FormType shouldn't query the database.
My approach so far was to implement a custom field type which extends entity and adds a null entry to the top of the list. The field type is loaded and used but unfortunately the dummy value is not displayed.
The field definition:
$builder->add('machine', 'first_null_entity', [
'label' => 'label.machine',
'class' => Machine::ident(),
'query_builder' => function (EntityRepository $repo)
{
return $repo->createQueryBuilder('m')
->where('m.mandator = :mandator')
->setParameter('mandator', $this->mandator)
->orderBy('m.name', 'ASC');
}
]);
The form type definition:
class FirstNullEntityType extends AbstractType
{
/**
* #var unknown
*/
private $doctrine;
public function __construct(ContainerInterface $container)
{
$this->doctrine = $container->get('doctrine');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired('query_builder');
$resolver->setRequired('class');
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$class = $options['class'];
$repo = $this->doctrine->getRepository($class);
$builder = $options['query_builder']($repo);
$entities = $builder->getQuery()->execute();
// add dummy entry to start of array
if($entities) {
$dummy = new \stdClass();
$dummy->__toString = function() {
return '';
};
array_unshift($entities, $dummy);
}
$options['choices'] = $entities;
}
public function getName()
{
return 'first_null_entity';
}
public function getParent()
{
return 'entity';
}
}
Here is what works in Symfony 3.0.3
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
$builder->add('example' EntityType::class, array(
'label' => 'Example',
'class' => 'AppBundle:Example',
'placeholder' => 'Please choose',
'empty_data' => null,
'required' => false
));
You can use placeholder from 2.6
An alternative approach would be to use a ChoiceList with choices that are generated from the database and then use that in a custom choice form type that will allow for an empty_value.
Choice List
namespace Acme\YourBundle\Form\ChoiceList;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
class MachineChoiceList extends LazyChoiceList
{
protected $repository;
protected $mandator;
public function __construct(ObjectManager $manager, $class)
{
$this->repository = $manager->getRepository($class);
}
/**
* Set mandator
*
* #param $mandator
* #return $this
*/
public function setMandator($mandator)
{
$this->mandator = $mandator;
return $this;
}
/**
* Get machine choices from DB and convert to an array
*
* #return array
*/
private function getMachineChoices()
{
$criteria = array();
if (null !== $this->mandator) {
$criteria['mandator'] = $this->mandator;
}
$items = $this->repository->findBy($criteria, array('name', 'ASC'));
$choices = array();
foreach ($items as $item) {
$choices[** db value **] = ** select value **;
}
return $choices;
}
/**
* {#inheritdoc}
*/
protected function loadChoiceList()
{
return new SimpleChoiceList($this->getMachineChoices());
}
}
Choice List Service (YAML)
acme.form.choice_list.machine:
class: Acme\YourBundle\Form\ChoiceList\MachineChoiceList
arguments:
- #doctrine.orm.default_entity_manager
- %acme.model.machine.class%
Custom Form Type
namespace Acme\YourBundle\Form\Type;
use Acme\YourBundle\Form\ChoiceList\MachineChoiceList;
..
class FirstNullEntityType extends AbstractType
{
/**
* #var ChoiceListInterface
*/
private $choiceList;
public function __construct(MachineChoiceList $choiceList)
{
$this->choiceList = $choiceList;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$choiceList = $this->choiceList;
$resolver->setDefault('mandator', null);
$resolver->setDefault('choice_list', function(Options $options) use ($choiceList) {
if (null !== $options['mandator']) {
$choiceList->setMandator($options['mandator']);
}
return $choiceList;
});
}
public function getName()
{
return 'first_null_entity';
}
public function getParent()
{
return 'choice';
}
}
Custom Form Type Service (YAML)
acme.form.type.machine:
class: Acme\YourBundle\Form\Type\FirstNullEntityType
arguments:
- #acme.form.choice_list.machine
tags:
- { name: form.type, alias: first_null_entity }
In Your Form
$builder
->add('machine', 'first_null_entity', [
'empty_value' => 'None Selected',
'label' => 'label.machine',
'required' => false,
])
;
Symfony 2.3
I'm embedding some Forms to be able to change each user property related to permissions. I've created an UserAdminType which is displayed for each user in the same page:
<?php
namespace Msalsas\UserAdminBundle\Form;
use Msalsas\UserBundle\Entity\User;
use Msalsas\UserBundle\Entity\ExtendedUser;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class UserAdminType extends AbstractType
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
$this->extendedUser = new ExtendedUser($this->user);
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$extendedUser = $this->extendedUser;
$builder
->add('extendedRole',
'choice', array('choices' => array(
$extendedUser::ROLE_1 => "Role 1",
$extendedUser::ROLE_2 => "Role 2",
$extendedUser::ROLE_3 => "Role 3",
),
'label' => $this->user->getUsername()
))
->add('Change roles for '.$this->user->getUsername(), 'submit')
;
$builder->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if( ! $form->getClickedButton('Change roles for '.$this->user->getUsername()) )
{
// Here I should avoid submitting the form
}
}
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Msalsas\UserBundle\Entity\ExtendedUser',
'empty_data' => function (FormInterface $form) {
return null;
}
));
}
/**
* #return string
*/
public function getName()
{
return 'extendedUserRoleForm';
}
}
The problem is that when I submit one of those forms, all other forms are also submitted, returning an error, because the extendedUser uses a constructor to initialize the object with the User as parameter:
Catchable Fatal Error: Argument 1 passed to Msalsas\UserBundle\Entity\ExtendedUser::__construct() must be an instance of Msalsas\UserBundle\Entity\User, none given, called in .../vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/Type/FormType.php on line 140 and defined
I've also tried to set the empty_data with a new default ExtendedUser:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Msalsas\UserBundle\Entity\ExtendedUser',
'empty_data' => function (FormInterface $form) {
return $this->extendedUser;
}
));
}
Now, when submitting the form, the new entity is persisted, but the other forms are still submitted, and returning an error: This form should not contain extra fields. This seems to be due to the duplicated property name (extendedRole).
How could I avoid the other forms to be submitted?
I've found out the solution here.
Each Form must have different name. So I've added a $name property, and assigned it in the constructor:
private $user;
private $name = 'default_name_';
private $extendedUser;
public function __construct(User $user, $formName)
{
$this->user = $user;
$this->extendedUser = new ExtendedUser($this->user);
$this->name = $this->name.$formName;
}
//... (No event required)
/**
* #return string
*/
public function getName()
{
return $this->name;
}
The $formName parameter is relative to the current user. In this way, only the "clicked" form is submitted. Wish it helps.
I'm working on a form that handle N:M relationship with an extra parameter (extra field/column). This is what I've done until now:
In OrdersType.php form:
class OrdersType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
// $builder fields
// $builder fields only need on edit form not in create
if ($options['curr_action'] !== NULL)
{
$builder
// other $builder fields
->add("orderProducts", "collection", array(
'type' => new OrdersHasProductType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\Orders',
'render_fieldset' => FALSE,
'show_legend' => FALSE,
'intention' => 'orders_form',
'curr_action' => NULL
));
}
public function getName()
{
return 'orders';
}
}
In OrderHasProductType.php:
class OrdersHasProductType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'text', array(
'required' => FALSE,
'label' => FALSE
))
->add('amount', 'text', array(
'required' => TRUE,
'label' => FALSE
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\OrderHasProduct',
'intention' => 'order_has_product'
));
}
public function getName()
{
return 'order_has_product';
}
}
And finally this is Orders.php and OrdersHasProduct.php entities:
class Orders {
use IdentifiedAutogeneratedEntityTrait;
// rest of fields for the entity
/**
* #ORM\OneToMany(targetEntity="OrderHasProduct", mappedBy="order", cascade={"all"})
*/
protected $orderProducts;
protected $products;
/**
* #ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
protected $deletedAt;
public function __construct()
{
$this->orderProducts = new ArrayCollection();
$this->products = new ArrayCollection();
}
public function getOrderProducts()
{
return $this->orderProducts;
}
public function getDeletedAt()
{
return $this->deletedAt;
}
public function setDeletedAt($deletedAt)
{
$this->deletedAt = $deletedAt;
}
public function getProduct()
{
$products = new ArrayCollection();
foreach ($this->orderProducts as $op)
{
$products[] = $op->getProduct();
}
return $products;
}
public function setProduct($products)
{
foreach ($products as $p)
{
$ohp = new OrderHasProduct();
$ohp->setOrder($this);
$ohp->setProduct($p);
$this->addPo($ohp);
}
}
public function getOrder()
{
return $this;
}
public function addPo($ProductOrder)
{
$this->orderProducts[] = $ProductOrder;
}
public function removePo($ProductOrder)
{
return $this->orderProducts->removeElement($ProductOrder);
}
}
/**
* #ORM\Entity
* #ORM\Table(name="order_has_product")
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
* #UniqueEntity(fields={"order", "product"})
*/
class OrderHasProduct {
use IdentifiedAutogeneratedEntityTrait;
/**
* Hook timestampable behavior
* updates createdAt, updatedAt fields
*/
use TimestampableEntity;
/**
* #ORM\ManyToOne(targetEntity="\Tanane\FrontendBundle\Entity\Orders", inversedBy="orderProducts")
* #ORM\JoinColumn(name="general_orders_id", referencedColumnName="id")
*/
protected $order;
/**
* #ORM\ManyToOne(targetEntity="\Tanane\ProductBundle\Entity\Product", inversedBy="orderProducts")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
protected $product;
/**
* #ORM\Column(type="integer", nullable=false)
*/
protected $amount;
/**
* #ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
protected $deletedAt;
public function setOrder(\Tanane\FrontendBundle\Entity\Orders $order)
{
$this->order = $order;
}
public function getOrder()
{
return $this->order;
}
public function setProduct(\Tanane\ProductBundle\Entity\Product $product)
{
$this->product = $product;
}
public function getProduct()
{
return $this->product;
}
public function setAmount($amount)
{
$this->amount = $amount;
}
public function getAmount()
{
return $this->amount;
}
public function getDeletedAt()
{
return $this->deletedAt;
}
public function setDeletedAt($deletedAt)
{
$this->deletedAt = $deletedAt;
}
}
But when I try to edit a order with this code in my controller:
public function editAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('FrontendBundle:Orders')->find($id);
$type = $order->getPerson()->getPersonType() === 1 ? "natural" : "legal";
$params = explode('::', $request->attributes->get('_controller'));
$actionName = substr($params[1], 0, -6);
$orderForm = $this->createForm(new OrdersType(), $order, array('action' => '#', 'method' => 'POST', 'register_type' => $type, 'curr_action' => $actionName));
return array(
"form" => $orderForm->createView(),
'id' => $id,
'entity' => $order
);
}
I get this error:
The form's view data is expected to be of type scalar, array or an
instance of \ArrayAccess, but is an instance of class
Proxies__CG__\Tanane\ProductBundle\Entity\Product. You can avoid this
error by setting the "data_class" option to
"Proxies__CG__\Tanane\ProductBundle\Entity\Product" or by adding a
view transformer that transforms an instance of class
Proxies__CG__\Tanane\ProductBundle\Entity\Product to scalar, array or
an instance of \ArrayAccess.
And I don't find or know how to fix it, can any give me some help?
I think this is happening because this code
class OrdersHasProductType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'text', array(
'required' => FALSE,
'label' => FALSE
))
));
}
//...
}
means that Symfony is expecting "product" to be a field of type "text", but when it calls getProduct() on the OrderHasProduct it gets a Product object (or a Doctrine Proxy to a Product, since it's not been loaded at that point). Symfony Fields inherit from Form/AbstractType, so they're essentially Forms in their own right, with just one field, hence the error message.
The solution is either to make that field of type "entity", or to create a different method which only gives the name of the Product, e.g. getProductName() on OrderHasProduct, and then use that as the data behind the field.