Vai al contenuto principale
Per aggiungere una relazione ai modelli dell’applicazione che usa il pacchetto, puoi registrare il metodo di creazione della relazione con resolveRelationUsing(). Senza modificare direttamente i file dei modelli, puoi recuperare i dati, usare l’Eager loading ed eseguire query sull’esistenza della relazione come con una relazione normale. Questa pagina esamina l’implementazione ufficiale di Laravel Framework v13.40.0. Per le basi delle relazioni consulta Introduzione alle relazioni Eloquent, per il caricamento dei provider consulta Package auto-discovery.

Scegliere tra definizione normale e dinamica

La documentazione ufficiale raccomanda i normali metodi di relazione per il comune sviluppo di applicazioni, e indica che la definizione dinamica può essere utile in casi come lo sviluppo di pacchetti. Le proprietà di relazione dinamiche sono una cosa diversa dalla funzionalità che registra la definizione di una relazione dall’esterno del modello. Inoltre, resolveRelationUsing() non è una Macro per aggiungere metodi arbitrari. Progetta il callback in modo che restituisca un’istanza Relation di Eloquent.

Registrare la relazione sul modello scelto dall’utente

Come esempio, aggiungiamo una relazione hasMany chiamata acmeNotes al modello degli ordini dell’applicazione. Si presuppone una struttura in cui il namespace del pacchetto Acme\OrderNotes\ corrisponde a src/ tramite PSR-4 di Composer e viene caricato come normale service provider non differito.

Preparare la configurazione e il modello correlato

Il valore iniziale in config/acme-order-notes.php è null. La semplice installazione non registra alcuna relazione sui modelli dell’applicazione.
src/Models/Note.php nel pacchetto è il modello correlato.
Nell’esempio, si presuppone che la tabella degli ordini abbia una chiave primaria intera id e che la tabella acme_order_notes abbia id, order_id, body e i timestamp. Prepara un indice su order_id. Se usi UUID o simili, fai corrispondere anche il tipo delle colonne correlate.
La registrazione di una relazione dinamica non crea tabelle né vincoli di chiave esterna. È un processo separato dalla Pubblicazione e aggiornamento delle migrazioni di un pacchetto. Anche il provider seguente non include accessi al DB né l’esecuzione di migrazioni.

Registrare il metodo di creazione in boot

In src/OrderNotesServiceProvider.php unisci la configurazione e registra il callback sul modello di destinazione in boot().
Nell’applicazione, pubblica la configurazione e imposta order_model sul modello concreto effettivamente usato, ad esempio \App\Models\Order::class. Il caricamento del provider si configura con l’auto-discovery.
In hasMany() sono indicati esplicitamente la chiave esterna order_id e la chiave del modello padre. In questo modo, anche se la classe del modello padre si chiama Purchase, non viene dedotto purchase_id e la relazione resta coerente con la definizione della tabella del pacchetto.
La configurazione del modello di destinazione spetta all’amministratore dell’applicazione. Non progettare il pacchetto in modo da ricevere nomi di classe o nomi di relazione direttamente da richieste HTTP o simili, né da registrarli di nuovo a ogni richiesta. Inoltre, registrare la relazione sul Model base di Eloquent influisce su tutti i modelli discendenti.

Usarla come una relazione normale

Sul modello degli ordini configurato, la chiamata al metodo restituisce HasMany, mentre l’accesso alla proprietà restituisce la collection dei risultati.
Con create(), hasMany imposta order_id, quindi non è necessario aggiungere la chiave esterna all’array passato dall’utente. La relazione inversa non viene aggiunta automaticamente: se serve, definiscila separatamente.

Non eseguire il recupero dei dati nel callback

Se il valore restituito dal callback è il risultato di get() o un singolo modello, non può essere trattato come una relazione valida, ad esempio durante l’accesso alla proprietà. Separa la costruzione della relazione dal recupero dei dati. Durante l’Eager loading, Builder::getRelation() costruisce la relazione su una nuova istanza del modello. Evita quindi progetti in cui il callback presuppone che “esista l’ID di un ordine già recuperato” o restituisce definizioni di relazione diverse a seconda di quell’ID. Anche con la registrazione dinamica il problema N+1 e i vincoli del Lazy Loading non scompaiono. Quando gestisci più modelli, carica le relazioni come di consueto con with() o simili.

