In questo esempio, prenderemo il caso concreto di una
semplice interfaccia che include diversi elementi.
Abbiamo
scelto di utilizzare una tabella "Progetti" nella quale
assegneremo dei documenti.
Di conseguenza, per un progetto,
possiamo assegnare diversi documenti. E per facilitare
l'amministrazione, ci assicureremo di poter gestire la nostra
interfaccia nidificata direttamente all'interno della vista di
modifica del nostro progetto.
Dobbiamo creare lo schema.
Innanzitutto, importiamo il nostro schema corrente in
MysqlWorbench usando la funzione "Database/reverse engineer".
Questo ci permetterà di avere le tabelle predefinite di Sonata
poiché andremo a lavorare con i media.
Con MysqlWorbench, scegliamo una relazione 1->N tra la
nostra tabella "progetti" e la tabella "progetti_medias" e poi una
relazione N->1 tra la tabella "progetti_medias" e la tabella
"media__media".
1->N = OneToMany
N->1 = ManyToOne
La sottigliezza consiste nel mettere un id autoincrementante predefinito nella nostra tabella di collegamento e non avere le nostre chiavi esterne come chiave primaria.
Ecco cosa otteniamo:
projet_id conterrà l'id della tabella
progetti e media_id conterrà
l'id della tabella media__media.
Aggiungiamo
un campo di ordine ma possiamo aggiungere tanti campi quanto
desideriamo. Perché vedremo come gestire l'intero modulo per
modificare gli elementi di progetti_medias.
In MysqlWorbench, scegliamo "Forward/reverse engineer" per inserire
il nostro schema in MySQL.
Facciamo un ultimo trucco con i
comandi di Symfony per generare le nostre entità e le nostre
interfacce di amministrazione predefinite.
bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity
bin/console make:entity --regenerate App
bin/console make:sonata:admin App/Entity/Projets
bin/console make:sonata:admin App/Entity/ProjetsMedias
È magico ma incompleto. Dobbiamo correggere le relazioni nelle
nostre 2 entità.
Definiremo già le proprietà di accesso in
entrambe le direzioni.
Per l'entità App\Entity\Projets sarà la proprietà documenti, con gli accessori addDocument e removeDocument
use Doctrine\Common\Collections\Collection;
...
...
private $documents;
public function getDocuments(): Collection
{
return $this->documents;
}
public function addDocument(ProjetsMedias $document): self
{
if (!$this->documents->contains($document)) {
$this->documents[] = $document;
$document->setProjets($this);
}
return $this;
}
public function removeDocument(ProjetsMedias $document): self
{
if ($this->documents->removeElement($document)) {
// set the owning side to null (unless already changed)
if ($document->getProjets() === $this) {
$document->setProjets(null);
}
}
return $this;
}
Per l'entità App\Entity\ProjetsMedias
sarà la proprietà progetti, con gli accessori
getProjets() e setProjets().
private $projets;
public function getProjets(): ?Projets
{
return $this->projets;
}
public function setProjets(?Projets $projets): self
{
$this->projets = $projets;
return $this;
}
Concentriamoci sulla configurazione della relazione Projets->ProjetsMedias perché qui sta il segreto.
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\OneToMany(targetEntity="App\Entity\ProjetsMedias", mappedBy="projets", cascade={"persist", "remove" })
* @ORM\JoinTable(name="projets_medias",
* joinColumns={
* @ORM\JoinColumn(name="projets_id", referencedColumnName="id")
* }
* )
*/
private $documents;
La proprietà targetEntity della nostra OneToMany deve puntare
all'entità della nostra tabella che contiene l'elenco dei
documenti e dobbiamo dargli la proprietà della nostra relazione
ManyToOne di questa entità.
Quindi è \App\Entity\ProjetsMedias
che ha una proprietà progetti. Gli diamo poi il
nome della tabella MySQL a cui deve unirsi:
projets_media. JoinColumn definisce il nome del
campo nella tabella unita che conterrà le nostre chiavi primarie
del progetto, quindi il valore della nostra proprietà
id, che deve essere memorizzato nel
projets_id di projets_media.
Poi configuriamo la nostra relazione in ProjetsMedia.
/**
* @var \Projets
*
* @ORM\ManyToOne(targetEntity="Projets",inversedBy="documents")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="projets_id", referencedColumnName="id")
* })
*/
private $projets;
public function getProjets(): ?Projets
{
return $this->projets;
}
public function setProjets(?Projets $projets): self
{
$this->projets = $projets;
return $this;
}
La proprietà targetEntity della nostra ManyToOne deve puntare
all'entità della nostra tabella che gestisce i progetti e dobbiamo
dargli la proprietà della nostra relazione OneToMany di questa
entità.
Quindi è Projets che ha una proprietà
documenti.
Gli diamo poi il campo della nostra
tabella attuale (ProjetsMedia) che memorizza la chiave primaria
del progetto. Quindi id dalla tabella
progetti che è memorizzato in
projets_id di projets_media
Questo non è altro che il contrario di ciò che abbiamo configurato per la nostra definizione di \App\Entity\Projets::$documenti
Ultimo passo, poiché abbiamo scelto di lavorare con i media, dobbiamo configurare la nostra relazione con la libreria media di Sonata e configurare gli accessori.
use Sonata\MediaBundle\Model\MediaInterface;
...
...
/**
* @var \MediaMedia
*
* @ORM\ManyToOne(targetEntity="App\Application\Sonata\MediaBundle\Entity\Media")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="media_id", referencedColumnName="id")
* })
*/
private $mediaMedia;
public function __toString(): string
{
return $this->getMediaMedia();
}
public function getMediaMedia(): ?MediaInterface
{
return $this->mediaMedia;
}
public function setMediaMedia(?MediaInterface $mediaMedia): self
{
$this->mediaMedia = $mediaMedia;
return $this;
}
Come sopra. Iniziamo dando l'entità target, qui "App\Application\Sonata\MediaBundle\Entity\Media". Perché è l'entità che abbiamo esteso nella nostra configurazione del bundle Sonata/Media.
In sonata_media.yml abbiamo questo:
sonata_media:
class:
media: App\Application\Sonata\MediaBundle\Entity\Media
gallery: App\Application\Sonata\MediaBundle\Entity\Gallery
gallery_item: App\Application\Sonata\MediaBundle\Entity\GalleryItem
category: App\Application\Sonata\ClassificationBundle\Entity\SonataClassificationCategory
Sembra ovvio ma se vi imbattete in questo articolo senza avere tutte le nozioni di base, sembra saggio ricordarvelo. Potrebbe sbloccare più di una persona. 😊
Poi JoinColumn definisce come registriamo la relazione. Quindi è il campo media_id dalla nostra tabella projets_media che conterrà il campo id della nostra tabella che gestisce i media (media__media).
Ora dobbiamo solo configurare il campo media del nostro admin che
abbiamo generato automaticamente sopra
(ProjetsMediasAdmin.php).
Per gestire un campo media, basta
utilizzare il ModelListType di Sonata. Sei libero di attivare i
pulsanti opzionali di aggiunta/modifica/lista/eliminazione.
use Sonata\AdminBundle\Form\Type\ModelListType;
...
...
protected function configureFormFields(FormMapper $form): void
{
$form
->add('projets')
->add('mediaMedia', ModelListType::class, [
'required' => false,
'btn_add'=>true,
'btn_edit'=>false,
'btn_list'=>false,
'btn_delete'=>false,
])
->add('ordre')
;
}
Le relazioni sono ora complete. Possiamo verificare che tutto funzioni attivando il nostro admin di ProjetsMedia.
Nel nostro servizio, la generazione delle nostre interfacce ci ha configurato questo:
admin.projets:
class: App\Admin\ProjetsAdmin
tags:
- { name: sonata.admin, model_class: App\Entity\Projets, controller: App\Controller\ProjetsAdminController, manager_type: orm, group: admin, label: Projets }
admin.projets_medias:
class: App\Admin\ProjetsMediasAdmin
tags:
- { name: sonata.admin, model_class: App\Entity\ProjetsMedias, controller: App\Controller\ProjetsMediasAdminController, manager_type: orm, group: admin, label: ProjetsMedias }
arguments: [~, App\Entity\ProjetsMediasAdmin, ~]
Ora possiamo andare direttamente alla nostra interfaccia per vedere se funziona.
Ma ciò che ci interessa è avere questa interfaccia di gestione direttamente nella gestione dei nostri Progetti.
E per questo useremo il CollectionType di Sonata.
Aggiungiamo al nostro admin generato automaticamente, nel
metodo che gestisce la configurazione del modulo di modifica, un
campo documenti che avrà
CollectionType come suo tipo con la sua
configurazione, e soprattutto, aggiungiamo il parametro
admin_code che punta al nome del nostro servizio
admin di projets_media.
$form->add('documents', \Sonata\Form\Type\CollectionType::class,
[
'required' => false,
'by_reference' => false,
'label'=> false],
[
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
'link_parameters' => [
'context' => 'default',
'provider' => 'sonata.media.provider.file',
'hide_context' => true
],
'admin_code' => 'admin.projets_medias'
]
);
Incidentalmente, in \App\Admin\ProjetsMediasAdmin, rimuoviamo il nostro campo progetti poiché il valore verrà riempito automaticamente quando l'admin è nidificato nei progetti.
protected function configureFormFields(FormMapper $form): void
{
$form
//->add('projets')
->add('mediaMedia', \Sonata\AdminBundle\Form\Type\ModelListType::class, [
'required' => false,
'btn_add'=>true,
'btn_edit'=>false,
'btn_list'=>false,
'btn_delete'=>false,
])
->add('ordre')
;
}
E la magia avviene. Ora possiamo aggiungere/rimuovere media direttamente all'interno della nostra interfaccia.
Ma non è ancora finita. Abbiamo un campo per determinare l'ordine del nostro display. Basta configurare l'admin di ProjetsMedia per modificare il tipo del campo ordine.
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
...
...
protected function configureFormFields(FormMapper $form): void
{
$form
//->add('projets')
->add('mediaMedia', ModelListType::class, [
'required' => false,
'btn_add'=>true,
'btn_edit'=>false,
'btn_list'=>false,
'btn_delete'=>false,
])
->add('ordre', HiddenType::class)
;
}
Poi configuriamo la nostra Collection in Projets, in modo che il parametro sortable punti al nostro campo corretto.
$form->add('documents', \Sonata\Form\Type\CollectionType::class,
[
'required' => false,
'by_reference' => false,
'label'=> false],
[
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'ordre',
'link_parameters' => [
'context' => 'default',
'provider' => 'sonata.media.provider.file',
'hide_context' => true
],
'admin_code' => 'admin.projets_medias'
]
);
E infine, modifichiamo la nostra relazione Projets->ProjetsMedia per impostare l'ordine desiderato.
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\OneToMany(targetEntity="App\Entity\ProjetsMedias", mappedBy="projets", cascade={"persist", "remove" })
* @ORM\JoinTable(name="projets_medias",
* joinColumns={
* @ORM\JoinColumn(name="projets_id", referencedColumnName="id")
* }
* )
* @ORM\OrderBy({"ordre" = "ASC"})
*/
private Collection $documents;
La nostra collezione può ora essere ordinata semplicemente con il trascinamento.