Symfony OneToOne relation render unique form - symfony

I have an entity with too many fields and datas to be handled by MySQL.
So I made another entity to store contents and linked it to the parent entity with OneToOne relations.
Here an extract of my parent entity HomeContent
// ...
class HomeContent
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="locale", type="string", length=6)
*/
private $locale;
/**
* #var string
*
* #ORM\OneToOne(targetEntity="ContentBlock", cascade={"all"})
*/
private $healthIntro;
/**
* #var string
*
* #ORM\OneToOne(targetEntity="ContentBlock", cascade={"all"})
*/
private $desktopIntro;
/**
* #var string
*
* #ORM\OneToOne(targetEntity="ContentBlock", cascade={"all"})
*/
private $techIntro;
// ...
public function __construct()
{
$this->healthIntro = new ContentBlock();
$this->desktopIntro = new ContentBlock();
$this->techIntro = new ContentBlock();
// ...
My ContentBlockentity has one text field content with setter and getter.
Now I want to simply render my form with a textarea for each fields, what would be the best way to do it?
For now, they're rendered as select elements, I defined a ContentBlockType class with content field
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content', 'textarea');
}
// ...
And a HomeContentType of course
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text')
->add('metadescription', 'text')
->add('healthIntro', 'entity', array(
'class' => 'NavaillesMainBundle:ContentBlock'
))
// ...

First of all I suggest to keep with a rule to use JoinColumn()annotation. Example:
/**
* #var string
*
* #ORM\OneToOne(targetEntity="ContentBlock", cascade={"all"})
* #ORM\JoinColumn(name="desktop_intro_id", referencedColumnName="id")
*/
private $desktopIntro;
Answer:
I don't know whether my way is the best but I suggest that you create ContentBlockFormType and embedded it to your form. So the form of your HomeContent entity will be like this:
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text')
->add('metadescription', 'text')
->add('desktopIntro', ContentBlockFormType::class, [
'label' => 'Desktop intro',
'required' => false,
])
->add('healthIntro', ContentBlockFormType::class, [
'label' => 'Health intro',
'required' => false,
])
->add('techIntro', ContentBlockFormType::class, [
'label' => 'Tech intro',
'required' => false,
])
}
// ...

Related

Symfony - Can't get a way to read the property (PropertyAccessor)

I develop with symfony a form for create tickets but when i try i got this error:
my function buildform from the file tickettype.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('Titre', TextType::class)
->add('Message', TextType::class)
->add('Date', DateTimeType::class, ['data' => new \DateTime()] )
->add('Demandeur', EntityType::class, [
'class' => Client::class,
'choice_label' => 'Nom',
])
->add('Agent', EntityType::class, [
'class' => Dealer::class,
'choice_label' => 'Nom',
])
->add('Etat_Ticket', EntityType::class, [
'class' => Etat::class,
'choice_label' => 'Statut',
]);
}
and in the controller :
/**
* #Route("/add/", name="add_ticket")
*
* #param Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
public function addTicketAction(Request $request)
{
$ticket = new Ticket();
$form = $this->createForm(TicketType::class, $ticket);
$form->add('send', SubmitType::class, ['label' => 'créé un nouveau ticket']);
$form->handleRequest($request);
if($form->isSubmitted()){
$ticket->setDate(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($ticket);
$em->flush();
return $this->redirectToRoute('List_ticket');
}
return $this->render("add.html.twig", array('form' => $form->createView()));
}
and my entity Ticket have this property:
/**
* #ORM\ManyToOne(targetEntity=Etat::class, inversedBy="Etat_Ticket")
* #ORM\JoinColumn(nullable=false)
*/
private $Etat_Ticket;
link to the entity Etat which look like this :
/**
* Etat
*
* #ORM\Table(name="etat")
* #ORM\Entity
*/
class Etat
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="statut", type="string", length=255, nullable=false)
*/
private $statut;
public function getId(): ?int
{
return $this->id;
}
public function getStatut(): ?string
{
return $this->statut;
}
public function setStatut(string $statut): self
{
$this->statut = $statut;
return $this;
}
}
You don't have a getter for Etat_Ticket property, so symfony (and that's a PHP common rule, when the property is not public) can't read its value from outside class scope. Symfony Form component, here, is trying by default to use the getter or access the property directly if it were public, and as neither getter nor public property are found, it symply doesn't know how to retrieve the value.
You can feed the form by yourself (docs) or use property_path.
Remember also that binding entities (or in general the domain model) directly to a form is good for RAD (rapid application development) but not so good in the long term. I would suggest to use a sort of DTO in order to read and write from and to the model (take a look here in order to get an idea about this concept)

display the name but insert the id whit entity field type

