Guide galattiche per aspiranti programmatori

developer in miniatura seduto su un computer con fumetto che mostra codice
Lezione 31 / 33

Mapping delle relazioni con Doctrine ORM: come mappare le relazioni in Symfony

Il mapping delle relazioni in Doctrine è un concetto fondamentale che facilita la rappresentazione e la gestione dei dati in applicazioni Symfony. Come già affrontato, il “mapping” permette la creazione di collegamenti e associazioni tra le classi PHP, ovvero le Entity, e le corrispondenti tabelle del database. È un processo che consente di tradurre e sincronizzare gli oggetti dell’applicazione con i dati memorizzati all’interno di un database.

Relazioni tra tabelle in Symfony

Nel contesto dei database, le relazioni sono connessioni logiche stabilite tra diverse tabelle. Queste relazioni sono fondamentali per organizzare e strutturare i dati in modo efficiente, garantendo integrità e coerenza dei dati. Le relazioni tra le tabelle sono categorizzate principalmente in tre tipi:

 

  • Uno-a-Uno (1:1): In questo tipo di relazione, un record in una tabella è associato a un solo record in un’altra tabella.
  • Uno-a-Molti (1:N): Qui, un record in una tabella può essere associato a più record in un’altra tabella.
  • Molti-a-Molti (M:N): In questa relazione, più record in una tabella possono essere associati a più record in un’altra tabella. 

Di seguito, un esempio di relazioni tra tabelle nell’ambito di un tipo sistema di e-commerce:

mappa relazioni tra tabelle

 

  • Esempio 1:1: un ordine ha una spedizione associata ed una spedizione fa riferimento ad un solo ordine.
  • Esempio 1:N: una categoria può contenere più prodotti ma un prodotto può appartenere ad una sola categoria. 
  • Esempio M:N: un ordine può contenere più prodotti e un prodotto può essere aggiunto a più ordini.

 

Nelle sezioni successive andremo a sviluppare gli esempi elencati precedentemente all’interno del nostro progetto Symfony di test. Pronto? Cominciamo!

 

Mapping 1:1 in Symfony: relazione Uno-a-Uno

mapping relazione 1 a 1

 

Per creare le entità Ordine e Spedizione utilizziamo, come al solito, il comando make.

In Ordine aggiungiamo le proprietà stato, per tracciare lo stato dell’ordine, e prezzoTotale. In Spedizione, invece, prevediamo le proprietà corriere, trackingNumber e dataConsegnaStimata. Il passaggio successivo è quello di mettere in relazione le due entità. Accediamo in modifica ad Ordine, sempre con l’aiuto del comando make, e aggiungiamo un campo di tipo relation per mappare la relazione con Spedizione.

relazione doctrine

 

Come si nota, il comando richiede una serie di informazioni per creare correttamente la relazione desiderata tra le entità:

  1. per prima cosa viene chiesto quale è l’entità con la quale relazionare Ordine. Digitiamo “Spedizione”
  2. Il passo successivo è la selezione della tipologia di relazione tra le entità. Symfony ci propone le varie opzioni in una tabella riepilogativa spiegando anche quali sono le caratteristiche di ognuna. Nel nostro caso digitiamo “OneToOne”.
  3. La nuova proprietà spedizione in Ordine può essere null in quanto, se l’ordine è ancora in fase di validazione, la spedizione non sarà stata creata ed associata.
  4. Possiamo scegliere se aggiungere o meno in Spedizione una proprietà per accedere alla relativa istanza di Ordine. Abbiamo digitato “yes”. Successivamente, ci viene richiesto il nome di tale proprietà che chiameremo ordine.

 

Conclusa l’esecuzione di questo comando, avremo le entità Ordine e Spedizione aggiornate e collegate da una relazione di tipo 1 a 1. Di seguito, il codice sorgente delle classi Ordine e Spedizione (sono stati rimossi per brevità proprietà e metodi non inerenti la relazione creata).

 

/src/Entity/Ordine.php
<?php
// imports . . .
#[ORM\Entity(repositoryClass: OrdineRepository::class)]
class Ordine
{
    // proprietà . . . 
    #[ORM\OneToOne(inversedBy: 'ordine', cascade: ['persist', 'remove'])]
    private ?Spedizione $spedizione = null;
    // getter e setter . . .
    public function getSpedizione(): ?Spedizione
    {
        return $this->spedizione;
    }
    public function setSpedizione(?Spedizione $spedizione): static
    {
        $this->spedizione = $spedizione;
        return $this;
    }
}

