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 relazionehasMany 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 inconfig/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.
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
Insrc/OrderNotesServiceProvider.php unisci la configurazione e registra il callback sul modello di destinazione in boot().
order_model sul modello concreto effettivamente usato, ad esempio \App\Models\Order::class. Il caricamento del provider si configura con l’auto-discovery.
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.
Usarla come una relazione normale
Sul modello degli ordini configurato, la chiamata al metodo restituisceHasMany, mentre l’accesso alla proprietà restituisce la collection dei risultati.
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 diget() 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').
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 ditests/DynamicRelationsTest.php. Rendi caricabili tramite Composer le classi del pacchetto e il namespace dei test, e prepara i driver PDO e SQLite.
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ènullla 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
- Documentazione ufficiale di Laravel: Dynamic Relationships
- Documentazione ufficiale di Laravel: Package Development
- HasRelationships: registrazione, ricerca nell’ereditarietà e gestione delle chiavi
- Model: chiamate dinamiche e cancellazione dello stato di boot
- HasAttributes: priorità degli attributi e conservazione dei risultati delle relazioni
- Builder: costruzione delle relazioni durante l’Eager loading
- QueriesRelationships: condizioni di esistenza delle relazioni
- HasOneOrMany: impostazione della chiave esterna in create
- Test ufficiali di Laravel: DatabaseEloquentDynamicRelationsTest