the form is the entity services
namespace Main\HomeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ServicesType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('techId', 'entity', array(
'class' => 'MainHomeBundle:Technical',
'property' => 'id',
))
->add('status', 'choice', array(
'choices' => array(
'pending' => 'Pending',
'finished' => 'Finished',
), 'multiple' => false), array('required'=>true))
->add('payment', 'text', array('required'=>true))
->add('endtime', 'datetime', array('required'=>true))
->add('send', 'submit')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Main\HomeBundle\Entity\Services'
));
}
/**
* #return string
*/
public function getName()
{
return 'main_homebundle_services';
}
}
Use this method to display a select users in a update form. The problem is that when the data are sent , send the name and want to send the id while showing the names in the select.
not use relationships in the database or the entities
Entity Services The technical id want to be inserted in techId of Services
class Services
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="customer_id", type="integer", nullable=false)
*/
private $customerId;
/**
* #var integer
*
* #ORM\Column(name="tech_id", type="integer", nullable=true)
*/
private $techId;
techId is linking with another entity
class Technical
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #Assert\NotBlank()
* #Assert\Length(
* min = 8,
* max = 55,
* minMessage = "{{ limit }}",
* maxMessage = "{{ limit }}"
* )
*
* #ORM\Column(name="name", type="string", length=55, nullable=false)
*/
private $name;
public function __toString()
{
return $this->nombre;
}
only informative way because there is no relationship in the database or entities
An Entity field is the wrong thing to use in this case - the form will try to map an instance of the Technical class onto the Services's integer $techId field, which cannot work.
You need to use a standard choice field and pass the list of (Technical entity) id choices into buildForm() as an option.
In the form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('techId', 'choice', array(
'choices' => $options['techChoices'],
))
...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'techChoices' => array(),
));
}
In the controller:
$repository = $this->getDoctrine()->getRepository('MainHomeBundle:Technical');
$technicals = $repository->findAll();
$techChoices = array();
foreach($technicals as $t) { $techChoices[$t->getId()] = $t->getName(); }
$formOptions = array('techChoices' => $techChoices);
$form = $this->createForm(new ServicesType(), $aServices, $formOptions);

symfony2 form with a many to many entity

