I'm trying to populate form from database by using two entities. Here's the code I use
CategoryType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
}
public function configureOptions(OptionsResolver $options)
{
return array(
'data_class' => 'AppBundle\Entity\Category',
);
}
public function getName()
{
return 'questionnaire';
}
}
ProductType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
}
public function configureOptions(OptionsResolver $options)
{
return array(
'data_class' => 'AppBundle\Entity\Product',
);
}
public function getName()
{
return 'questionnaire';
}
}
CommonType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use AppBundle\Form\CategoryType;
use AppBundle\Form\ProductType;
class CommonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('category', new CategoryType());
$builder->add('product', new ProductType());
}
public function getName()
{
return 'app_common_type';
}
}
DefaultController
<?php
namespace AppBundle\Controller;
use AppBundle\Form\CommonType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$data = $this->getDoctrine()->getRepository('AppBundle:Product')->findOneBy(['id' => 2]);
$form = $this->createForm(new CommonType(), $data);
return $this->render('AppBundle:Default:index.html.twig', ['form' => $form->createView()]);
}
}
But 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__\AppBundle\Entity\Category. You can avoid this error by setting the "data_class" option to "Proxies\__CG__\AppBundle\Entity\Category" or by adding a view transformer that transforms an instance of class Proxies\__CG__\AppBundle\Entity\Category to scalar, array or an instance of \ArrayAccess.
So what I'm doing wrong and is this the correct way to populate form based on two or more entities?
Your Common form's model data format is an array (as far as no data_class given). So the right data to set is an array with product and category keys:
$product = $this->getDoctrine()->getRepository('AppBundle:Product')->findOneBy(['id' => 2]);
$form = $this->createForm(new CommonType(), ['product' => $product]);
Related
I have a form based on a collection. I would like that form to be set with a part of the data I have in my entity (not all).
I have search but haven't find the answer I'm looking for.
I tought using PRE_SET_DATA but the way I understand it doesn't work.
Can someone help me to do that?
I have an entity Service
<?php
namespace App\Entity;
use App\Repository\ServiceRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=ServiceRepository::class)
*/
class Service
{
//[...]
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="services")
*/
private $performer;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="performer_services")
*/
private $service_performer;
//[...]
public function getPerformer(): ?user
{
return $this->performer;
}
public function setPerformer(?user $performer): self
{
$this->performer = $performer;
return $this;
}
public function getServicePerformer(): ?User
{
return $this->service_performer;
}
public function setServicePerformer(?User $service_performer): self
{
$this->service_performer = $service_performer;
return $this;
}
}
My Collection Form
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PerformerServiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('performer_services',CollectionType::class,[
'entry_type' => ServicePerformerNoMoneyUnitType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
])
->add('enregistrer', SubmitType::class)
;
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
My Entity Form (based on Service)
<?php
namespace App\Form;
use App\Entity\Service;
use App\Repository\UserRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Security\Core\Security;
use App\Entity\User;
class ServicePerformerNoMoneyUnitType extends AbstractType
{
public function __construct(Security $security)
{
$this->security = $security;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price', MoneyType::class, [
'currency' => false])
->add('servicePerformer', EntityType::class, [
'class' => 'App\Entity\User',
'query_builder' => function (UserRepository $er) {
return $er->findByPartners($this->security->getUser());
},
])
->add('description')
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$service = $event->getData();
$userService = new User;
$performerService = new User;
if ($service <> null)
{
$userService = $service->getPerformer();
dump($userService);
$performerService = $service->getServicePerformer();
dump($performerService);
}
$form = $event->getForm();
// checks if the Product object is "new"
// If no data is passed to the form, the data is "null".
// This should be considered a new "Product"
if ($userService == $performerService) {
$form->removeElement();
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Service::class,
]);
}
}
What I would like to do is not show rows where Performer = Service Performer.
I created code for tags in symfony but I have this bug:
The autoloader expected class "Tag\TagBundle\Form\Types\TagsType" to be defined in file "/var/www/html/TagProject/vendor/composer/../../src/Tag/TagBundle/Form/Types/TagsType.php". The file was found but the class was not in it, the class name or namespace probably has a typo.
TagsType.php:
<?php
namespace Tag\TagBundle\Form\Types;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Tag\TagBundle\Form\DataTransformer\TagsTransformer;
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionArrayTransformer;
use Symfony\Component\Form\FormBuilderInterface;
class TagsTypes extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->addModelTransformer(new CollectionArrayTransformer(),true)
->addModelTransformer(new TagsTransformer(),true);
}
public function getParent(){
return TextType::class;
}
}
}
TagsTransformer.php:
<?php
namespace Tag\TagBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
class tagsTransformer implements DataTransformerInterface {
public function transform($value){
dump($value);
return "";
}
public function reverseTransform($value){
}
}
PostType.php:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Tag\TagBundle\Form\Types\TagsType;
class PostType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')
->add('content')
->add('tags',TagsType::class);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Post'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_post';
}
}
Your error is naming class.
You have TagsType.php is php file and your class name is TagsTypes:
You have to change class name:
class TagsTypes
to
class TagsType
I have a Filter and FilterCollection object. The FilterCollection holds a collection of Filters, just like the name indicate.
Now I need to validate everything, so I created a FilterType and FilterTypeCollection Forms. In the FilterCollectionType I have:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('filters', CollectionType::class, array(
'entry_type' => FilterType::class
));
}
And in the FilterCollection definition I have the following:
/**
* #var array
* #Assert\Valid()
*/
private $filters = [];
I created a paramConverter so I could convert elements from my request into FilterCollection ones. In the apply method I try to validate everything by using:
public function apply(Request $request, ParamConverter $configuration)
$filterCollection = new FilterCollection();
$form = $this->formFactory->create(
FilterTypeCollection::class,
$filterCollection
);
$form->submit($request->query->all());
if ($form->isSubmitted() && $form->isValid()) {
$request->attributes->set($configuration->getName(), $filterCollection);
return true;
} else {
throw new FormValidationException($form);
}
}
I was expecting that the validation not only validates the FilterCollection but also the Filters. But the validations I have in my Filter definition, are not working, even if I have validations that should fail, it still passes. I think the validator is not passing on the Filter elements.
Any idea on what might be happening?
I finally got it to work. Perhaps you made the same mistake as me, forgetting to add "data_class" in the configureOptions in the formType.
Anyway, here's the code that works (on fresh install of Symfony 3.3)
DefaultController.php
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Filter;
use AppBundle\Entity\FilterCollection;
use AppBundle\Form\FilterCollectionType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
// add first filter, so we don't have to implement the collection javascript etc to test quickly
$collection = new FilterCollection();
$collection->filters[] = new Filter();
$form = $this->createForm(FilterCollectionType::class, $collection);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
echo "valid input"; // we don't want to see this ;)
}
}
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'form' => $form->createView()
]);
}
}
Filter.php
<?php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Filter {
/**
* #var string
* #Assert\NotBlank()
* #Assert\Regex(pattern="/[0-9]+/")
*/
public $name;
}
FilterCollection.php
<?php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class FilterCollection {
/**
* #var Filter[]
* #Assert\Valid()
*/
public $filters = [];
}
FilterType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Filter'
]);
}
}
FilterCollectionType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FilterCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('filters', CollectionType::class, [
'entry_type' => FilterType::class,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\FilterCollection',
]);
}
public function getName()
{
return 'app_bundle_filter_collection_type';
}
}
Note: I didn't make a ParamConverter like you did, but that seems beside the point of the question. You can change the code to use a ParamConverter easily.
the error that arises in symfony is as follows:
Expected argument of type "string or Symfony\Component\Form\FormTypeInterface", "Proyecto\LavocBundle\Entity\Datos" given
I will not put all code so that it does not become too large , I think the problem is in the Type:
DatosType:
<?php
namespace Proyecto\LavocBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class DatosType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('empresa');
$builder->add('cuit');
$builder->add('localidad');
$builder->add('calle');
$builder->add('altura');
$builder->add('areaTel');
$builder->add('telefono');
$builder->add('areaCel');
$builder->add('celular');
$builder->add('email');
}
public function getName()
{
return 'datos_form';
}
}
UserType:
<?php
namespace Proyecto\LavocBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('usuario');
$builder->add('pass');
}
public function getName()
{
return 'user_form';
}
}
RegistroUsuarioType (this is the embedded form) :
<?php
namespace Proyecto\LavocBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Proyecto\LavocBundle\Entity\Datos;
class RegistroUsuarioType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder->add('usuario', 'text');
$builder->add('pass', 'text');
$builder->add('datos', new Datos());
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Proyecto\LavocBundle\Entity\User'
));
}
public function getName() {
return 'RegistroUsuario_form';
}
}
TablaController:
public function crearUsuarioDatosAction()
{
$request = $this->getRequest();
$usuario = new User();
$datos = new Datos();
$usuario->setDatos($datos);
$form = $this->createForm(new RegistroUsuarioType(), $usuario);
if($request->getMethod() == 'POST')
{
$form->bind($request);
if( $form->isValid() )
{
$em = $this->getDoctrine()->getEntityManager();
$em->persist($form);
$em->flush();
return $this->redirect($this->generateUrl('home'));
}
}
return $this->render('AtajoBundle:IngresarValores:crearUsuarioDatos.html.twig', array('form' => $form->createView()));
}
I thank you all very grateful for your help
$builder->add('datos', new Datos());
should be
$builder->add('datos', new DatosType());
Also your DatosType needs the data_class property set, add this function to your DatosType class:
public function setDefaultOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Proyecto\LavocBundle\Entity\Datos',
));
}
For newer versions of Symfony2 configureOptions instead of setDefaultOptions
Make sure your DatosType has:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
I have a question about service and Form in Symfony2,so I created my calss form and I hope to add a multiselect list of cities then I want to get list of cities from another class "city",so how I can call my class "city" in my form using "Service" to get a function "getcities" to return me a list of cities? (I dont use Doctrine here)...
Edit
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CityType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'choices' => array( /**
* Here I will call function getcities(return list of cities)
*/
)
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'gender';
}
}
class City.php :
Class City {
/**
* here i will get list of cities
*/
public function getcities()
{
.....
return $Listcities;
}
}
So I would like to use "Service" to call function "getcities" in form?
The form objects are not container-aware...at least, they're not meant to be. That said, your controller should use the service to get the cities, and then it should pass that list into the form object either through a constructor or a method.
Controller:
class SomethingController
{
public function someAction()
{
...
$cities = $this->get("citiesService")->getCities();
$form = $this->createForm(new SomeType($cities), $someEntity);
...
}
}
Form:
class SomeType extends AbstractType
{
private $cities;
public function __construct($cities)
{
$this->cities = $cities;
}
public function buildForm(FormBuilder $builder, array $options)
{
// Now you have access to $this->cities, so you can use it to build the form
}
}
Do you can set City object as form data object?
so it can looks like that...
$form = $this->createForm(new SomeType(), new City());
class SomeType extends AbstractType
{
public buildForm(FormBuilderInterface $builder, array $options)
{
$formFactory = $builder->getFormFactory();
$builder->addEventListener(
FormsEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formFactory) {
$event->getForm()->add(
$formFactory->createNamed(
'gender',
'choice',
null,
array(
'choices' => $event->getData()->getCites()
)
)
);
}
);
}
}