sonata admin one to many and many to many form - symfony

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
])

Related

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()],
]);

many to many save data

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!

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.

Sonata Admin - Display attribute for each entity from sonata_type_collection in configureFormFields

all is in the title,
Actually I have this in my sonata admin :
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('images', 'sonata_type_collection', [
'label' => 'admin.solution_page.images',
'required' => false,
'by_reference' => false,
], [
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
'link_parameters' => array('context' => 'image'),
'admin_code' => 'app.admin.solution_page_has_image'
]);
}
Also I would like to display for each of my entity the media tag if a media is selected.
The current display :
Thanks in advance for your time.

Sonata Admin - Sortable behavior in admin listing

I trying to implement a sortable system in my sonata admin list.
I'm following this documentation: https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_sortable_listing.html
But it'z not working.
Here is my config:
My entity field
* #Gedmo\SortablePosition
* #ORM\Column(name="rank", type="integer")
*/
protected $rank;
My service.yml:
services:
gedmo.listener.sortable:
class: Gedmo\Sortable\SortableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "#annotation_reader" ] ]
app.admin.client:
class: AppBundle\Admin\ClientAdmin
tags:
- { name: sonata.admin, manager_type: orm, label: "Clients" }
arguments:
- ~
- AppBundle\Entity\Client
- 'PixSortableBehaviorBundle:SortableAdmin'
stof_doctrine_extensions:
orm:
default:
sortable: true
My QuestionAdmin for Sonata EDIT:
class Question extends Admin
{
public $last_position = 0;
private $positionService;
protected $datagridValues = array(
'_page' => 1,
'_sort_by' => 'ASC',
'_sort_order' => 'position',
);
public function setPositionService(PositionHandler $positionHandler)
{
$this->positionService = $positionHandler;
}
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('move', $this->getRouterIdParameter().'/move/{position}');
}
/**
* #param DatagridMapper $datagridMapper
*/
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('headline')
->add('body')
->add('category')
->add('slug');
}
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')
->add('headline', null, array(
'identifier' => true,
'label' => 'Titres',
))
->add('category', null, array(
'label' => 'Catégories',
))
->add('position')
->add('_action', 'actions', array(
'actions' => array(
'edit' => array(),
'delete' => array(),
'move' => array('template' => 'PixSortableBehaviorBundle:Default:_sort.html.twig'),
),
'label' => 'Actions',
));
}
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('headline', null, array(
'attr' => array('class' => 'span12'),
'label' => 'Questions',
))
->add('body', null, array(
'required' => false,
'label' => 'Réponses',
'attr' => array('class' => 'span12'),
))
->add('category', null, array(
'expanded' => true,
'required' => true,
'label' => 'Catégories',
'attr' => array('class' => 'radio-list vertical'),
))
->add('position', 'hidden', array(
'required' => true,
'attr' => array('class' => 'span12'),
'label' => 'Rang',
'data' => 1,
))
->add('slug', null, array(
'required' => false,
'attr' => array('class' => 'span12'),
))
->end();
}
}
I have this error:
Call to undefined method AppBundle\Entity\Faq\Question::getPosition()
1.
You have to name your DB field position
2.
In your entity use Gedmo\Mapping\Annotation as Gedmo;
3.
/*
* #Gedmo\SortablePosition
* #ORM\Column(name="position", type="integer", nullable=false)
*/
private $position;
4.
create setter and getter for it
5.
Install and configure with composer StofDoctrineExtensionsBundle and PixSOrtableBehaviorBundle
6.
and use them in AppKernel to be available
$bundles = array(
...
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new Pix\SortableBehaviorBundle\PixSortableBehaviorBundle(),
);
7.
In services.yaml enable the gedmo listener
gedmo.listener.sortable:
class: Gedmo\Sortable\SortableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "#annotation_reader" ] ]
(stick to the right indentation else your listener will not work)
8.
In your admin service connect the new sortable behavior and activate calls
admin.something:
class: AppBundle\Admin\SomethingAdmin
tags:
- { name: sonata.admin, manager_type: orm, label: Articles }
arguments:
- ~
- AppBundle\Entity\Something
- 'PixSortableBehaviorBundle:SortableAdmin'
calls:
- [setPositionService, ["#pix_sortable_behavior.position"]]
(follow the proper indentation else it will not work and will throw some strange error)
9.
in config.yml configure your stof-doctrine-extenstions bundle:
stof_doctrine_extensions:
orm:
default:
sortable: true
(sortable is indented under default in tutorial it is shown wrong)
Enjoy :)

Resources