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.
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 want to create entity with symfony form and DTO. I tried to do DTO and form like I've seen on symfonycast. But there's something wrong and I can't figure it out.
After sending json file via postman I get an error:
Typed property App\Form\Model\CreateFacilityDTO::$pitchTypes must not be accessed before initialization (500 Internal Server Error)
postman body:
{
"name": "legia",
"pitchTypes": ["basketball"],
"address": "kosynierów"
}
Can You tell me what I'm doing wrong?
<?php
namespace App\Controller;
use App\Entity\Facility;
use App\Form\CreateFacilityFormType;
use App\Form\Model\CreateFacilityDTO;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #method saveEntities(array $array)
*/
class CreateFacilityAction extends AbstractController
{
/**
* #Route("/api/create/facility", name="create_facility")
* #param Request $request
* #return Response
*/
public function __invoke(Request $request, EntityManagerInterface $em)
{
$form = $this->createForm(CreateFacilityFormType::class);
$data = json_decode($request->getContent(), true);
$form->submit($data);
// if ($form->isSubmitted() && $form->isValid()) {
/** #var CreateFacilityDTO $facilityDto */
$facilityDto = $form->getData();
$createFacility = new Facility($facilityDto->name, $facilityDto->pitchTypes,
$facilityDto->address);
$em = $this->getDoctrine()->getManager();
$em->persist($createFacility);
$em->flush();
// return new Response($data, 201);
// }
return new Response($createFacility, 201);
}
}
<?php
namespace App\Form;
use App\Form\Model\CreateFacilityDTO;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CreateFacilityFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('pitchTypes')
->add('address');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => CreateFacilityDTO::class,
]);
}
}
<?php
namespace App\Form\Model;
class CreateFacilityDTO
{
public string $name;
public array $pitchTypes;
public string $address;
}
I try add fields with form events in symfony but fields is add automatically when refresh page ... scenario is when I fill input email fields body is display ...
code PostType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class PostType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('titl')->add('email')->add('phone');
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event){
$post = $event->getData();
$form = $event->getForm();
if(!empty($post->getEmail))
{
$form->add('body');
}
});
$builder->add('save', SubmitType::class);
}/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Post'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_post';
}
}
as it says in comment you have to use Javascript to do this, for example make an ajax request to submit the form and to apply, always in javascript, the HTML response.
There is a great video tutorial for this (in french) https://www.youtube.com/watch?v=F0Z-D3MSjA0&t=1504s
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]);
With symfony I am trying to listen to the preset data event on a form declared as a service.
I have a parent form in which I call ->add('unit', 'fmu_unit')
EDIT : I add here a full simple example. It's not working as expected.
My controller:
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\MarketPlace\Product;
use AppBundle\Form\UnitTestType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class DefaultController extends Controller
{
/**
* #Route("/", name="home")
* #Method({"POST", "GET"})
* #Template(":Default:index.html.twig")
*/
public function indexAction()
{
$formManager = $this->get('form_manager');
$product = new Product();
$unit = $this->getDoctrine()->getRepository('AppBundle:FoodAnalytics\Unit')->findOneByName('g');
$product->setUnit($unit);
$form = $formManager->createForm(new UnitTestType(), $product ,'POST', 'home');
return array('form' => $form->createView());
}
}
My first Type (UnitTest):
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UnitTestType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('unit', 'fmu_unit')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\MarketPlace\Product'
));
}
/**
* #return string
*/
public function getName()
{
return 'unit_test';
}
}
My second type (the service one in which I try to use Form Events):
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UnitType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
//This is not working
$form->add('unit','entity', array(
'class' => 'AppBundle:FoodAnalytics\Unit'
));
});
//This is working
// $builder->add('unit','entity', array(
// 'class' => 'AppBundle:FoodAnalytics\Unit'
// ));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'inherit_data' => true,
));
}
/**
* #return string
*/
public function getName()
{
return 'fmu_unit';
}
}
The declaration as a service:
services:
unit.type:
class: %unit.type.class%
tags:
- { name: form.type, alias: fmu_unit }
The view : {{ form(form) }}
Nothing shows up but the label when I use the form Events. It works fine if I don't use it.