resolveRelationUsing(). Without editing the model file directly, you can retrieve, eager load, and query by relationship existence just as you would with a regular relationship.
This page is based on the official source of Laravel Framework v13.40.0. For relationship basics, see Eloquent relationships. For provider loading, see Package auto-discovery.
Choose between this and regular definitions
The official documentation recommends regular relationship methods for typical application development, and notes that dynamic definitions may be useful in cases such as package development.
Dynamic relationship properties are different from the feature that registers relationship definitions from outside the model. Also,
resolveRelationUsing() is not a macro that adds arbitrary methods. Design the callback to return an Eloquent Relation instance.
Register on the model chosen by the user
As an example, we add ahasMany named acmeNotes to the consuming application’s order model. This assumes the package namespace Acme\OrderNotes\ maps to src/ via Composer PSR-4 and is loaded as a regular non-deferred service provider.
Prepare the configuration and the related model
Set the default value inconfig/acme-order-notes.php to null. Installing the package alone does not register a relationship on the application’s models.
src/Models/Note.php is the related model.
id, and the acme_order_notes table has id, order_id, body, and timestamps. Add an index on order_id. If you use UUIDs or similar, make the types of the related columns match as well.
Registering a dynamic relationship does not create tables or foreign key constraints. It is a separate process from Publishing and updating package migrations. The provider below also does not include database access or migration runs.
Register the creation logic in boot
Insrc/OrderNotesServiceProvider.php, merge the configuration and register the callback on the target model in boot().
order_model to the concrete model you actually use, such as \App\Models\Order::class. Configure provider loading with auto-discovery.
order_id and the parent key are passed explicitly to hasMany(). This prevents Eloquent from guessing purchase_id even if the parent model’s class name is Purchase, and keeps it consistent with the package’s table definition.
Use it like a regular relationship
On the configured order model, calling the method returns aHasMany, and accessing the property returns the collection of results.
create(), hasMany sets order_id, so you do not need to mix the foreign key into the array passed by the user. The inverse relationship is not added automatically, so define it separately if needed.
Do not run retrieval in the callback
If the callback returns the result ofget() or a single model, it cannot be treated as a valid relationship, for example when accessed as a property. Keep building the relationship separate from retrieving data.
Builder::getRelation() for eager loading builds the relationship on a new model instance. Therefore, avoid designs where the callback assumes “a retrieved order ID exists” or returns a different relationship definition depending on that ID.
Dynamic registration does not eliminate the N+1 problem or lazy loading restrictions. When working with multiple models, load relationships with with() and the like as usual.
Be careful with relationship name guessing in belongsTo
When the relationship name is omitted,belongsTo() guesses it from the caller using a backtrace. When defining it from a closure, rather than relying on the same guessing as a regular model method, you can explicitly specify the relationship name as the fourth argument in addition to the foreign key and owner key.
The following is a standalone example where the order has customer_id and the customer has id.
Understand the rules for name collisions and inheritance
HasRelationships stores closures in a static property keyed by the model’s class name and the relationship name. Model::__call(), which receives calls to nonexistent methods, looks up the resolver and runs it, passing the target model instance.
The diagram shows the path when a resolver is found. Note that collisions with existing methods or attributes follow a different path.
Use a unique name such as
acmeNotes for the relationship and check for collisions with the user’s methods, attributes, and other packages. method_exists() alone cannot detect dynamic registrations. Eloquent’s isRelation() also checks resolvers, but it does not guarantee that there are no collisions with attributes.
Separate the lifetime of registered state from retrieved results
Resolver registrations and the loaded relationship data held by each model are different things.
Overwriting a resolver after
$order->acmeNotes has already been loaded does not automatically update the result held by that model. If you need to retrieve it again, remove the relationship result and then use load('acmeNotes') or similar.
Similarly, do not assume that recreating the application in Testbench clears resolvers. For tests that compare enabled and disabled states, use separate model fixtures or PHPUnit process isolation to avoid depending on earlier registrations.
Test both registration and SQL
With Orchestra Testbench, verify not only provider registration but also the foreign key and actual relationship retrieval. The following is an example oftests/DynamicRelationsTest.php. Make the package classes and test namespace loadable via Composer, and have the PDO and SQLite drivers available.
OrderFixture, but the foreign key remains order_id rather than order_fixture_id. This confirms that it does not depend on guessing from the class name.
In the package’s CI, also verify the following boundaries.
- With a separate unregistered fixture, no relationship is added when
order_modelisnull. - Inheritance to child models, and precedence when a resolver with the same name is registered only on the child.
- The intended API is available when colliding with existing methods or attributes.
- The difference between a model that has already loaded the relationship and a new model after the resolver is re-registered.
- Column types, indexes, and foreign key constraints on supported databases, as well as primary keys changed by the user.
Primary sources
- Laravel official documentation: Dynamic Relationships
- Laravel official documentation: Package Development
- HasRelationships: Registration, inheritance lookup, and key handling
- Model: Dynamic calls and clearing boot state
- HasAttributes: Attribute precedence and retaining relationship results
- Builder: Building relationships during eager loading
- QueriesRelationships: Relationship existence conditions
- HasOneOrMany: Setting the foreign key on create
- Laravel official tests: DatabaseEloquentDynamicRelationsTest