I have problem with form collection. I get an error:
Notice: Array to string conversion in /home/.../vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php line 103
i tried foreach (like is in doctrine documentation) and perist each object, but I get an error:
The class 'Doctrine\Common\Collections\ArrayCollection' was not found in the chain configured namespaces FOS\UserBundle\Entity, Ix\UserBundle\Entity, Ix\x\Entity, FOS\UserBundle\Model
Below is my code:
Entity:
/**
* #ORM\Entity
* #ORM\Table(name="availability")
*/
class Availability
{
public function __construct()
{
$this->availabilityFlexible = new \Doctrine\Common\Collections\ArrayCollection();
$this->availabilitySession = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="description", type="string", length=255, nullable=true)
*/
protected $description;
/**
* #ORM\OneToMany(targetEntity="AvailabilityFlexible", mappedBy="availability", cascade={"persist", "remove"})
*/
protected $availabilityFlexible;
/**
* #ORM\OneToOne(targetEntity="AvailabilityFull", mappedBy="availability", cascade={"persist", "remove"})
*/
protected $availabilityFull;
/**
* #ORM\OneToMany(targetEntity="AvailabilitySession", mappedBy="availability", cascade={"persist", "remove"})
*/
protected $availabilitySession;
// AvailabilityFlexible.php
/**
* #ORM\ManyToOne(targetEntity="Availability", inversedBy="availabilityFlexible", cascade={"persist"})
* #ORM\JoinColumn(name="availability_id", referencedColumnName="id")
*/
protected $availability;
// AvailabilityFull.php
/**
* #ORM\OneToOne(targetEntity="Availability", inversedBy="availabilityFull", cascade={"persist"})
* #ORM\JoinColumn(name="availability_id", referencedColumnName="id")
*/
protected $availability;
// AvailabilitySession.php
/**
* #ORM\ManyToOne(targetEntity="Availability", inversedBy="availabilitySession", cascade={"persist"})
* #ORM\JoinColumn(name="availability_id", referencedColumnName="id")
*/
protected $availability;
Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description')
->add('AvailabilitySession', 'collection', array(
'type' => new AvailabilitySessionFormType(),
'allow_add' => true,
'prototype' => true,
'by_reference' => false,
'allow_delete' => true,
))
->add('AvailabilityFull', new AvailabilityFullFormType())
->add('AvailabilityFlexible', 'collection', array(
'type' => new AvailabilityFlexibleFormType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
));
}
Controller:
public function addAvailabilityAction(Request $request)
{
$availability = new Availability;
$availability->getAvailabilityFlexible()->add(new AvailabilityFlexible);
$availability->getAvailabilityFlexible()->add(new AvailabilityFlexible);
$form = $this->createForm(new AvailabilityFormType(), $availability);
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$availability = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->perist($availability);
$em->flush();
}
}
}
Properties $availability are in different class.
AvailabilityFlexible.php
AvailabilityFull.php
AvailabilitySession.php
This is relationship to Availability Entity.
Related
I'm trying to upload multiple files on Symfony but when the form is submitted the form image field returns a null object like this
object(Doctrine\Common\Collections\ArrayCollection)#1455 (1) {
["elements":"Doctrine\Common\Collections\ArrayCollection":private]=>
array(1) {
[0]=>
object(AdminBundle\Entity\ImageNew)#1717 (5) {
["nom":"AdminBundle\Entity\ImageNew":private]=>
NULL
["path":"AdminBundle\Entity\ImageNew":private]=>
NULL
["idimage":"AdminBundle\Entity\ImageNew":private]=>
NULL
["categorie":"AdminBundle\Entity\ImageNew":private]=>
NULL
["file":"AdminBundle\Entity\ImageNew":private]=>
NULL
}
}
}
But when I get files directly inside the request files attributes file exist. I've tried to upload a file by accessing the attribute in the request, it works but it still wants to upload file via Symfony $form request handler.
That's my controller
public function addColorAction(Request $request, Article $article)
{
$couleur = new Couleur();
$form = $this->createForm('AdminBundle\Form\CouleurType', $couleur);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$files = $couleur->getImages();
echo "<pre>";
var_dump($files); die;
$imgs = $request->files->get("adminbundle_couleur")["images"];
foreach ($imgs as $img) {
$image = new ImageNew();
$image->setFile($img["file"]);
$image->upload();
$couleur->addImage($image);
$em->persist($image);
$em->flush();
}
$color_art_dispo = new CouleurArticleDispo();
$color_art_dispo->setEnStock(true);
$color_art_dispo->setArticle($article);
$color_art_dispo->setCouleur($couleur);
$em->persist($couleur);
$em->persist($color_art_dispo);
$em->flush();
return $this->redirectToRoute('article_index');
}
return $this->render(
'admin/article/couleur/new.html.twig', array(
'couleur' => $couleur,
'form' => $form->createView(),)
);
}
The couleur entity
class Couleur
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
* #Assert\NotBlank(message="Veuillez entrer le nom de la couleur")
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="code_couleur", type="string", length=6)
* #Assert\NotBlank(message="Veuillez entrer le code couleur correspondant")
* #Assert\Length(
* min=6,
* max=6,
* minMessage="Le code couleur n'est pas correct.",
* maxMessage="Le code couleur n'est pas correct.",
* )
*/
private $codeCouleur;
/**
*
* #ORM\OneToMany(targetEntity="CouleurArticleDispo", mappedBy="_couleurs")
*/
private $colorArticles;
/**
* Many Colors have Many Images.
*
* #ORM\ManyToMany(targetEntity="ImageNew",cascade={"remove"})
* #ORM\JoinTable(name="color_images",joinColumns={#ORM\JoinColumn(name="color_id",referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="image_id", referencedColumnName="idimage", onDelete="CASCADE")}
* )
*/
private $images;
public function __toString()
{
return (string) $this->getNom();
}
/**
* Class Constructor
*/
public function __construct()
{
$this->images = new ArrayCollection();
}
}
This is the image entity
class ImageNew
{
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=100)
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="path", type="string", length=255)
*/
private $path;
/**
* #var integer
*
* #ORM\Column(name="idimage",type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idimage;
/**
* #var \AdminBundle\Entity\Categorie
*
* #ORM\ManyToOne(targetEntity="AdminBundle\Entity\Categorie",cascade={"persist"},inversedBy="slides")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_categorie",referencedColumnName="idcategorie",nullable=true,onDelete="SET NULL")
* })
*/
private $categorie;
/**
* #Assert\NotNull()
* #Assert\File(
* maxSize = "6000k",
* mimeTypes = {"image/png", "image/jpg", "image/bmp"},
* mimeTypesMessage = "Please upload a valid Image File (PNG, JPEG or BMP)"
* )
*/
private $file;
public function __toString()
{
return (string) $this->getPath();
}
}
and this is the couleur type
class CouleurType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('codeCouleur')
->add(
'images', CollectionType::class,
array(
'label' => 'Images de l\'article ayant cette couleur',
'entry_type' => ImageNewFileType::class,
'allow_add' => true,
'allow_delete' => true,
)
);
}
}
and finally the image type
class ImageNewFileType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'file', FileType::class,
[
'mapped' => false,
'required' => false,
'attr' => array(
'accept' => 'image/*',
)
]
);
}
}
I think you shouldn't add the mapped => false option in the ImageNewFileType.
https://symfony.com/doc/current/reference/forms/types/form.html#mapped
As you can see in the documentation the field is ignored when writing to the object.
the error was inside the ImageNewFileType, because of property 'mapped' => false, the form wasn't set uploaded files information in file field of ImageNew Entity, so I've replaced this :
class ImageNewFileType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'file', FileType::class,
[
'mapped' => false,
'required' => false,
'attr' => array(
'accept' => 'image/*',
)
]
);
}
}
by this:
class ImageNewFileType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'file', FileType::class,
[
'mapped' => true(or live this empty because by default it is true),
'required' => false,
'attr' => array(
'accept' => 'image/*',
)
]
);
}
}
I am trying to submit a JSON through a complex form. I can't figure out what I am missing. The "normal" form is functioning. I am able to get serialized data with groups.
Class TaskBoard
class TaskBoard
{
/**
* #var integer $id id
*
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var \DateTime $createdTime createdTime
*
* #ORM\Column(type="datetime")
* #Assert\DateTime()
*/
private $createdTime;
/**
* #var \DateTime $lastUpdatedTime lastUpdatedTime
*
* #ORM\Column(type="datetime", nullable=true)
* #Assert\DateTime()
*/
private $lastUpdatedTime;
/**
* #var string $name name
*
* #ORM\Column(type="string", length=20)
* #Assert\Type("string")
* #Assert\NotBlank()
* #Assert\Length(
* min = 2,
* max = 20,
* minMessage = "The name must be at least {{ limit }} characters long",
* maxMessage = "The name cannot be longer than {{ limit }} characters"
* )
*/
private $name;
/**
* #var App\Entity\User $user user
*
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="taskboards", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #var string $description description
*
* #ORM\Column(type="text", nullable=true)
* #Assert\Type("string")
*/
private $description;
/**
* #var App\Entity\Status $status status
*
* #ORM\ManyToOne(targetEntity="App\Entity\Status", inversedBy="taskboards", cascade={"persist"})
* #ORM\JoinColumn(name="status_id", referencedColumnName="id", nullable=false)
*/
private $status;
/**
* #var boolean $completed completed
*
* #ORM\Column(type="boolean")
*/
private $completed;
/**
* #var \DateTime $deadLine deadLine
*
* #ORM\Column(type="date", nullable=true)
* #Assert\DateTime()
* #Assert\GreaterThanOrEqual("today")
*/
private $deadLine;
Class Status
class Status
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=20)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\TaskBoard", mappedBy="status", cascade={"persist"})
*/
public $taskboards;
Class User
class User extends BaseUser
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\TaskBoard", mappedBy="user", cascade={"persist"})
*/
public $taskboards;
Form
class TaskBoardType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', TextType::class)
->add('user', EntityType::class, array(
'class' => User::class,
'choice_label' => 'username',
)
)
->add('description', TextareaType::class, array(
'required' => false
))
->add('status', EntityType::class, array(
'class' => Status::class,
'choice_label' => 'name',
)
)
->add('deadLine', DateTimeType::class)
;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => TaskBoard::class,
'csrf_protection' => false,
]);
}
Controller
class TaskBoardAPIController extends AbstractController {
public function postTaskBoard(Request $request) {
$form = $this->createForm(TaskBoardType::class, new TaskBoard());
$data = json_decode(
$request->getContent(), true
);
var_dump($data);
$form->submit($data);
if (!$form->isValid()) {
return new JsonResponse(
[
'status' => 'error',
'errors' => $form->getErrors(),
'form' => $form,
], JsonResponse::HTTP_BAD_REQUEST
);
}
$this->entityManager->persist($form->getData());
$this->entityManager->flush();
return new JsonResponse(
[
'status' => 'ok',
], JsonResponse::HTTP_CREATED
);
}
JSON sent
{
"name": "XXX",
"user": {
"id": 1,
"username": "BFA"
},
"description": "XXXXXXXXXXXXXXX",
"status": {
"id": 1,
"name": "To Do"
},
"completed": false
}
The form is not valid and blank in the JsonResponse.
I based myself on : https://codereviewvideos.com/course/beginners-guide-back-end-json-api-front-end-2018/video/symfony-4-json-api-form-submission
and Deserialize an entity with a relationship with Symfony Serializer Component
Thanks for your help.
What was wrong was the JSON input.
The form does this :
class TaskBoardType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', TextType::class)
->add('user', EntityType::class, array(
'class' => User::class,
'choice_label' => 'username',
)
)
->add('description', TextareaType::class, array(
'required' => false
))
->add('status', EntityType::class, array(
'class' => Status::class,
'choice_label' => 'name',
)
)
->add('deadLine', DateTimeType::class)
;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => TaskBoard::class,
'csrf_protection' => false,
]);
}
When checking the code generated from the form, this is the result :
<div>
<label for="task_board_user" class="required">User</label>
<select id="task_board_user" name="task_board[user]">
<option value="1">XXX</option>
<option value="2">XXX</option>
</select>
</div>
Thus the form is expecting directly an INT/ID.
By changing the JSON as follow it goes through validation :
{
"name": "XXXO",
"user": 1,
"description": "XXXXXXXXXXXXXXX",
"status": 1
}
Your forgot to handle and get form submission.
Note that you will need to get request data using something like
$form = $this->createFormBuilder($task)
... add fields
->getForm();
$form->handleRequest($request); // handling request
if ($form->isSubmitted() && $form->isValid()) {
// $form->getData() holds the submitted values
// but, the original `$task` variable has also been updated
$task = $form->getData();
// ... perform some action, such as saving the task to the database
// for example, if Task is a Doctrine entity, save it!
// $entityManager = $this->getDoctrine()->getManager();
// $entityManager->persist($task);
// $entityManager->flush();
return $this->redirectToRoute('...');
}
for more, see Handling Form Submissions
I work on a OneToMany association in my database. This association work perfectly when I try to add data from fixtures and when I try to return data from database.
The problem is with my FormType CommandType which does'nt work. Symfony and Doctrine return this error message :
An exception occurred while executing 'INSERT INTO command_product (quantity, command_id, product_id) VALUES (?, ?, ?)' with params [3, null, 1]:\n\nSQLSTATE[23000]: Integrity constraint violation: 1048 Column 'command_id' cannot be null
CommandType's code :
class CommandType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('establishment', EntityType::class, array(
'class' => Company::class,
'required' => true
))
->add('dateCreation', DateTimeType::class, array(
'widget' => 'single_text',
'format' => 'yyyy-MM-dd',
'required' => true
))
->add('contains', CollectionType::class, array(
'entry_type' => CommandProductType::class,
'required' => true,
'allow_add' => true
))
->add('state',TextType::class, array(
'required' => true
))
->add('totalAmount', MoneyType::class, array(
'required' => true
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Command::class
));
}
public function getBlockPrefix()
{
return 'appbundle_command';
}
}
CommandProductType's code :
class CommandProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('quantity', NumberType::class, array(
'required' => true
))
->add('product', EntityType::class, array(
'class' => Product::class,
'required' => true
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => CommandProduct::class
));
}
public function getBlockPrefix()
{
return 'appbundle_commandproduct';
}
}
Command's code class :
class Command
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Company $establishment
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Company")
* #ORM\JoinColumn(name="establishment_id", referencedColumnName="id", nullable=false)
*/
private $establishment;
/**
* #var DateTime $dateCreation
*
* #ORM\Column(name="dateCreation", type="datetime", nullable=false)
* #Assert\Type("datetime")
*/
private $dateCreation;
/**
* #var string $state
*
* #ORM\Column(name="state", type="string", length=255, nullable=false)
* #Assert\Type("string")
*/
private $state;
/**
* #var float $totalAmount
*
* #ORM\Column(name="totalAmount", type="float", precision=10, scale=2, nullable=false)
* #Assert\NotBlank()
* #Assert\Type(type="float")
*/
private $totalAmount;
/**
* #var mixed $contains
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\CommandProduct", mappedBy="contain", cascade={"persist", "remove"})
*/
private $contains;
public function __construct()
{
$this->contains = new ArrayCollection();
}
/**
* #var CommandProduct $commandProduct
*/
public function addContain(CommandProduct $commandProduct = null)
{
$commandProduct->setContain($this);
$this->contains->add($commandProduct);
}
/**
* #param CommandProduct $commandProduct
*/
public function removeContain(CommandProduct $commandProduct)
{
if ($this->contains->contains($commandProduct)) {
$this->contains->removeElement($commandProduct);
}
}
}
CommandOrder's code class :
class CommandProduct
{
/**
* #var int $id
*
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Command $contain
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Command", inversedBy="contains")
* #ORM\JoinColumn(name="command_id", referencedColumnName="id", nullable=false)
*/
private $contain;
/**
* #var int $quantity
*
* #ORM\Column(name="quantity", type="integer", nullable=true, options={"default": 1})
* #Assert\NotBlank()
* #Assert\Type(type="int")
*/
private $quantity;
/**
* #var Product $product
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Product")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
}
I'm learning by myself the symfony framework (my job is not about developing, I'm not a developer) and I find out most of case the solution but here, is one what I didn't know how to manage.
I have 2 entity :
Product:
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="ProductBundle\Repository\ProductRepository")
* #UniqueEntity("productNumber")
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="productNumber", type="string", length=255, unique=true)
* #Assert\Regex(
* pattern="/[0-9][.][0-9]{3}/",
* message="It should be like 1.234"
* )
*/
private $productNumber;
/**
* #ORM\ManyToOne(targetEntity="ProductGroup")
*/
private $productGroup;
/**
* Constructor
*/
public function __construct()
{
}
}
Camera :
/**
* Camera
*
* #ORM\Table(name="camera")
* #ORM\Entity(repositoryClass="ProductBundle\Repository\CameraRepository")
*/
class Camera
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="modele", type="string", length=255, unique=true)
*/
private $modele;
/**
* #var string
*
* #ORM\Column(name="description", type="text")
*/
private $description;
/**
*
* #ORM\ManyToOne(targetEntity="Product")
*/
private $product;
/**
* #ORM\ManyToMany(targetEntity="CustomField", inversedBy="camera", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $customFields;
/**
* Constructor
*/
public function __construct()
{
$this->customFields = new ArrayCollection();
}
}
My form :
namespace ProductBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
class CameraType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('product', EntityType::class, [
'class' => 'ProductBundle:Product',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('p')
->select('p')
->leftJoin('ProductBundle:Camera', 'c', 'WITH', 'c.product = p.id')
->where('c.product IS NULL')
;
},
'attr' => [
'required' => true,
],
'choice_label' => 'productNumber',
])
->add('modele', TextType::class, [
'label' => "Modele",
])
->add('description', TextType::class, [
'label' => "Description",
])
->add('customFields', CollectionType::class, [
'entry_type' => CustomFieldType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'attr' => [
'class' => 'customfield'
]
])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'ProductBundle\Entity\Camera'
));
}
}
When I add a camera, I would like only the Product:productNumber where are available (not take by a camera), the querybuilder is working but my issue concern the edit form, it show only available productNumber so it's changing every time I need to edit this camera.
What can I handle this ? Should I try to found another way to add a productNumber ? do you have a "trick" ?
I hope you will understand the problem and my english because it's not my first language.
Have a nice day.
Edit : I'm on Symfony 3.1.4
I presume on new form your choice field shows only unused ProductBundle:Camera entity, and on edit form it should show saved ProductBundle:Camera entity and all unused ones.
You should look into Form Event Subscribers
You need to implement two event listeners PRE_SET_DATA and PRE_SUBMIT.
Here is one way to do it. Something like this works on SF 2.8
First you will have to create product entity form from custom ProductFieldSubscriber which becomes EventSubscriberInterface:
$builder->addEventSubscriber(new ProductFieldSubscriber('product', [])
Now ProductFieldSubscriber should look something like this (untested)
namespace ProductBundle\Form\EventListener;
use Symfony\Component\Form\FormInterface,
Symfony\Component\Form\FormEvent,
Symfony\Component\EventDispatcher\EventSubscriberInterface,
Symfony\Component\Form\FormEvents,
Doctrine\ORM\EntityRepository,
Symfony\Bridge\Doctrine\Form\Type as DoctrineTypes
;
class ProductFieldSubscriber implements EventSubscriberInterface
{
private $propertyPathToSelf;
public function __construct($propertyPathToSelf, array $formOptions=[]) {
$this->propertyPathToSelf = $propertyPathToSelf;
$this->formOptions = $formOptions;
}
public static function getSubscribedEvents() {
return [
FormEvents::PRE_SET_DATA => 'onPreSetData',
FormEvents::PRE_SUBMIT => 'onPreSubmit',
];
}
private function addForm(FormInterface $form, $selfId = null) {
$formOptions = array_replace_recursive ([
'class' => 'ProductBundle:Product',
'placeholder' => null,
'compound' => false,
'query_builder' => function (EntityRepository $er) use ($selfId) {
$qb = $er->createQueryBuilder('p')
->select('p')
->leftJoin('ProductBundle:Camera', 'c', 'WITH', 'c.product = p.id')
->where('c.product IS NULL')
;
if (null !== $selfId) {
$qb
->orWhere($qb->expr()->eq('p.product', ':existingId'))
->setParameter('existingId', $selfId->getId())
;
}
return $qb;
},
],
$this->formOptions
);
if ($selfId) {
$formOptions['data'] = $selfId;
}
$form->add($this->propertyPathToSelf, DoctrineTypes\EntityType::class, $formOptions);
}
public function onPreSetData(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$selfIdTypeMethod = "get{$this->propertyPathToSelf}";
$selfId = $data->$selfIdTypeMethod();
$this->addForm($form, $selfId);
}
public function onPreSubmit(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
$selfId = array_key_exists($this->propertyPathToSelf, $data) ? $data[$this->propertyPathToSelf] : null;
$this->addForm($form, $selfId);
}
}
Query builder would be simpler if you had mapped entity relations.
Bonus update:
form option 'placeholder' => null, takes care that no default 'empty' option is available.
form option 'required' => true, forces html5 form popup validation.
Then you should use something like entity #assert notations and use validator constraints on entity attribute:
use Symfony\Component\Validator\Constraints as Assert;
/**
* #var string
*
* #Assert\NotNull()
* #ORM\Column(name="modele", type="string", length=255, unique=true)
*/
private $modele;
You could also disallow edit form from opening via controller editAction (maybe some redirect) and twig, where you could hide edit button.
This error appears when I try to persist my entity :
Warning: spl_object_hash() expects parameter 1 to be object, integer
given in
C:\wamp\www\projet\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php
line 1601
This is my entity Site :
/**
* Etablissement
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Intranet\RhBundle\Entity\SiteRepository")
*/
class Site {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id; /**
* #ORM\OneToMany(targetEntity="Intranet\RhBundle\Entity\SiteEtablissement",
mappedBy="site", cascade={"persist", "remove"}, orphanRemoval=true)
* */
private $siteEtablissements;
}
This is my entity Etablissement :
/**
* Etablissement
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Intranet\RhBundle\Entity\EtablissementRepository")
*/
class Etablissement {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
And the entity for the relation :
/**
* #ORM\Entity(repositoryClass="Intranet\RhBundle\Entity\SiteEtablissementRepository")
*/
class SiteEtablissement {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Intranet\RhBundle\Entity\Site", inversedBy="siteEtablissements")
* #ORM\JoinColumn(name="site_id", referencedColumnName="id")
*/
private $site;
/**
* #ORM\ManyToOne(targetEntity="Intranet\RhBundle\Entity\Etablissement")
* #ORM\JoinColumn(name="etablissement_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $etablissement;
}
So I don't understand why I have this error, my mapping seems to be ok. I don't have any entity error and I have no error before to persist $site in my controller.
This is my controller :
public function gererEtablissementAction(Request $request, Site $site) {
$form = $this->createForm(new GererSiteEtabType(), $site);
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
foreach ($site->getSiteEtablissements() as $sitEtab)
if (is_null($sitEtab->getAutor()))
$sitEtab->setAutor($this->getUser());
$em->persist($site);
$em->flush();
$this->get('session')->getFlashBag()->add('success', "Les établissement du site " . $site->getNom() . " ont été modifiés avec succès !");
return $this->redirect($this->generateUrl('intranet_rh_homepage_employes'));
} else
$this->get('session')->getFlashBag()->add('danger', "Erreur de formulaire !");
}
return $this->render('IntranetRhBundle:Site:gererEtablissement.html.twig', array('site' => $site, 'form' => $form->createView()));
}
When I print $site->getSiteEtablissements, there are Site and Etablissement on each side of each object...
This is the build form :
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('siteEtablissements', 'collection', array(
'type' => new SiteEtablissementType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false
))
;
}
And the subform :
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('etablissement', 'entity', array(
'label' => 'Etablissement :',
'class' => 'IntranetRhBundle:Etablissement',
'property' => 'nom',
'query_builder' => function(EtablissementRepository $er) {
// récupère les établissements triés par ordre alpabétique croissant de nom
return $er->createQueryBuilder('e')
->orderBy('e.nom', 'ASC');
},
'attr' => array('class' => 'form-control')
))
;
}
I find the error : a bad initialsiation of Autor.
Autor was initialise at "0" and in the loop I test if is_null. So 0 is not null and when it persists, there is an error ebcause there is no user with id 0...