Con belongsTo, attenzione anche alla deduzione del nome della relazione

Se il nome della relazione viene omesso, belongsTo() deduce il chiamante tramite il backtrace. Con una definizione tramite closure, invece di affidarti alla stessa deduzione di un normale metodo del modello, puoi indicare esplicitamente, oltre alla chiave esterna e alla chiave del proprietario, anche il nome della relazione come quarto argomento. Di seguito un esempio indipendente in cui l’ordine ha customer_id e il cliente ha id.

Comprendere le regole su collisioni di nomi ed ereditarietà

HasRelationships salva la closure in una proprietà static usando come chiave il nome della classe del modello e il nome della relazione. Model::__call(), quando riceve la chiamata a un metodo inesistente, cerca il resolver e lo esegue passandogli l’istanza del modello di destinazione. Il diagramma mostra il percorso quando il resolver viene trovato. Tieni presente che in caso di collisione con metodi o attributi esistenti il percorso è diverso. Usa per la relazione un nome univoco come acmeNotes e verifica che non collida con metodi e attributi dell’utente né con altri pacchetti. method_exists() da solo non permette di verificare l’esistenza di una registrazione dinamica. isRelation() di Eloquent verifica anche i resolver, ma non garantisce l’assenza di collisioni con gli attributi.

Separare la durata dello stato di registrazione e dei risultati recuperati

La registrazione del resolver e i dati di relazione già caricati conservati da ciascun modello sono cose diverse. Se sovrascrivi il resolver dopo aver già caricato $order->acmeNotes, il risultato conservato da quel modello non viene aggiornato automaticamente. Se devi recuperarlo di nuovo, rimuovi il risultato della relazione e poi usa ad esempio load('acmeNotes').
La registrazione avviene in una proprietà static, non nel service container. Se la closure cattura la richiesta, l’utente autenticato, il tenant o simili, questi possono essere conservati nei processi a lunga durata. Limita la registrazione a definizioni stabili al momento dell’avvio e, quando cambi configurazione o codice, aggiorna anche processi come i queue worker e Octane. Consulta anche Progettare il ciclo di vita dei servizi di un pacchetto.
Allo stesso modo, non presumere che ricreare l’applicazione in Testbench sia sufficiente a eliminare i resolver. Per i test che confrontano lo stato abilitato e disabilitato, usa una fixture di modello diversa o l’isolamento dei processi di PHPUnit, così da non dipendere da registrazioni precedenti.

Testare sia la registrazione sia l’SQL

Con Orchestra Testbench verifica non solo la registrazione del provider, ma anche la chiave esterna e il recupero effettivo della relazione. Di seguito un esempio di tests/DynamicRelationsTest.php. Rendi caricabili tramite Composer le classi del pacchetto e il namespace dei test, e prepara i driver PDO e SQLite.
Il modello di test si chiama OrderFixture, ma la chiave esterna resta order_id e non order_fixture_id. Questo permette di verificare che la relazione non dipenda dalla deduzione basata sul nome della classe. Nella CI del pacchetto, verifica anche i seguenti casi limite.
  • Con un’altra fixture non registrata, se order_model è null la relazione non viene aggiunta.
  • L’ereditarietà verso i modelli figli e l’ordine di precedenza quando un resolver con lo stesso nome è registrato solo sul figlio.
  • In caso di collisione con metodi o attributi esistenti, l’API prevista resta utilizzabile.
  • La differenza, dopo una nuova registrazione del resolver, tra un modello che ha già caricato la relazione e un modello nuovo.
  • Tipi di colonna, indici e vincoli di chiave esterna sui DB supportati, e chiavi primarie modificate dall’utente.

Fonti primarie consultate

Ultima modifica il 4 novembre 2026