Entità e relazioni polimorfiche

1

Diciamo che ho uno scaffale sul quale posso mettere gli articoli che sono fissabili (non sono sicuro che sia effettivamente una parola ma puoi capire il mio punto). Quindi ho il seguente:

class Shelf
{
    /** @var ShelfableItemInterface[] **/
    private $items = [];

    // addItem, getItems...
}

interface ShelfableItemInterface
{
}

/** let's imagine that not all books are shelfable **/
class Notebook extends Book implements ShelfableItemInterface
{
}

class PhotoFrame implements ShelfableItemInterface
{
}

Quindi quelle sono le definizioni, e ora posso creare degli scaffali con diversi elementi su di essi:

$shelf = new Shelf();
$shelf->addItem(new Notebook(...));
$shelf->addItem(new PhotoFrame(...));
$shelf->addItem(new PhotoFrame(...));

// will return the three items
var_dump($shelf->getItems());

Quindi, questo funziona perfettamente mentre tutti i dati non sono persistenti e recuperati dalla memoria.

Attualmente sto usando Doctrine ORM che può recuperare automaticamente gli oggetti correlati e creare un'istanza degli oggetti delle loro classi appropriate, ma funziona solo se tutti gli elementi correlati estendono la stessa classe che dovrebbe essere impostata nella definizione della relazione.

Questo è un problema in quanto non voglio essere costretto a estendere alcune classi (dal momento che possiamo vedere che il Notebook estende già qualcosa), ma facciamo in modo che la relazione funzioni per interfaccia.

So che avrei ancora qualche strategia di mappatura (o comunque dovremmo chiamarla), quindi per esempio nel database avremo una tabella con il seguente:

+----+---------+-----------+
| id | shef_id | item_type |
+----+---------+-----------+
|  1 |       5 |         1 |
|  2 |       5 |         2 |
|  3 |       5 |         2 |
+----+---------+-----------+

(dati aggiuntivi in tabelle per tipo)

Quindi, sono confuso su come fare questo lavoro o dove mettere il processo, in modo che l'ORM (o la mia logica) sappia che quando chiamo getItems (), quando prelevo gli elementi correlati, se "item_type "è 2, dovrebbe istanziare l'oggetto da PhotoFrame, 1 per Notebook ecc.

Non so se ho dichiarato chiaramente il mio problema, ma spero che tu possa capirmi.

Aggiornamento: vedo che questo è disponibile in java - link senza la parte dell'ereditarietà della classe, ma mi piacerebbe sapere come posso ottenere ciò in php senza tale libreria

    
posta mysubject 07.08.2018 - 14:12
fonte

1 risposta

2

Il problema qui è che al database non interessa le interfacce . Le interfacce sono un costrutto linguaggio / compilatore e non hanno alcun significato reale al di fuori dell'applicazione. Per un osservatore esterno, è impossibile vedere se due classi hanno le stesse proprietà perché un'interfaccia lo richiede, o per pura coincidenza.

Gli ORM di solito forniscono una gestione di base dell'ereditarietà, ma il database stesso non si preoccupa nemmeno dell'ereditarietà. Il database è interessato a tabelle e chiavi, niente di più (l'ho semplificato troppo, ma il punto è ancora valido).

Sembra che ti aspetti una soluzione in cui puoi implementare un'interfaccia su una classe e non dover modificare il modello di dati per riflettere tale modifica. Non funziona così. Se vuoi archiviare una relazione "A è accantonata su B", dovrai definire un FK per ogni possibile implementazione di A. Oppure elimina un vincolo FK, cosa che non suggerisco affatto.

Quello che hai qui è una relazione uno-a-molti. Uno scaffale può avere molti articoli, un articolo può essere solo su uno scaffale alla volta.

Le relazioni "uno a molti" impongono che "molti" memorizzino un FK su "uno". Che in questo caso significa che ogni ShelfableItem deve avere un ShelfId .

Il mio PHP è un po 'arrugginito. Se ricordo bene, le interfacce non possono implementare proprietà / campi, solo metodi. Non sono sicuro di come farlo in sintassi PHP, ma puramente per mostrare un esempio di base, userò C #.

L'interfaccia include FK:

public interface ShelfableItemInterface
{
    int ShelfId { get; set; }
}

Implementa l'interfaccia in modo che gli articoli abbiano effettivamente un ShelfId :

public class Book : ShelfableItemInterface
{
    public int Id { get; set; }

    //other fields

    public int ShelfId { get; set; }
}

public class Jar: ShelfableItemInterface
{
    public int Id { get; set; }

    //other fields

    public int ShelfId { get; set; }
}

Memorizza questo valore nelle tue tabelle di database esistenti:

+-----------------------------+
|            BOOKS            |
+----+---------+--------------+
| id | shelfid | other fields |
+----+---------+--------------+
|  1 |       5 |              |
|  2 |       5 |              |
|  3 |       5 |              |
+----+---------+--------------+

+-----------------------------+
|            JARS             |
+----+---------+--------------+
| id | shelfid | other fields |
+----+---------+--------------+
|  1 |       5 |              |
|  2 |       5 |              |
|  3 |       5 |              |
+----+---------+--------------+

(additional data in tables per type)

Capisco perché pensi di aver bisogno di un tavolo "shelfableitems", ma in realtà è più un peso che una benedizione.

Crea un sacco di lavoro extra:

  • Devi definire i tipi di elementi.
  • Devi recuperare gli elementi dalla tabella corretta in base al loro tipo di elemento, che è meno-ovvio da fare in una singola query.
  • Si rinuncia all'integrità dei dati di avere un vincolo FK.
    • Pertanto, si corre il rischio che l'elemento di riferimento (libro / barattolo) sia già stato eliminato ma una "voce fantasma" rimane nella tabella ShelfableItems.

In alternativa, puoi creare una tabella intermedia che ha vincoli FK (facoltativi), ma poi devi creare una colonna separata per ogni FK possibile:

+----------------------------------------------------+
|            ShelfableItems                          |
+----+---------+----------+---------+----------------+
| id | shelfid |  bookid  |  jarid  |  photoframeid  |
+----+---------+----------+---------+----------------+
|  1 |       5 |    1     |  NULL   |   NULL         |
|  2 |       5 |  NULL    |   6     |   NULL         |
|  3 |       5 |  NULL    |  NULL   |   15           |
+----+---------+----------+---------+----------------+

Questo crea molto spazio sprecato. Hai N colonne, ma puoi garantire che in ogni riga, avrai colonne (N-1) che sono NULL.

Ma, aspetto ancora più importante, l'implementazione di questa tabella intermedia non è necessaria ai fini dell'integrità dei dati . Tutto funziona perfettamente senza di esso, e ci vuole meno sforzo per sviluppare / mantenere l'integrità dei dati.

    
risposta data 07.08.2018 - 14:41
fonte

Leggi altre domande sui tag