/src/Entity/Spedizione.php
<?php
// imports . . .
#[ORM\Entity(repositoryClass: SpedizioneRepository::class)]
class Spedizione
{
    
     // proprietà . . .
    #[ORM\OneToOne(mappedBy: 'spedizione', cascade: ['persist', 'remove'])]
    private ?Ordine $ordine = null;
    // getter e setter . . .
    public function getOrdine(): ?Ordine
    {
        return $this->ordine;
    }
    public function setOrdine(?Ordine $ordine): static
    {
        // unset the owning side of the relation if necessary
        if ($ordine === null && $this->ordine !== null) {
            $this->ordine->setSpedizione(null);
        }
        // set the owning side of the relation if necessary
        if ($ordine !== null && $ordine->getSpedizione() !== $this) {
            $ordine->setSpedizione($this);
        }
        $this->ordine = $ordine;
        return $this;
    }
}

Notiamo l’utilizzo del PHP attribute ORM\OneToOne in entrambe le classi:

 

 // Ordine
   #[ORM\OneToOne(inversedBy: 'ordine', cascade: ['persist', 'remove'])]
    private ?Spedizione $spedizione = null;
   
    // Spedizione
    #[ORM\OneToOne(mappedBy: 'spedizione', cascade: ['persist', 'remove'])]
    private ?Ordine $ordine = null;

Tale sintassi è fondamentale per definire il mapping tra le Entity. All’interno di esse notiamo le opzioni inversedBy e mappedBy. In Doctrine, sono utilizzate per specificare e configurare le relazioni bidirezionali tra le entità. Sono essenziali per definire chiaramente la proprietà che mappa e gestisce la relazione tra le due parti coinvolte.

 

mappedBy viene utilizzato nella classe dell’entità che è il lato “inverso” della relazione, indica che la relazione è già stata mappata dall’altra entità. L’entità con mappedBy non è responsabile per la gestione della relazione ovvero non viene utilizzata per fare modifiche dirette alla relazione, come l’aggiunta o la rimozione di elementi;

 

inversedBy viene utilizzato nella classe dell’entità che è il lato “proprietario” della relazione. L’entità con inversedBy è responsabile per la gestione della relazione. Le modifiche alla relazione (come l’aggiunta o la rimozione di elementi) devono essere effettuate da questo lato per essere riconosciute e applicate anche al database.

Notiamo, infine, la presenza dell’opzione cascade. Essa è utile per definire come le operazioni effettuate su un’entità debbano riflettersi sulle entità correlate.

 

Mapping 1:N in Symfony: relazione Uno-a-Molti 

Per implementare una relazione 1:N consideriamo l’esempio Categoria-Prodotto:

mapping relazione 1 a N

 

Ogni prodotto appartiene ad una categoria specifica. A quella stessa categoria, però, possono afferire anche altri prodotti.

Utilizziamo il comando make per creare la Entity Categoria e generare la relazione.

comando make per generare modello e relazione

 

Abbiamo scelto una relazione “OneToMany” in quanto, come da schermata: ogni Categoria può essere relazionata con più oggetti di tipo Prodotto. Ogni Prodotto può avere una sola Categoria.

Il comando andrà ad aggiungere anche una nuova proprietà in Prodotto per gestire la Categoria relazionata ad una specifica istanza.

 

Lato Entity Categoria, è stata aggiunta in automatico la proprietà prodotti che definisce la relazione con una Collection di Prodotto:

 

  #[ORM\OneToMany(mappedBy: 'categoria', targetEntity: Prodotto::class)]
    private Collection $prodotti;

Dal lato di Prodotto ritroviamo la mappatura inversa:

   #[ORM\ManyToOne(inversedBy: 'prodotti')]
    private ?Categoria $categoria = null;

Mapping M:N in Symfony: relazione Molti-a-Molti

Ora è il momento di implementare l’ultimo esempio, quello relativo alle entità Prodotto e Ordine legate da una relazione di molti a molti (M:N).

mapping relazione N a N

 

Con il comando make andiamo a richiedere le modifiche alla classe Ordine. Aggiungiamo un nuovo campo chiamato prodotti di tipo relation.

comando generazione modello e relazione

 

La relazione scelta è “ManyToMany”.

All’interno di Ordine è stato aggiunto in automatico il mapping della relazione:

 

    #[ORM\ManyToMany(targetEntity: Prodotto::class, inversedBy: 'ordini')]
    private Collection $prodotti;

