I need a help on how to upload a pdf file in symfony. In general, the relationship between the student and the pdf card is as follows: A single student can have several pdf cards and one card for a single student. The entity sheet is as follows:
class FichePDF
{
/**
* #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)
*/
private $nom;
/**
* #ORM\Column(type="string")
*
* #Assert\NotBlank(message="Please, upload the evaluation file as a PDF file.")
* #Assert\File(mimeTypes={ "application/pdf" })
*/
private $file;
/**
* #var string
*
* #ORM\Column(name="Path", type="string", length=255)
*/
private $path;
/**
* #ORM\ManyToOne(targetEntity="Polytech\SkillsBundle\Entity\Utilisateur", inversedBy="fichesPdf")
* #ORM\JoinColumn(nullable=false)
*
*/
private $etudiant;
/**
* #ORM\OneToOne(targetEntity="Polytech\SkillsBundle\Entity\SousOccasion")
* #ORM\JoinColumn(name="ssocc_id", referencedColumnName="id")
*/
private $ssocc;
With getters and setters of course. For the student entity I added this line
/**
* #ORM\OneToMany(targetEntity="Polytech\SkillsBundle\Entity\FichePDF" , mappedBy="etudiant", cascade={"remove"})
*/
private $fichesPdf;
I have a form that retrieves information about several entities in my application such as teaching units, exams and students and then retrieves a pdf file.
<?php
namespace Polytech\SkillsBundle\Form\Rapport;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class FicheOccasionType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('ues', EntityType::class,
array(
'class' => 'Polytech\SkillsBundle\Entity\UE',
'attr' => array('class' => 'browser-default ue'),
'choice_label' => 'nom',
'label' => false,
'required' => false,
'placeholder' => 'Choisissez une UE'
)
)
->add('etudiants', EntityType::class,
array(
'class' => 'Polytech\SkillsBundle\Entity\Utilisateur',
'attr' => array('class' => 'browser-default etudiants'),
'choice_label' => 'nom',
'label' => false,
'required' => false,
'placeholder' => 'Choisissez un utilisateur'
)
)
->add('file', FileType::class, array('label' => 'PDF File'))
->add('submit', HiddenType::class)
->add('export', ButtonType::class, array('label' => 'Exporter'))
->add('import', ButtonType::class, array('label' => 'Import'));
}
public function getName()
{
return 'fiche_occasion';
}
}
How can I retrieve the file as Uploaded file and add it to the database. I read the documentation and it's not exactly what I do. Can you please help me
You are halfway there as is described in the Documentation:
https://symfony.com/doc/current/controller/upload_file.html
The following steps you already did:
Adding the property to your entity
Adding the upload element to the form
Now you have to handle the uploaded file and add the upload path to the entity. In your controller where you handle the form you now have to do the following:
$fiche = new FichePDF();
$form = $this->createForm(FichePDF::class, $fiche);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$file = $fiche->getFile();
// Generate a unique name for the file before saving it
$fileName = md5(uniqid()).'.'.$file->guessExtension();
// Move the file to the directory where brochures are stored
$file->move(
$this->getParameter('upload_directory'),
$fileName
);
// Update the 'fichePDF' property to store the PDF file name
// instead of its contents
$fiche->setFile($fileName);
// Persist $fichePDF and do whatever else you want to
}
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 have two classes User.php and Group.php, extended from the classes defined in fosuserbundle .
Besides this I have a ManyToMany relation between them .
The idea of the application is when I add a new user, I assign one or many groups to user (I am using material design multi select option). The problem arises when user does not have any group(s) assigned (e.g when group option is empty). I get this javascript error in the console:
An invalid form control with name='user[groups][]' is not focusable.
So to resolve this issue, I need to provide a default value for group field. How to set a default user group if group is not selected ? My classes are defined as follow.
UserType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'username',
null,
array(
'attr' => array(
'placeholder' => 'username'
),
))
->add(
'email',
null,
array(
'attr' => array(
'placeholder' => 'email'
),
))
/*->add('groups', EntityType::class, array(
'class' => 'AppBundle\Entity\Group',
'choice_label' => 'name',
'expanded' => false,
'multiple' => false
)) */
->add('groups', EntityType::class, array(
'class' => 'AppBundle\Entity\Group',
'choice_label' => 'name',
'attr'=>array(
'class' => 'mdb-select'
),
'multiple' => true,
))
->add('plainPassword', RepeatedType::class, array(
'invalid_message' => 'Les mots de passe doivent être identiques.',
'first_options' => array('label' => 'Mot de passe'),
'second_options' => array('label' => 'Répétez le mot de passe'),
))
//<input id="input-id" type="file" class="file" multiple data-show-upload="false" data-show-caption="true">
->add('enabled', null, array(
'required' => false,
))
->add('imageFile',VichFileType::class, [
'required' => false,
'download_link' => true,
'attr'=>array(
'type' => 'file',
'onchange' => 'loadFile(event)'), // not mandatory, default is true
])
;
}
GroupEntity.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('roles', CollectionType::class, array(
'entry_type' => ChoiceType::class,
'entry_options' => array(
'attr' => array(
'class'=>'mdb-select colorful-select dropdown-default'
),
'choices' => array(
'Admin' => 'ROLE_ADMIN',
'USER' => 'ROLE_USER',
),
)
))
;
}
User.php
<?php
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #Vich\Uploadable
* #ORM\Entity
* #ORM\Table(name="fos_user")
* #UniqueEntity(fields="usernameCanonical", errorPath="username", message="fos_user.username.already_used", groups={"Default", "Registration", "Profile"})
* #UniqueEntity(fields="emailCanonical", errorPath="email", message="fos_user.email.already_used", groups={"Default", "Registration", "Profile"})
*/
class User extends BaseUser {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
*/
protected $id;
/**
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Group", inversedBy="users", cascade={"remove"})
* #ORM\JoinTable(name="fos_user_user_group",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* #ORM\Column(type="integer", length=6, options={"default":0})
*/
protected $loginCount = 0;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", nullable=true)
*/
protected $firstLogin;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="user_image", fileNameProperty="imageName")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255,nullable=true)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="datetime",nullable=true)
*
* #var \DateTime
*/
private $updatedAt;
public function __construct() {
parent::__construct();
$this->enabled = true;
$this->groups = new ArrayCollection();
}
}
Group.php
namespace AppBundle\Entity;
use FOS\UserBundle\Model\Group as BaseGroup;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_group")
*/
class Group extends BaseGroup
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\User", mappedBy="groups")
*
*
*/
protected $users;
/**
* #var string
*/
protected $name;
/**
* #var array
*/
protected $roles;
/**
* Group constructor.
*
* #param string $name
* #param array $roles
*/
public function __construct($name, $roles = array())
{
$this->name = $name;
$this->roles = $roles;
}
}
You need to pass the default data when creating the form. For example in your controller you'll have:
$user = new User();
// Add default groups to the $user
// ...
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// ...
}
You can achieve that by adding a 'POST_SET_DATA' or 'PRE_SUBMIT' form event to assign a group to the user if there aren't any groups in the form data.
$builder->addEventListener(FormEvents::POS_SET_DATA, function (FormEvent $event) {
$user = $event->getData();
$form = $event->getForm();
if (!$user->getGroups->count() > 0) {
//...
}
});
http://symfony.com/doc/current/form/dynamic_form_modification.html
https://symfony.com/doc/current/form/events.html
you could also use a lifecycle callback for your user entity
http://symfony.com/doc/current/doctrine/lifecycle_callbacks.html
or directly modify the object in your controller (#martin 's answer)
I have three entities running in Symfony:
Professional
class Professional extends User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="TurnsProfessional", mappedBy="professional")
*/
private $turns;
}
Turn
class Turn
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="TurnsProfessional", mappedBy="turn")
*/
private $professionals;
}
TurnsProfessionals
class TurnsProfessional
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Turn", inversedBy="professionals")
* #ORM\JoinColumn(name="turn_id", referencedColumnName="id")
*/
private $turn;
/**
* #ORM\ManyToOne(targetEntity="Professional", inversedBy="turns")
* #ORM\JoinColumn(name="professional_id", referencedColumnName="id")
*/
private $professional;
/**
* #ORM\Column(type="boolean")
*/
private $status;
}
In the FormType I have this:
->add('turns', 'entity',
array('class' => 'AppBundle:TurnsProfessional',
'property' => 'label',
'multiple' => true,
'expanded' => true,
));
What I would like to do is load all the "turns" available in "Turn" entity (Monday morning, Monday evening, Tuesday morning, etc.) and show them like checkboxes in a form. If the turn is checked, the turn will be registered in TurnsProfessional with status = 1 and if not with status = 0.
When I have all the turns saved in TurnsProfessional with status = 0 or status = 1, Symfony print all the options right and everything works. But, the first time no turn are created for the professional so the add('turns') method returns an empty value with no checkboxes.
How could I show all the options available in Turn entity in this case?
Thanks!
UPDATE FormType
I've tried to add a query_builder option in the FormType:
->add('turns', EntityType::class,
array('class' => 'AppBundle:Turn',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('turn')
->orderBy('turn.id', 'ASC');
},
'choice_label' => 'label',
'multiple'=>true,
'expanded'=>true,
))
Now, the form shows all the options but when I try to save the form I get the following error:
Found entity of type AppBundle\Entity\Turn on association
AppBundle\Entity\Professional#turns, but expecting
AppBundle\Entity\TurnsProfessional
You have an error because your entity is waiting for a TurnsProfessional object and you are trying to give it a Turn object. To solve this you should not map directly the field to your entity:
->add('turns', EntityType::class,
array('class' => 'AppBundle:Turn',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('turn')
->orderBy('turn.id', 'ASC');
},
'choice_label' => 'label',
'multiple' => true,
'expanded' => true,
'mapped' => false
))
Then in your controller you can access to the result like this:
$turns = $form->get('turns);
But you have to keep in mind that $turns will only contains the Turn entities you selected in your form.
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.
I have an entity Travel has an attribute country and I have an entity City related to Travel.
I'd like that when I choose a country showing all cities related. In fact I don't know Ajax and I need a help
class TravelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', 'country', array(
'required' => true,
'label' => 'Country',
))
->add('destination', 'entity',array(
'required' => true,
'class' => 'ProjectAdminBundle:City',
'property' => 'name',
'multiple' => false,
'expanded' => false,
'empty_value' => 'Choose a city',
'label' => 'Destination',
))
//.....
}
}
and this is entity Travel:
class Travel
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="country", type="string", length=255, nullable=false)
*
* #Assert\Country
*/
protected $country;
/**
* #ORM\ManyToOne(targetEntity="Project\AdminBundle\Entity\City", inversedBy="travels")
* #ORM\JoinColumn(nullable=false)
*/
protected $destination;
//........
}
Each city has a country code, for exemple:
London -> UK
Paris -> FR
.....
A similar case like yours is discussed in details in the Symphony Cookbook:
http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms