many to many save data - symfony

I have two entity that have manytomany relation
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Esame
*
* #ORM\Table(name="esame", indexes={#ORM\Index(name="id_tipologia_esame", columns={"id_tipologia_esame"}), #ORM\Index(name="id_analisi", columns={"id_analisi"})})
* #ORM\Entity(repositoryClass="AppBundle\Repository\EsameRepository")
*/
class Esame
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \AppBundle\Entity\Nome_esame
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Nome_esame")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_tipologia_esame", referencedColumnName="id")
* })
*/
private $idTipologiaEsame;
/**
* #var \AppBundle\Entity\Analisi
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Analisi")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_analisi", referencedColumnName="id")
* })
*/
private $idAnalisi;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\TipologiaCampione", inversedBy="esami")
*/
private $campioni;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set idAnalisi
*
* #param \AppBundle\Entity\Analisi $idAnalisi
*
* #return Esame
*/
public function setIdAnalisi($idAnalisi)
{
$this->idAnalisi = $idAnalisi;
return $this;
}
/**
* Get idAnalisi
*
* #return int
*/
public function getIdAnalisi()
{
return $this->idAnalisi;
}
/**
* Constructor
*/
public function __construct()
{
$this->campioni = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add campioni
*
* #param \AppBundle\Entity\TipologiaCampione $campioni
*
* #return Esame
*/
public function addCampioni(\AppBundle\Entity\TipologiaCampione $campioni)
{
$this->campioni[] = $campioni;
return $this;
}
/**
* Remove campioni
*
* #param \AppBundle\Entity\TipologiaCampione $campioni
*/
public function removeCampioni(\AppBundle\Entity\TipologiaCampione $campioni)
{
$this->campioni->removeElement($campioni);
}
/**
* Get campioni
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCampioni()
{
return $this->campioni;
}
/**
* Set idTipologiaEsame
*
* #param \AppBundle\Entity\Nome_esame $idTipologiaEsame
*
* #return Esame
*/
public function setIdTipologiaEsame(\AppBundle\Entity\Nome_esame $idTipologiaEsame = null)
{
$this->idTipologiaEsame = $idTipologiaEsame;
return $this;
}
/**
* Get idTipologiaEsame
*
* #return \AppBundle\Entity\Nome_esame
*/
public function getIdTipologiaEsame()
{
return $this->idTipologiaEsame;
}
}
and Entity 2
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* TipologiaCampione
*
* #ORM\Table(name="tipologia_campione")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TipologiaCampioneRepository")
*/
class TipologiaCampione
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="tipologia", type="string", length=255, nullable=true)
*/
private $tipologia;
/**
* #var string
*
* #ORM\Column(name="matricola", type="string", length=255, nullable=true)
*/
private $matricola;
/**
* #var string
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Esame",mappedBy="campioni")
*/
private $esami;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set tipologia
*
* #param string $tipologia
*
* #return TipologiaCampione
*/
public function setTipologia($tipologia)
{
$this->tipologia = $tipologia;
return $this;
}
/**
* Get tipologia
*
* #return string
*/
public function getTipologia()
{
return $this->tipologia;
}
/**
* Set matricola
*
* #param string $matricola
*
* #return TipologiaCampione
*/
public function setMatricola($matricola)
{
$this->matricola = $matricola;
return $this;
}
/**
* Get matricola
*
* #return string
*/
public function getMatricola()
{
return $this->matricola;
}
/**
* Constructor
*/
public function __construct()
{
$this->esami = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add esami
*
* #param \AppBundle\Entity\Esame $esami
*
* #return TipologiaCampione
*/
public function addEsami(\AppBundle\Entity\Esame $esami)
{
$this->esami[] = $esami;
return $this;
}
/**
* Remove esami
*
* #param \AppBundle\Entity\Esame $esami
*/
public function removeEsami(\AppBundle\Entity\Esame $esami)
{
$this->esami->removeElement($esami);
}
/**
* Get esami
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEsami()
{
return $this->esami;
}
}
now i must save data and in my controller i write this:
for($i=0;$i<$form->get('numero_campioni')->getData();$i++) {
$campione=new TipologiaCampione();
$campione->setMatricola($form['matricola_'.$i]->getData());
$campione->setTipologia($form['tipologia_'.$i]->getData());
foreach($form['esame_'.$i]->getData() as $value){
$tipo_esame = $this->getDoctrine()
->getRepository('AppBundle:nome_esame')
->find($value->getId());
$esame=new Esame();
$esame->setIdAnalisi($analisi);
$esame->setIdTipologiaEsame($tipo_esame);
$em->persist($esame);
$em->flush();
$campione->addEsami($esame); -> this don't work
}
$em->persist($campione);
$em->flush();
}
I expect that $campione->addEsami($esame); creates a new record in my table esame_tipologia_campione for each $campione but it doesn't work ..
My form Type:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Validator\Constraints\DateTime;
use AppBundle\Entity\anagrafica;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use AppBundle\Form\anagraficaType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use AppBundle\Form\EsameType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class AnalisiType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$province = array(
'AG' => 'Agrigento',
'AL' => 'Alessandria',
'AN' => 'Ancona',
);
$builder->add('ruminanti', ChoiceType::class, array(
'label' => false,
'mapped' => false,
'placeholder' => 'Tipologia di modulo',
'choices' => array('Ruminanti' => 'Ruminanti', 'Suini' => 'Suini'),
'required' => true,
'data' => ($options['data']->getTipoModulo() != NULL ? $options['data']->getTipoModulo() : ''),
'attr' => array(
'placeholder' => 'Provincia',
)))
->add('idLaboratorio', EntityType::class, array(
'label' => false,
'placeholder' => 'Laboratorio a cui inviare i dati',
'class' => 'AppBundle:Laboratorio',
'choice_label' => 'nome',
// 'data'=>5
))
// Dati proprietario
->add('privato', ChoiceType::class, array(
'label' => false,
'choices' => array('Privato' => 'Privato', 'Azienda' => 'Azienda'),
'data' => 'Privato',
'expanded' => true,
'multiple' => false,
'mapped' => false,
))
->add('Nome_proprietario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getNome() : ''),
'attr' => array(
'placeholder' => 'Nome',
'novalidate' => 'novalidate'
)))
->add('Cognome_proprietario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getCognome() : ''),
'attr' => array(
'placeholder' => 'Cognome',
'novalidate' => 'novalidate'
)))
->add('Ragione_sociale_proprietario', TextType::class, array(
'label' => false,
'mapped' => false,
'required' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getRagioneSociale() : ''),
'attr' => array(
'placeholder' => 'Ragione Sociale',
'novalidate' => 'novalidate'
)))
->add('Via_proprietario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getvia() : ''),
'attr' => array(
'placeholder' => 'Via'
)))
->add('Comune_proprietario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getComune() : ''),
'attr' => array(
'placeholder' => 'Comune'
)))
->add('Provincia_proprietario', ChoiceType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getProvincia() : ''),
'choices' => $province,
'placeholder' => 'Provincia'
))
->add('Telefono_proprietario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->gettel() : NULL),
'attr' => array(
'placeholder' => 'Telefono'
)))
->add('email_proprietario', EmailType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getemail() : ''),
'attr' => array(
'placeholder' => 'E-mail'
)
))
->add('Cod_allev_proprietario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdProprietario() != NULL ? $options['data']->getIdProprietario()->getCodiceAllievo() : ''),
'attr' => array(
'placeholder' => 'Codice Allevamento'
)))
// Dati Veterinario
->add('Nome_veterinario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdVeterinario() != NULL ? $options['data']->getIdProprietario()->getNome() : ''),
'attr' => array(
'placeholder' => 'Nome'
)))
->add('Cognome_veterinario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdVeterinario() != NULL ? $options['data']->getIdProprietario()->getCognome() : ''),
'attr' => array(
'placeholder' => 'Cognome'
)))
->add('Via_veterinario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdVeterinario() != NULL ? $options['data']->getIdVeterinario()->getvia() : ''),
'attr' => array(
'placeholder' => 'Via'
)))
->add('Comune_veterinario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdVeterinario() != NULL ? $options['data']->getIdVeterinario()->getComune() : ''),
'attr' => array(
'placeholder' => 'Comune'
)))
->add('Provincia_veterinario', ChoiceType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdVeterinario() != NULL ? $options['data']->getIdVeterinario()->getProvincia() : ''),
'choices' => $province,
'attr' => array(
'placeholder' => 'Provincia'
)))
->add('Telefono_veterinario', TextType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdVeterinario() != NULL ? $options['data']->getIdVeterinario()->getTel() : NULL),
'attr' => array(
'placeholder' => 'Telefono'
)))
->add('email_veterinario', EmailType::class, array(
'label' => false,
'mapped' => false,
'data' => ($options['data']->getIdVeterinario() != NULL ? $options['data']->getIdVeterinario()->getEmail() : ''),
'attr' => array(
'placeholder' => 'E-mail'
)
))
->add('trattamenti', TextareaType::class, array(
'label' => false,
'attr' => array(
'placeholder' => 'Trattamenti(inserire ANAMNESI; SINTOMATOLOGIA; TRATTAMENTI)'
)
))
->add('vacinazione', TextareaType::class, array(
'label' => false,
'attr' => array(
'placeholder' => 'Vacinazioni(obbligatorio)'
)
))
->add('numero_campioni', NumberType::class, array(
'label' => false,
'attr' => array(
'placeholder' => 'Numero Totale di campioni'
)
));
//->add('save', SubmitType::class, array('label' => 'Create Task'));
$builder->add('idTipologiaEsame', EntityType::class, array(
'label' => false,
'mapped' => false,
'class' => 'AppBundle:Nome_esame',
'choice_label' => 'nome',
'group_by' => 'idCategoriaEsame.tipo',
))
->add('Aggiungi', ButtonType::class, array(
'attr' => array(
'class' => 'btn-primary col-md-2 conferma_esame'),
))
->add('Rimuovi', ButtonType::class, array(
'attr' => array(
'class' => 'btn-primary col-md-2 conferma_esame'),
));
// for($i=0;$i<$options['data']->getNumeroCampioni();$i++){
for ($i = 0; $i < 60; $i++) {
$builder->add('checkbox_' . $i, CheckboxType::class, array(
'label' => false,
'required' => false,
'mapped' => false,
'attr' => array(
'class' => 'check_box_table',
// 'class'=>'col-md-1 col-xs-3 allineare_sinistra',
)
))
->add('matricola_' . $i, TextType::class, array(
'label' => false,
'mapped' => false,
'required' => false,
'attr' => array(
'placeholder' => 'Matricola (FACOLTATIVA)',
// 'class'=>'col-md-2 col-xs-9 allineare_sinistra',
)))
->add('tipologia_' . $i, TextType::class, array(
'label' => false,
'mapped' => false,
'required' => false,
'attr' => array(
'placeholder' => 'Tipologia (FACOLTATIVA)',
// 'class'=>'col-md-2 col-md-offset-0 col-xs-9 col-xs-offset-3 allineare_sinistra',
)))
->add('esame_' . $i, EntityType::class, array(
'label' => false,
'mapped' => false,
'class' => 'AppBundle:Nome_esame',
'required'=>true,
'multiple'=>true,
'choice_label' => 'nome',
// 'disabled' => 'disabled',
'attr' => array(
'placeholder' => 'Esami',
'class' => 'max_width esame_row select_esame',
// 'class'=>'col-md-12 col-md-offset-0 col-xs-9 col-xs-offset-3 ',
)
))
->add('Stampa', ButtonType::class, array(
'disabled' => 'disabled',
'attr' => array(
'class' => 'btn-primary text-center',
)
))
->add('saveAndAdd', SubmitType::class, array('label' => 'Save and Add'))
;
}
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Analisi'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix() {
return 'appbundle_analisi';
}
}
$esame entity and $campione entity are correctly saved.
Can anybody say to me what I'm doing wrong??

You need to create a Join Table
After that you need to choose which entity is the owner of the association (just after the manytomany chapter in the doc).
For exampre, if you may want to add "campioni" via the "esami" entity you should code a setter like this:
public function addCampioni(\AppBundle\Entity\TipologiaCampione($campioni) {
$campioni->addEsami($this);
$this->campioni[] = $campioni;
return $this;
}
Ciao!

Related

Laminas / Zf3 - getting parameters from Fieldset but it return null values

I was following the instructions of #crash and I get the value of my parameter passed in my fieldset. But sometimes it returns a null value. Here are my complete code
My RapportEffetForm :
<?php
/**
* #module Commun
* #subpackage Form\Admin
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2021 Nslabs
*/
namespace Commun\Form\Modules\Application;
use Commun\Form\Modules\Application\Fieldset\RapportEffetFieldset;
use Commun\Form\CommunForm;
class RapportEffetForm extends CommunForm
{
private $parameters;
public function __construct($params)
{
$this->parameters=$params;
parent::__construct('RapportEffetForm',$this->parameters,[]);
}
public function init() {
$myFieldset = $this->getFormFactory()->getFormElementManager()->get(RapportEffetFieldset::class, ['params' => $this->parameters]);
$this->setName('RapportEffetForm');
$this->setAttribute('class', 'form-saisie');
//$this->addFieldset(RapportEffetFieldset::class,['use_as_base_fieldset' => true]);
$this->addFieldsetWithParameters($myFieldset,['use_as_base_fieldset' => true]);
$this->addSubmitButton('next', 'Poursuivre', 'next', 'btn btn-vert w-100');
$this->addSubmitButton('previous', 'Retour', 'previous', 'btn btn-rouge w-100');
}
}
My RapportEffetFormFactory :
<?php
/**
* #module Commun
* #subpackage Form
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2020 Nslabs
*/
namespace Commun\Factory\Form;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Commun\Form\Modules\Application\RapportEffetForm;
class RapportEffetFormFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$params = $options['id'] ?? [];
unset($options['id']);
return new RapportEffetForm($params);
}
}
My RapportEffetFieldset :
<?php
/**
* #module Commun
* #subpackage Form\Admin
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2020 Nslabs
*/
namespace Commun\Form\Modules\Application\Fieldset;
use Commun\Model\Entity\Supervision;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Hydrator\ReflectionHydrator;
use Commun\Form\Modules\Application\Fieldset\EffetRapportFieldset;
use Commun\Form\CommunFormFieldset;
class RapportEffetFieldset extends CommunFormFieldset implements InputFilterProviderInterface
{
private $mapper;
private $params;
public function __construct($mappers=[],$params=[], $options = [])
{
$this->mapper = $mappers;
$this->params = $params;
parent::__construct($this->mapper,'RapportEffetForm',$options);
$this->setHydrator(new ReflectionHydrator());
$this->setObject(new Supervision());
$this->setLabel('RapportEffetFieldset');
}
public function init() {
parent::init();
$myFieldset = $this->getFormFactory()->getFormElementManager()->get(EffetRapportFieldset::class, ['params' => $this->params]);
$this->addCollection('effets','', get_class(new $myFieldset()),1,[],'__index__',FALSE,FALSE);
$optionsNotation = $this->mapper['notations']->getOptions('idRefNotation','libelle','Sélectionner',null,['ordre']);
$this->addSelect('notationEffetPresent', 'Notation du présent rapport',$optionsNotation,['class' => 'form-control'],'champ-requis');
$this->addSelect('notationEffetPrecedent', 'Notation du rapport précédent',$optionsNotation,['class' => 'form-control champ-affiche', 'placeholder' => 'Choisir']);
$this->addTextarea('JustifNotationEffet', 'Justification', NULL, ['class' => 'form-control','rows' => 5]);
}
/**
* #return array
*/
public function getInputFilterSpecification()
{
return [
];
}
}
My RapportEffetFieldsetFactory :
<?php
/**
* #module Commun
* #subpackage Form
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2020 Nslabs
*/
namespace Commun\Factory\Form\Fieldset;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Commun\Form\Modules\Application\Fieldset\RapportEffetFieldset;
class RapportEffetFieldsetFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$params = $options['params'] ?? [];
unset($options['params']);
$mappers = ['notations' => $container->get('TREFNOTATION')];
return new RapportEffetFieldset($mappers,$params,$options);
}
}
My EffetRapportFieldset :
<?php
/**
* #module Commun
* #subpackage Form\Admin
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2020 Nslabs
*/
namespace Commun\Form\Modules\Application\Fieldset;
use Commun\Model\Entity\EffetProjet;
use Laminas\InputFilter\InputFilterProviderInterface;
use Laminas\Hydrator\ReflectionHydrator;
use Laminas\Filter\StripTags;
use Laminas\Filter\StringTrim;
use Commun\Form\CommunFormFieldset;
class EffetRapportFieldset extends CommunFormFieldset implements InputFilterProviderInterface
{
private $mapper;
private $inputFilter;
private $params;
public function __construct($mappers=[],$params=[], $options = [])
{
$this->mapper = $mappers;
$this->params = $params;
parent::__construct($this->mapper,'EffetRapportForm',$options);
$this->setHydrator(new ReflectionHydrator());
$this->setObject(new EffetProjet());
}
public function init() {
parent::init();
var_dump($this->params);
//die;
$idProjet = $this->params['id']; // I retrieve my param here like this. But I get this warning <<Notice: Undefined index: id in EffetRapportFieldset.php on line 45>>
$optionsEffetCadreLogique = $this->mapper['effetProjet']->getOptions('idEffetProjet','libelleEffet','Sélectionner',['idProjet' => $idProjet],['libelleEffet']);
$this->addSelect('idEffetProjet', 'Intitule de l\'effet',$optionsEffetCadreLogique,['class' => 'form-control champ-affiche'],'champ-requis');
$this->addText('valeurReference','Valeur de référence (a)',NULL,['class' => 'form-control champ_decimal champ-affiche'],'champ-requis');
$this->addText('valeurRecente','Valeur la plus récente (b)',NULL,['class' => 'form-control champ_decimal'],'champ-requis');
$this->addText('valeurCible','Cible finale (c)',NULL,['class' => 'form-control champ_decimal champ-calcule'],'champ-requis');
$this->addText('progresRealisation','Progrès vers la réalisation de la cible (% de réalisation) (d=b/c)',NULL,['class' => 'form-control champ_decimal champ-affiche'],'champ-requis');
$this->addTextarea('evaluation', 'Évaluation', NULL, ['class' => 'form-control','rows' => 3]);
}
/**
* #return array
*/
public function getInputFilterSpecification()
{
return [
];
}
}
My EffetRapportFieldsetFactory :
<?php
/**
* #module Commun
* #subpackage Form
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2020 Nslabs
*/
namespace Commun\Factory\Form\Fieldset;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Commun\Form\Modules\Application\Fieldset\EffetRapportFieldset;
class EffetRapportFieldsetFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$params = $options['params'] ?? [];
unset($options['params']);
$mappers = ['effetProjet' => $container->get('TEFFETPROJET')];
return new EffetRapportFieldset($mappers,$params,$options);
}
}
My CommunForm class here :
<?php
/**
* #module Commun
* #subpackage Form
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2020 Nslabs
*/
namespace Commun\Form;
use Laminas\Form\Form;
use Laminas\Hydrator\ClassMethodsHydrator;
use Laminas\InputFilter\InputFilter;
use Laminas\Form\Element\Select;
use Laminas\Form\Element\Hidden;
class CommunForm extends Form
{
protected $mapper;
public $params;
public function __construct($formName=null,$params=[],$mapper=[]) {
parent::__construct($formName);
$this->mapper = $mapper;
$this->params = $params;
$this->setHydrator(new ClassMethodsHydrator(false));
$this->setInputFilter(new InputFilter());
$this->setAttribute('class', 'form-horizontal');
//$this->setOption('params', $params);
}
public function populateSelect(\Laminas\Form\Element\Select $selectName,$optionValues=[]){
$selectName->setValueOptions($optionValues);
}
public function addFieldset($fieldset,$options=[]){
$this->add([
'type' => $fieldset,
'options' => $options,
]);
}
public function addFieldsetWithParameters($fieldset,$options=[]){
$this->add([
'type' => get_class(new $fieldset()),
'options' => $options,
]);
}
public function addSubmitButton($name,$label,$id='',$className=''){
$this->add([
'name' => $name,
'type' => 'submit',
'attributes' => [
'value' => $label,
'id' => $id,
'class' => $className,
]
]);
}
public function addSelect($name,$label){
$this->add([
'name' => $name,
'type' => Select::class,
'options' => [
'label' => $label,
'value_options' => [
'0' => 'French',
'1' => 'English',
'2' => 'Japanese',
'3' => 'Chinese',
],
],
]);
}
public function addFile($name,$label,$id='',$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => 'file',
'options' => [
'label' => $label,
'id' => $id,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
public function addHiddenElement($name,$attributes=[]){
$this->add([
'name' => $name,
'type' => Hidden::class,
'attributes' => $attributes,
]);
}
public function addText($name,$label,$id='',$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => 'text',
'options' => [
'label' => $label,
'id' => $id,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
}
and my CommunFieldset :
<?php
/**
* #module Commun
* #subpackage Form
* #author Samuel NANGUI <nanguisamuel#gmail.com>
* #copyright Copyright (c) 2021 Nslabs
*/
namespace Commun\Form;
use Laminas\Form\Element\Select;
use Laminas\Form\Fieldset;
use Laminas\Form\Element\Checkbox;
use Laminas\Form\Element\MultiCheckbox;
use Laminas\Form\Element\Button;
use Laminas\Form\Element\Hidden;
class CommunFormFieldset extends Fieldset
{
/**
* Shortcut for adding an input type text element
* #param string $name name of the item
* #param string $label label of the item
* #param string $id unique HTML id of the item
* #param array $attributes array of attributes to apply to the item
*/
//public $additionnalParams;
private $mapper;
public function __construct($mapper=[],$name=null,$options = []) {
//$this->additionnalParams = $options;
$this->mapper = $mapper;
parent::__construct($name, $options);
}
public function init(){
parent::init();
}
/**
* Shortcut for adding an input type text element
* #param string $name name of the item
* #param string $label label of the item
* #param string $id unique HTML id of the item
* #param array $attributes array of attributes to apply to the item
* #param string $labelClass label class(espacially for required item)
*/
public function addHiddenElement($name,$attributes=[]){
$this->add([
'name' => $name,
'type' => Hidden::class,
'attributes' => $attributes,
]);
}
public function addText($name,$label,$id='',$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => 'text',
'options' => [
'label' => $label,
'id' => $id,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
/**
* Shortcut for adding an input type text element
* #param string $name name of the item
* #param string $label label of the item
* #param string $id unique HTML id of the item
* #param array $attributes array of attributes to apply to the item
* #param string $labelClass label class(espacially for required item)
*/
public function addFile($name,$label,$id='',$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => 'file',
'options' => [
'label' => $label,
'id' => $id,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
public function addTextarea($name,$label,$id='',$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => 'textarea',
'options' => [
'label' => $label,
'id' => $id,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
/**
* Shortcut for adding a select list type element
* #param string $name name of the select item
* #param string $label label of the select item
* #param array $valueOptions array values to populate the Select with
* #param array $attributes array of attributes to apply to the Select item
* #param string $labelClass label class(espacially for required item)
*/
public function addSelect($name,$label,$valueOptions=[],$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => Select::class,
'options' => [
'label' => $label,
'value_options' => $valueOptions,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
public function addSelectFromTable($name,$label,$type,$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => get_class($type) ,
'options' => [
'label' => $label,
'label_attributes' => [
'class' => $labelClass
],
],
//'label_attributes' => ['class' => $labelClass],
'attributes' => $attributes,
]);
}
/**
* Shortcut for adding a select list type element
* #param string $name name of the select item
* #param string $label label of the select item
* #param string $id id of the select item
* #param string $checkedValue value of checked item.
* #param string $unCheckedValue value of unchecked item.
* #param array $attributes attribute array.
* #param string $labelClass label class(espacially for required item)
*/
public function addCheckbox($name,$label,$id='',$checkedValue='',$unCheckedValue='',$attributes=[],$labelClass=''){
$this->setLabelAttributes(['class' => $labelClass]);
$this->add([
'name' => $name,
'type' => Checkbox::class,
'options' => [
'label' => $label,
'id' => $id,
'use_hidden_element' => true,
'checked_value' => $checkedValue,
'unchecked_value' => $unCheckedValue,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
public function addMultiCheckbox($name,$label,$id='',$valueOptions=[],$attributes=[],$labelClass=''){
$this->add([
'name' => $name,
'type' => MultiCheckbox::class,
'options' => [
'label' => $label,
'id' => $id,
'value_options' => $valueOptions,
'label_attributes' => [
'class' => $labelClass
],
],
'attributes' => $attributes,
]);
}
public function addButton($name,$label,$attributes=[]){
$this->add([
'name' => $name,
'type' => Button::class,
'options' => [
'label' => $label,
],
'attributes' => $attributes,
]);
}
/**
* Shortcut for adding a collection list type element
* #param string $name name of the select item
* #param string $label label of the select item
* #param string $targetElement name of the target fieldset
* #param boolean $isObject indicate if the variable is an instance of a class or just a string
* #param int $count number of visible row
* #param boolean $shouldCreateTemplate indicate if the html markup must be created
* #param boolean $allowAdd indicate if new item can to addeds
*/
private function getStringFromElement($element){
if(gettype($element)==='object'){
return get_class($element);
}elseif(gettype($element)==='string'){
return $element;
}else{
throw new \Exception('This type cannot be used with collection element');
}
}
public function addCollection($name,$label, $targetElement,$count=2,$attributes=[],$templatePlaceholder='__index__',$shouldCreateTemplate=true,$allowAdd=true){
$this->add([
'type' => \Laminas\Form\Element\Collection::class,
'name' => $name,
'options' => [
'label' => $label,
'count' => $count,
'should_create_template' => $shouldCreateTemplate,
'template_placeholder' => $templatePlaceholder,
'allow_add' => $allowAdd,
'target_element' => [
'type' => $this->getStringFromElement($targetElement),
],
],
'attributes' => $attributes,
]);
}
}
When I do a var_dump in my init() function in EffetRapportFielset, I get this
And in the same page I get also this :
I need to say that when I do echo on $this->parameters on my RapportEffetForm I get my correct parameter value.
I do not know why for the first time, the value is good, and the (2nd time ??) is null. Do not know also why this value is shown more than once.
Here are all my infos. Hope it helps you to also help me finding a solution.
Best regards !
My intelligent guess is, that the problem is your CommunForm::addFieldsetWithParameters() method. You're loosing the parameters, since you'r creating the fieldset a second time, without the parameters getting passed again.
Just use Laminas\Form\Form::add() to add the fieldset:
$myFieldset = $this->getFormFactory()
->getFormElementManager()
->get(RapportEffetFieldset::class, ['params' => $this->parameters]);
$this->add($myFieldset);
And you do the same thing within the collection. Just pass the object to the collection there as "target_element":
$myFieldset = $this->getFormFactory()
->getFormElementManager()
->get(EffetRapportFieldset::class, ['params' => $this->params]);
$this->add([
'type' => \Laminas\Form\Element\Collection::class,
'name' => 'effets',
'options' => [
'label' => '',
'count' => 1,
'should_create_template' => false,
'template_placeholder' => '__index__',
'allow_add' => false,
'target_element' => $myFieldset,
],
'attributes' => []
]);

Symfony - Embedded Collection Form validation conditionally

I have a carousel with a collection of slides (OneToMany) and each slide contain a file that I have to validate if not exist.
When a slide has no file, the field is required but when the file exist, it's not required.
My CarouselType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('slides', CollectionType::class, [
'entry_type' => SlideType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
'row_attr' => [
'class' => 'hidden'
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Carousel::class,
]);
}
My SlideType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => false
])
->add('url', UrlType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => false
])
->add('file', FileType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Slide::class,
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if ($data->getFile() === null && $data->getFileName() === null) {
return ['upload'];
}
return ['Default'];
},
]);
}
My Carousel Entity:
/**
* #ORM\Entity(repositoryClass=CarouselRepository::class)
*/
class Carousel
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(
* targetEntity=Slide::class,
* mappedBy="carousel",
* fetch="EXTRA_LAZY",
* orphanRemoval=true,
* cascade={"persist"}
* )
* #Assert\Count(min=1, minMessage="carousel.slides.count.min")
* #Assert\Valid(groups={"upload"})
*/
private $slides;
...
...
}
My Slide Entity:
/**
* #ORM\Entity(repositoryClass=SlideRepository::class)
*/
class Slide
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $title;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $url;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $fileName;
/**
* #var UploadedFile
* #Assert\NotNull(message="slide.file.not_null", groups={"upload"})
*/
protected $file;
/**
* #ORM\ManyToOne(targetEntity=Carousel::class, inversedBy="slides")
* #ORM\JoinColumn(nullable=false)
*/
private $carousel;
...
...
}
I've added validation_groups in my SlideType but it's not working.
What's wrong with my code ?
Use PreSetData Event for adding file in the SlideType
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
$isNotRequired = ($data instanceof Slide) && $data->getUrl();
$constraints = isNotRequired ? [] : [new NotBlank()];
$event->getForm()->add('file', FileType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => !$isNotRequired,
'constraints' => $constraints,
]);
});
You need to use "valid" to validate the subform.
https://symfony.com/doc/current/reference/constraints/Valid.html
$builder->add('slides', CollectionType::class, [
'entry_type' => SlideType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
'row_attr' => [
'class' => 'hidden'
],
'constraints' => [new Valid()],
]);

Symfony: Creating a new user results in overwriting of properties

When someone creates a new user on our application, some of his own properties get overwritten by the new users properties. I have no clue yet, where to look for the mistake, since in the controller or entities, nothing was changed.
Here are some information: the user entity (we are using the FOS User Bundle)
class User extends BaseUser{
/**
* #Solr\Id
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Solr\Field(type="string")
* #Assert\NotBlank()
* #var string
* #ORM\Column(type="string", length=32, nullable=false)
*/
protected $firstName;
/**
* #Solr\Field(type="string")
* #Assert\NotBlank()
* #var string
* #ORM\Column(type="string", length=32, nullable=false)
*/
protected $lastName;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Agency", inversedBy="useragencies", cascade={"persist"})
* #ORM\JoinTable(name="user_user_agencies",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="iata8", referencedColumnName="iata8")})
* #var \AppBundle\Entity\Agency
**/
private $agencies;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Product", inversedBy="users", cascade={"persist"})
* #ORM\JoinTable(name="user_user_products",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="product_id", referencedColumnName="id")})
* #var \AppBundle\Entity\Product
**/
private $products;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Market", inversedBy="users", cascade={"persist"})
* #ORM\JoinTable(name="user_user_markets",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="market_id", referencedColumnName="id")})
* #var \AppBundle\Entity\Market
* #Solr\Field(type="string", getter="getId")
**/
private $markets;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Airline", inversedBy="users", cascade={"persist"})
* #ORM\JoinTable(name="user_user_airlines",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="airline_id", referencedColumnName="id")})
* #var \AppBundle\Entity\Airline
* #Solr\Field(type="string", getter="getId")
**/
private $airlines;
...
The properties that get overwritten are products and airlines. There is no prePersist() or preUpdate() function for this entity!
The create controller
class CreateController extends Controller
{
/**
* #Security("has_role('ROLE_USER_INVITER')")
* #Route("/user/create/{employer}", defaults={"employer": 1}
* , requirements={"employer": "\d+"}, name="userBundle_create")
*/
public function createAction($employer, Request $request)
{
$currentUser=$this->container->get('security.token_storage')->getToken()->getUser();
// get employer Object
$em = $this->getDoctrine ()->getManager ();
$repository = $this->getDoctrine()
->getRepository('UserBundle:Employer');
$employerObj = $repository->findOneByIdInContext($employer, $currentUser);
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->createUser();
$url = $this->get('router')->generate('userBundle_create', array(
'employer' => $employerObj->getId()
));
// generate form and handle
$form = $this->createForm(CreateType::class, $user
, array('employer' => $employerObj,
'action' => $url,)
);
$form->handleRequest($request);
if ($form->isValid()) {
$tokenGenerator = $this->container->get('fos_user.util.token_generator');
$user->setConfirmationToken($tokenGenerator->generateToken());
$user->setUsername($user->getEmail());
$user->setEnabled(false);
$user->setApprover($currentUser);
$user->setInviter($currentUser);
$user->addRole($user->getMainRole()->getName());
$userManager->updateUser($user);
if(count($user->getAgencies()) > 0){
$userId = rtrim($user->getId(),"_user");
$query = $em->createQuery("SELECT DISTINCT (a.market) FROM UserBundle\Entity\User u JOIN u.agencies a WHERE u.id = $userId");
$marketIds = $query->getResult();
$em = $this->getDoctrine ()->getManager ();
$repository = $this->getDoctrine()
->getRepository('AppBundle:Market');
$markets = $repository->findOneById($marketIds[0]);
$user->addMarket($markets);
$userManager->updateUser($user);
}
$this->addFlash(
'success',
'The user was created and an activation email was sent!'
);
return $this->redirectToRoute('userBundle_list');
}
return $this->render('UserBundle:User:create.html.twig', array(
'form' => $form->createView(),
'user'=>$user,
));
}
}
Here are more classes that may be useful for helping to point me in the right direction.
CreateType
class CreateType extends AbstractType
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
'employer' => null
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$employer = $options['employer'];
$builder
->add('firstName', 'text', array('label' => 'label.firstname',
'translation_domain' => 'User',))
->add('lastName', 'text', array('label' => 'label.lastname',
'translation_domain' => 'User',))
;
$user = $this->tokenStorage->getToken()->getUser();
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($user, $employer){
$form = $event->getForm();
// show iata selection if empoyer is any agency
$employerArr = array(10, 11);
if (in_array($employer->getId(), $employerArr)) {
// only show iata based on user's employer
if($user->hasRoles(array('ROLE_AGENCY_TC_ONLY',
'ROLE_AGENCY_WAIVER_REQUEST_ONLY',
'ROLE_AGENCY_CORE_TEAM',
'ROLE_AGENCY',
'ROLE_AGENCY_MANAGEMENT',
'ROLE_AGENCY_SIGNEE','ROLE_CORPORATION_TRAVEL_MANAGER' ))){
$form
->add('agencies', EntityType::class, array(
'class' => 'AppBundle:Agency',
'query_builder' => function (EntityRepository $er) use ($user) {
return $er->createQueryBuilder('a')
->addOrderBy('a.id', 'ASC')
->andWhere('a.id IN (:ids)')
->setParameter('ids',$user->getAgencies());
},
'choice_label' => 'agencyName',
// 'data' => $user->getAgencies(),
'label' => 'label.iata',
'empty_value' => "label.select.agency",
'property' => 'id',
'expanded' => false, 'multiple' => true,
'required' => true,
'translation_domain' => 'User',
'choice_translation_domain' => 'User'));
} else {
// only show iata based on user's context
$form
->add('agencies', EntityType::class, array(
'class' => 'AppBundle:Agency',
'query_builder' => function (EntityRepository $er) use ($user) {
return $er->createQueryBuilder('a')
->addOrderBy('a.id', 'ASC')
->andWhere('a.market IN (:markets)')
->setParameter('markets',$user->getMarkets());
},
'choice_label' => 'agencyName',
'label' => 'label.iata',
'empty_value' => "label.select.agency",
'property' => 'id',
'expanded' => false, 'multiple' => true,
'required' => true,
'translation_domain' => 'User',
'choice_translation_domain' => 'User'));
}
// show market only if not agency user
} else {
// only show specific markets based on user's context
$form->add('markets', 'entity', array(
'class' => 'AppBundle:Market', 'property' => 'id',
'query_builder' => function (EntityRepository $er) use ($user) {
$markets = $user->getMarkets();
return $er->createQueryBuilder('m')
->addOrderBy('m.id', 'ASC')
->andWhere('m.id IN (?1)')
->setParameter(1,$markets);
},
// 'choice_value' => 'id',
'choice_label' => 'id', 'label' => 'label.markets',
'translation_domain' => 'User',
'expanded' => false, 'multiple' => true,));
}
// only show specific roles based on user's employer
$form->add('mainRole', 'entity', array(
'class' => 'UserBundle:Role',
'query_builder' => function (EntityRepository $er) use ($employer){
$roles = $employer->getRoles();
return $er->createQueryBuilder('r')
->orderBy('r.sort', 'ASC')
->andWhere('r.id IN (?1)')
->setParameter(1,$roles);
},
'choice_label' => 'translationKey',
'choices_as_values' => true, 'label' => 'label.role',
'expanded' => true, 'multiple' => false,
'translation_domain' => 'User',
'choice_translation_domain' => 'Role',));
// only show specific airlines based on user's context
$form->add('airlines', 'entity', array(
'class' => 'AppBundle:Airline', 'property' => 'id',
'query_builder' => function (EntityRepository $er) use ($user) {
$airlines = $user->getAirlines();
return $er->createQueryBuilder('a')
->addOrderBy('a.id', 'ASC')
->andWhere('a.id IN (?1)')
->setParameter(1,$airlines);
},
'choice_value' => 'id',
'data' => $user->getAirlines(),
'choice_label' => 'id', 'label' => 'label.airlines',
'translation_domain' => 'User',
'expanded' => false, 'multiple' => true,));
// only show specific products based on user's context
$form->add('products', 'entity', array(
'class' => 'AppBundle:Product', 'property' => 'id',
'query_builder' => function (EntityRepository $er) use ($user) {
$products = $user->getProducts();
return $er->createQueryBuilder('p')
->addOrderBy('p.id', 'ASC')
->andWhere('p.id IN (?1)')
->setParameter(1,$products);
},
'choice_value' => 'id',
'data' => $user->getProducts(),
'choice_label' => 'translationKey', 'label' => 'label.products',
'translation_domain' => 'User',
'expanded' => false, 'multiple' => true,
'choice_translation_domain' => 'AppBundle',));
});
}
public function getParent()
{
return 'FOS\UserBundle\Form\Type\RegistrationFormType';
}
public function getBlockPrefix()
{
return 'userBundle_create';
}
}
Your issue is caused by these to lines:
'data' => $user->getAirlines(),
and
'data' => $user->getProducts(),
To change the new user s airlines & products collection, symfony form uses the loggedin users airlines & products collection reference.
Clone the collection when setting it as default, to remove the reference to the logged in user.
'data' => clone $user->getAirlines(),
and
'data' => clone $user->getProducts(),
Hope it helps.
Wrong assumption: Your error is in CreateType class. You use the loggedin user s properties to populate agencies, airlines and products fields of your form:
$user = $this->tokenStorage->getToken()->getUser();
...
'query_builder' => function (EntityRepository $er) use ($user) {
You should instead use the user you edit. Add in your buildForm function:
$userEdited = $builder->getData();
and then change:
'query_builder' => function (EntityRepository $er) use ($userEdited) {
in all places.
Do not forget to change also:
$user->getMarkets()
to
$userEdited->getMarkets()
in your callbacks, same for ailines & products.

sonata admin one to many and many to many form

I have OneToMany related entities Gallery and Media stored in galleries_media
and I have Many To One related and Many To One related in GalleryMedia Entitiy
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="\Entity\GalleryMedia", mappedBy="gallery", cascade={"persist", "remove"})
*/
protected $galleriesMedia;
and in GalleryMedia Entitiy
/**
* #ORM\ManyToOne(targetEntity="\Entity\Gallery", inversedBy="galleriesMedia")
* #ORM\JoinColumn(name="gallery_id", referencedColumnName="id", nullable=false)
*/
protected $gllary;
/**
* #ORM\ManyToOne(targetEntity="\Entity\Media", inversedBy="galleries")
* #ORM\JoinColumn(name="media_id", referencedColumnName="id", nullable=false)
*/
protected $media;
how i do this in form
One-To-Many : sonata_type_collection
In your case:
class GalleryAdmin
{
// ...
$formMapper
->add('galleriesMedia', 'sonata_type_collection', [
'required' => false,
'label' => 'my_galleries_media_label',
'btn_add' => 'my_add_button_name',
'type_options' => [
'delete' => false,
],
], [
'edit' => 'inline', // or standard
'inline' => 'table', // or standard
'sortable' => 'id', // by any field in your entity
'limit' => 5, // you can remove this - this is a limit of items
'allow_delete' => false,
'placeholder' => $this->trans('admin.placeholder.no_media'),
])
;
class GalleriesMediaAdmin
{
// ...
$formMapper
->add('media', 'sonata_type_model_list', [
'required' => true,
'btn_add' => false,
'btn_list' => 'name_of_list_button'
'btn_delete' => false,
'btn_catalogue' => 'admin',
'label' => 'name_of_your_label',
], [
'placeholder' => 'choose media',
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'id',
])
Many-To-Many : sonata_type_model
You can make many-to-many with doctrine:
class Gallery
{
// ...
/**
* Unidirectional Many-To-Many ()
*
* Every Gallery can have a lot of medias
*
* #ORM\ManyToMany(targetEntity="\Entity\Media")
* #ORM\JoinTable(
* name="gallery_media_table",
* joinColumns={#ORM\JoinColumn(name="gallery_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="media_id", referencedColumnName="id", onDelete="CASCADE")}
* )
*
* #Assert\Valid()
*/
protected $medias;
Then in your GalleryAdmin you should use sonata_type_model:
$formMapper
->add('medias', 'sonata_type_model', [
'multiple' => true,
'expanded' => true, // or false
'class' => Media::class,
'property' => 'name', // or any field in your media entity
'label' => 'your_label',
'btn_add' => true,
'btn_list' => false,
'btn_delete' => true,
'btn_catalogue' => 'admin', // or your own translate catalogue in my case file admin.en.yml
])

Symfony form collection custom prototype

I have a problem with form collection on symfony.
I have 3 entities Article, AdditionnalFile, AdditionnalInformation
Article entity
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="articles")
* #ORM\JoinColumn(nullable=false)
* #Gedmo\Versioned
*/
private $category;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\AdditionnalInformation", mappedBy="article", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $additionnalInformations;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\AdditionnalFile", mappedBy="article", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $additionnalFiles;
AdditionnalInformation entity
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Article", inversedBy="additionnalInformations")
*/
private $article;
/**
* #ORM\ManyToMany(targetEntity="UserLdapBundle\Entity\Group", inversedBy="additionnalInformations")
* #ORM\JoinColumn(nullable=false)
*
* #Assert\Count(
* min = 1,
* max = 5,
* minMessage = "Il faut au minimum 1 groupe autorisé",
* maxMessage = "Il faut au maximum {{ limit }} groupe autorisé"
* )
*/
private $groups;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
* #Gedmo\Versioned
* #Assert\Type(type="string")
* #Assert\NotBlank()
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="text", type="text")
* #Gedmo\Versioned
* #Assert\Type(type="string")
* #Assert\NotBlank()
*/
private $text;
I don't give the last entity because it's not important
I have create a form type for AdditionnalFile
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'title',
TextType::class,
array(
'attr' => array(
'placeholder' => 'Titre'
),
'label' => 'Titre :'
)
)
->add(
'text',
TextareaType::class,
array(
'attr' => array(
'placeholder' => 'Texte'
),
'label' => 'Texte :'
)
)
->add(
'groups',
EntityType::class,
array(
'attr' => array(
'placeholder' => 'Droits'
),
'class' => 'UserLdapBundle:Group',
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'label' => 'Accessible pour :'
)
);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => AdditionnalInformation::class,
));
}
And I have create my article formtype who "embed" my additionnalInformationType
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'title',
TextType::class,
array(
'attr' => array(
'placeholder' => 'Titre'
),
'label' => 'Titre :'
)
)
->add(
'category',
EntityType::class,
array(
'attr' => array(
'placeholder' => 'Catégorie'
),
'class' => 'AppBundle\Entity\Category',
'choice_value' => 'id',
'choice_label' => 'name',
'multiple' => false,
'label' => 'Catégorie :'
)
)
->add(
'text',
TextareaType::class,
array(
'attr' => array(
'placeholder' => 'Texte',
'class' => 'tinymce'
),
'label' => 'Texte :',
'required' => false
)
)
->add(
'tags',
TextType::class,
array(
'attr' => array(
'placeholder' => 'Tags'
),
'label' => 'Tags :'
)
)
->add(
'ticketNumber',
TextType::class,
array(
'attr' => array(
'placeholder' => 'Numéro de ticket, 301, 302,'
),
'label' => 'Numéro(s) de ticket :',
'required' => false
)
)
->add(
'groups',
EntityType::class,
array(
'attr' => array(
'placeholder' => 'Droits'
),
'class' => 'UserLdapBundle:Group',
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'label' => 'Accessible pour :'
)
)
->add(
'additionnalInformations',
CollectionType::class,
array(
'entry_type' => AdditionnalInformationType::class,
'allow_add' => true,
'label' => 'Information(s) additionnel(s) :',
'prototype' => true
)
)
->add(
'additionnalFiles',
CollectionType::class,
array(
'entry_type' => AdditionnalFileType::class,
'allow_add' => true,
'label' => 'Fichier(s) :',
'prototype' => true
)
)
->add(
'save',
SubmitType::class,
array(
'label' => 'Sauvegarder',
'attr' => array(
'class' => 'btn-primary'
)
)
);
But now i have some question... :)
How can i custom the prototype? i want to use a bootstrap panel and put the additionnalInformation form inside.
And duplicate this for add other AdditionnalInformation
Is that possible ?
Here is a working example which I have used:
{% block _article_additionnalInformations_entry_row %}
<br>
<div class="panel panel-primary">
<div class="panel-heading">Information supplémentaire
<a href="#" class="btn btn-xs btn-danger pull-right">
<span class="glyphicon glyphicon-remove confirmation-suppression"></span>
</a>
</div>
<div class="panel-body">
<div class="row">
<div class="col-lg-8">
{{ form_row(form.title) }}
{{ form_row(form.text) }}
</div>
<div class="col-lg-4">
{{ form_row(form.groups) }}
</div>
</div>
</div>
</div>
{% endblock %}
You should try to write a custom twig theme. You can find more information on this page.
You can for example try to put this code in your template (where you render your form):
{% form_theme form _self %}
{% block _additionnalFiles_entry_widget %}
<tr>
<td>{{ form_widget(form.task) }}</td>
<td>{{ form_widget(form.dueDate) }}</td>
</tr>
{% endblock %}
Just make sure to use the right block name. You can do that by understanding how form fragment are named or just inspect a {{ dump(form) }} in your template.

Resources