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;
}
Related
I'm using Symfony 5.0.11 trying to render a form. For some reason on the $form->createView() call it throws the error 'Failed to start the session: already started by PHP.' It is only happening on this form, my other forms are working fine. Other routes in this class are working as well. Here's my code...
Controller method:
<?php
namespace App\Controller;
use App\Entity\Site;
use App\Form\SiteType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class SitesController extends AbstractController
{
/**
* #Route("/sites/add", name="app_sites_add")
*/
public function site_add(EntityManagerInterface $em, Request $request)
{
$form = $this->createForm(SiteType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$site = $form->getData();
$em->persist($site);
$em->flush();
$this->addFlash('success', 'Site Successfully Added!');
return $this->redirectToRoute('sites');
} else {
return $this->render('sites/form.html.twig', ['siteForm' => $form->createView()]);
}
}
}
Form Method:
<?php
namespace App\Form;
use App\Entity\Site;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SiteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('url')
->add('type')
->add('is_active')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Site::class,
]);
}
}
Here's the trace:
/app/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php:148
/app/vendor/symfony/http-foundation/Session/Session.php:57
/app/vendor/symfony/security-csrf/TokenStorage/SessionTokenStorage.php:77
/app/vendor/symfony/security-csrf/CsrfTokenManager.php:72
/app/vendor/symfony/form/Extension/Csrf/Type/FormTypeCsrfExtension.php:77
/app/vendor/symfony/form/ResolvedFormType.php:168
/app/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php:111
/app/vendor/symfony/form/ResolvedFormType.php:161
/app/vendor/symfony/form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php:111
/app/vendor/symfony/form/Form.php:1039
/app/src/Controller/SitesController.php:98
App\Controller\SitesController->site_edit(Site $id, EntityManagerInterface $em, Request $request) …
return $this->render('sites/form.html.twig', [
'siteForm' => $form->createView()
]);
}
/app/vendor/symfony/http-kernel/HttpKernel.php:157
/app/vendor/symfony/http-kernel/HttpKernel.php:79
/app/vendor/symfony/http-kernel/Kernel.php:191
/app/public/index.php:25
}```
Thanks for your help.
I found the issue. It was in the security configuration. I'm using the guard authenticator and I hadn't setup a firewall rule for these forms.
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.
I'm working on a project using Doctrine-MongoDB and Symfony.
I've embedded a document into an other using \#EmbedMany annotation.
Here are the documents :
MusicalInfos :
<?php
// app/Resources/Document/Musical.php
namespace AppBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document(collection="bv_musical_infos")
*/
class MusicalInfos
{
/**
* #MongoDB\Id(strategy="auto")
*/
protected $id;
/**
* #MongoDB\ReferenceOne(targetDocument="User")
*/
protected $user;
/**
* #MongoDB\EmbedMany(targetDocument="InstrumentsPlayed")
*/
protected $instruments = array();
And the embedded document :
<?php
namespace AppBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\EmbeddedDocument
*/
class InstrumentsPlayed
{
/**
* #MongoDB\ReferenceOne(targetDocument="Instruments")
*/
protected $instrument;
/**
* #MongoDB\Field(type="int")
*/
protected $practiceLevel;
After that I've created a form to fill those documents :
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class MusicalInfosType extends AbstractType
{
private $dm;
public function __construct($dm)
{
$this->dm = $dm;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('instruments', 'collection', array(
'type' => new InstrumentsPlayedType($this->dm),
'allow_add' => true,
'allow_delete' => true
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Document\MusicalInfos',
));
}
public function getName()
{
return 'musical_infos';
}
}
Controller
<?php
//...
if('POST' === $request->getMethod()) {
$form->bind($request);
if($form->isValid()) {
$user = $this->container->get('security.context')->getToken()->getUser();
$musicalInfos->setUser($user);
$dm->persist($musicalInfos);
$dm->flush();
$response = new JsonResponse();
$response->setData(array('registred_musical' => true));
return $response;
}
But when I try this I always obtain this exception :
Warning: spl_object_hash() expects parameter 1 to be object, array given
I don't know why ...
Thanks for your help !
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.
Ok here is a quick overview of what I am trying to do. I have a "Client" entity with a relationship to a "ClientDomain" entity. I need to have a form that will show me a list of all the ClientDomains for a given client. In the controller I know what client i need to filter for but im unsure how to pass that information to the formBuilder.
Heres what i have so far:
//src/NameSpace/ClientBundle/Entity/Client.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Client{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $client_id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="ClientDomain", mappedBy="client")
*/
protected $domains;
...
}
And the form:
//src/LG/ClientBundle/Form/ClientDomainSelectionForm.php
namespace LG\ProjectBundle\Form\Projects;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ClientDomainSelectionForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('client_domain', 'entity', array(
'class' => 'LG\ClientBundle\Entity\ClientDomain',
'query_builder'=> function(EntityRepository $er) {
return $er->createQueryBuilder('cd')
/* NEEDS TO FIND DOMAINS BY CLIENT X */
},
'property' => 'domain',
'label' => 'Domain: '
));
}
}
And then finally the controller:
//src/LG/ClientBundle/Controller/DomainSelectorController.php
namespace LG/ClientBundle/Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use LG\ClientBundle\Entity\Client;
use LG\ClientBundle\Entity\ClientDomain;
use LG\ClientBundle\Entity\ClientActiveDomain;
use LG\ClientBundle\Form\ClientDomainSelectionForm;
/**
* #Route("")
*/
class DomainSelectorController extends Controller{
/**
* #Route("/client/{client_slug}/select-domain", name="lg.client.clientdomainselection.selectclient")
* #Template
*/
public function selectDomainAction(Request $request, Client $client){
$activeDomain = new ClientActiveDomain();
$form = $this->createForm(new ClientDomainSelectionForm(), $activeDomain );
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($activeDomain );
$em->flush();
return $this->redirect(/*huge long url*/);
}
}
return array(
'form' => $form->createView(),
);
}
}
As you can see I have access to the client entity in the controller im just not sure how to give that to the form builder so that it will only return domains for the current client.
I Have found the answer, you just need to add a constructor to the form and pass in the client from the controller like so:
//src/LG/ClientBundle/Form/ClientDomainSelectionForm.php
namespace LG\ProjectBundle\Form\Projects;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ClientDomainSelectionForm extends AbstractType {
protected $client;
public function __construct(Client $client) {
$this->client = $client;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$client = $this->client;
$builder->add('client_domain', 'entity', array(
'class' => 'LG\ClientBundle\Entity\ClientDomain',
'query_builder'=> function(\Doctrine\ORM\EntityRepository $er) use ($client) {
return $er->createQueryBuilder('cd')
->where('cd.client = :client')
->orderBy('cd.domain', 'ASC')
->setParameter('client',$client->getClientId());
},
'property' => 'domain',
'label' => 'Domain: '
));
}
}
And Then in the controller:
//src/LG/ClientBundle/Controller/DomainSelectorController.php
...
public function selectDomainAction(Request $request, Client $client){
...
$form = $this->createForm(new ClientDomainSelectionForm($client), $activeDomain );
...
}
...