I have this form with manyTomany relation working perfectly like this:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('manifestations', 'entity', array(
'class' => 'PrifProtocoleBundle:Manifestation',
'multiple' => true,
'expanded' => false,
'property' => 'libelle',
'empty_value' => 'Choississez',
'required' => false,));
}
but i want to set the'multiple' parameter to 'false', this way, i just have a select box with the option 'Choississez', so when i click on it, it displays all the other values. Unfortunately i get an error message: nor of the methods _set()" or "_call()" exist and have public access in class. i've been searching for some solutions on the web and tried this one:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('manifestations', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AcmeProtoBundle:Manifestation',
'multiple' => false,
'expanded' => false,
'property' => 'libelle',
'empty_value' => 'Choisissez',
'required' => false,)));
}
i have no error message! but the select form doesn't display even when i set the 'multiple' to 'true, i only have the submit button, when clicked shows me the results, so i think i miss something in the parameters to display the form!
can anyone help? Thanks
Manifestation.php
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="ManifestationRepository")
*/
class Manifestation {
public function __construct() {
$this->dateCreation = new \DateTime;
$this->dateModif = new \DateTime;
}
public function __toString() {
return $this->getLibelle();
}
/**
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Id
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
* #Assert\GreaterThan(
* value = 0,
* message = "La valeur doit être positive"
* )
*/
private $numOrdre;
/**
* #ORM\Column(type="string",length=50)
* #Assert\Length(
* min = "5",
* minMessage = "Le libellé doit faire au moins {{ limit }} caractères"
* )
*/
private $libelle;
/**
* #ORM\Column(type="datetime")
*/
private $dateCreation;
/**
* #ORM\Column(type="datetime")
*/
private $dateModif;
/**
* #ORM\Column(type="boolean")
* #Assert\NotBlank( message=" ")
*/
private $etat;
//getters and setters
invite.php
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="InviteRepository")
*
*/
class Invite {
/**
* #var boolean
*
* #ORM\ManyToMany(targetEntity="Acme\ProtoBundle\Entity\Manifestation", cascade={"persist"})
* #Assert\NotBlank(message=" ")
*/
private $manifestations;
Can you show your manifestation.php file (your entity)?
Multiple doesn't have the behaviour you are looking for : Multiple is used to allow a user to check multiple checkboxes (true) or only one (false) of a given form (symfony doc : multiple).
In your case a common solution is to use javascript on a parent field that would disable/enable the children fields. Make sure to add server-side validation on these fields if you go for this.
This is the solution working for me now with 'multiple' => false':
i've added this function in the other entity in relation with Manifestation, to consider manifestations as an array
public function setManifestations($manifestations){
if(!is_array($manifestations)){
$manifestations = array($manifestations);
}
$this->manifestations = $manifestations;
}

Symfony update related entities

Lets say we have Employee registration system. We have four entities: Employee, Login, Phone, Email
One Employee has Many login,s, Many Phones and ManyEmails
I have created LoginType:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('username')
->add('password');
}
Then Phone type:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('phone');
}
Email Type
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('eMail') ;
}
And Finally Employee type, what ties all of them togheder, plus some extra fields:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('firstname')
->add('middleNames')
->add('lastname')
->add('dateofbirth', 'date', array(
'widget' => 'single_text',
'read_only' => true
))
->add('address')
->add('idcode')
->add('position')
->add('phone', new EmployeePhoneType(), array('data_class' => null))
->add('email', new EmployeeEmailType(), array('data_class' => null))
->add('login', new LoginType(), array('data_class' => null))
->add('save', 'submit');
}
In ORM all entities have relations
class Employee {
/* ...Other fileds not shown here...*/
/**
* #var Doctrine\Common\Collections\ArrayCollection $login
* #ORM\OneToMany(targetEntity="Crm\AuthBundle\Entity\Login", mappedBy="employee", cascade={"persist"})
*/
protected $login;
/**
* #var Doctrine\Common\Collections\ArrayCollection $phone
* #ORM\OneToMany(targetEntity="Crm\AdminBundle\Entity\EmployeePhone", mappedBy="employee", cascade={"persist"})
*/
protected $phone;
/**
* #var Doctrine\Common\Collections\ArrayCollection $email
* #ORM\OneToMany(targetEntity="Crm\AdminBundle\Entity\EmployeeEmail", mappedBy="employee", cascade={"persist"})
*/
protected $email;
}
class EmployeePhone{
/**
* #ORM\ManyToOne(targetEntity="Crm\AdminBundle\Entity\Employee", inversedBy="phone", cascade={"persist"})
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id", nullable=false)
*/
private $employee;
}
class EmployeeEmail{
/**
* #ORM\ManyToOne(targetEntity="Crm\AdminBundle\Entity\Employee", inversedBy="email", cascade={"persist"})
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id", nullable=false)
*/
private $employee;
}
class Login{
/**
* #ORM\ManyToOne(targetEntity="Crm\AdminBundle\Entity\Employee", inversedBy="login", cascade={"persist"})
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id", nullable=false)
*/
protected $employee;
}
Now when i do the updtade action i first load employee object in controller:
$employee = $this->getDoctrine()->getRepository('AdminBundle:Employee')
->find($empId);
And then initiate form and tie $employee with form:
$form = $this->createForm(new EmployeeType(), $employee, array(
'action' => $this->generateUrl('employee_save')
));
The problem $employee object itself is correctly loaded and displaied in form fields, but all releated objects are shown as object(Doctrine\ORM\PersistentCollection)[416] and no data is retrived. Even if i do initialize() for those PersistentCollections, then data is shown under coll atribute, but not displayed in form.
How to do thos update action correctly ?
You're looking for the collection field-type for a one-to-many relation between Employee and Phone/Email/Login
Yes collection type field would have been best choice in this case, but since i realized that application actually needs fixed number of phone and e-mail fields, then i removed separate entity and saved fixed number of entries into main entity. Also fixed data_class to correct one.

Delete an item from oneToMany relationship

I have the following Gallery entity
class Gallery
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Tessa\GalleryBundle\Entity\Photo", mappedBy="gallery", cascade={"persist", "remove"})
*/
private $photos;
/* ... */
}
This gallery is linked with a manyToOne relationship to a PointOfInterest entity. Here is the declaration
class PointOfInterest
{
/* ... */
/**
* #ORM\ManyToOne(targetEntity="Tessa\GalleryBundle\Entity\Gallery", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $gallery;
/* ... */
I also use a Form to update the PointOfInterest entity. Here is the form declaration
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('gallery', new GalleryType())
;
}
and the GalleryType declaration.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('photos', 'collection', array('type' => new PhotoType(),
'required' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
))
;
}
When I edit the PoI I can add photos to the gallery without problem, but I can't delete anything.
I tried to hook on gallery PreUpdate, but it is never called. I printed output in removePhotos method of Gallery entity, and the photos are removed from the gallery. I then suspect the Gallery to never be persisted.
Here is the code when I persist the PoI after editing.
private function handleForm($elem, $is_new)
{
$form = $this->createForm(new CircuitType, $elem);
$request = $this->get('request');
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($elem);
$em->flush();
return $this->redirect($this->generateUrl('tessa_circuit_index'));
}
}
return $this->render('TessaUserBundle:Circuits:add.'.'html'.'.twig',
array(
'form' => $form->createView(),
'is_new' => $is_new,
));
}
There is article in Symfony2 cookbook about handling this type of situation. As you have OneToMany relationship, you have to remove related objects manually in controller.
Edit:
Or you can make use of Doctrine's orphan removal feature.
class Gallery
{
//...
/**
* #ORM\OneToMany(targetEntity="Photo", mappedBy="gallery", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $photos;
//...
public function removePhotos($photo)
{
$this->photos->remove($photo);
$photo->setGallery(null);
}
}

Resources