e i metodi per la gestione dei prodotti associati ad un ordine specifico:

 

 /**
     * @return Collection<int, Prodotto>
     */
    public function getProdotti(): Collection
    {
        return $this->prodotti;
    }
    public function addProdotti(Prodotto $prodotti): static
    {
        if (!$this->prodotti->contains($prodotti)) {
            $this->prodotti->add($prodotti);
        }
        return $this;
    }
    public function removeProdotti(Prodotto $prodotti): static
    {
        $this->prodotti->removeElement($prodotti);
        return $this;
    }

Stessa cosa è stata fatta dal lato dell’entità Prodotto che avrà come targetEntity la classe Ordine.


Tramite la relazione appena creata, attraverso un’istanza di Ordine è possibile accedere a tutti i dettagli dei prodotti associati direttamente dall’oggetto stesso attraverso il metodo getProdotti().

Contattaci senza impegno per informazioni sul corso

Scopriamo insieme se i nostri corsi fanno per te. Compila il form e aspetta la chiamata di uno dei nostri consulenti.

Contattaci senza impegno per informazioni sul corso

Pagamento rateale

Valore della rata: A PARTIRE DA 112 €/mese.

Esempio di finanziamento  

Importo finanziato: € 2440 in 24 rate da € 112 – TAN fisso 9,37% TAEG 12,57% – importo totale del credito € 2788,68.

Il costo totale del credito comprende: interessi calcolati al TAN indicato, oneri fiscali (imposta di bollo sul contratto 16,00 euro*) addebitati sulla prima rata, costo mensile di gestione pratica € 3,90, spesa di istruttoria € 0,00, spesa per invio rendicontazione periodica cartacea € 0,98 (o spesa per invio rendicontazione periodica cartacea € 0,00), imposta di bollo su rendicontazione periodica € 0,00. Modalità di rimborso obbligatoria: addebito diretto su c/c. La scadenza delle rate è determinata dal giorno della liquidazione del contratto; la data di scadenza delle rate è prevista il giorno 15 del mese. L’importo di ciascuna rata comprende una quota di capitale crescente e interessi decrescente secondo un piano di ammortamento “alla francese”. Offerta valida dal 01/07/2023 al 31/12/2023.

Messaggio pubblicitario con finalità promozionale. Per le informazioni precontrattuali richiedere sul punto vendita il documento “Informazioni europee di base sul credito ai consumatori” (SECCI) e copia del testo contrattuale. Salvo approvazione di Sella Personal Credit S.p.A. Aulab S.r.l. opera quale intermediario del credito NON in esclusiva.

* In fase di richiesta del finanziamento verrà proposta la facoltà di selezionare, in alternativa all’imposta di bollo sul contratto di 16,00 euro, l’imposta sostitutiva, pari allo 0,25% dell’importo finanziato.

Pagamento rateale

Valore della rata: A PARTIRE DA 183 €/mese.

Esempio di finanziamento 

Importo finanziato: € 3990 in 24 rate da € 183 – TAN fisso 9,37% TAEG 12,57% – importo totale del credito € 4496,56.

Il costo totale del credito comprende: interessi calcolati al TAN indicato, oneri fiscali (imposta di bollo sul contratto 16,00 euro*) addebitati sulla prima rata, costo mensile di gestione pratica € 3,90, spesa di istruttoria € 0,00, spesa per invio rendicontazione periodica cartacea € 0,98 (o spesa per invio rendicontazione periodica cartacea € 0,00), imposta di bollo su rendicontazione periodica € 0,00. Modalità di rimborso obbligatoria: addebito diretto su c/c. La scadenza delle rate è determinata dal giorno della liquidazione del contratto; la data di scadenza delle rate è prevista il giorno 15 del mese. L’importo di ciascuna rata comprende una quota di capitale crescente e interessi decrescente secondo un piano di ammortamento “alla francese”. Offerta valida dal 01/07/2023 al 31/12/2023.

Messaggio pubblicitario con finalità promozionale. Per le informazioni precontrattuali richiedere sul punto vendita il documento “Informazioni europee di base sul credito ai consumatori” (SECCI) e copia del testo contrattuale. Salvo approvazione di Sella Personal Credit S.p.A. Aulab S.r.l. opera quale intermediario del credito NON in esclusiva.

* In fase di richiesta del finanziamento verrà proposta la facoltà di selezionare, in alternativa all’imposta di bollo sul contratto di 16,00 euro, l’imposta sostitutiva, pari allo 0,25% dell’importo finanziato.