Apologies in advance if this is long.
I’m slowly migrating a running site, and major changes to the database and its (ancient and often idiotic) structure are unfortunately off-limits.
One of the central types of model in the database is publications – but, annoyingly, they’re separated out into four different tables based on publication type: books, journals, chapters and articles. Nearly all properties and functions are the same for all four types. All publications have a resource ID (RID) which is unique across all four tables, and the only way to determine what type of publication something is is by looking at the first digit of this RID; e.g., if it begins with 3, it’s a book; 5, it’s a journal, etc. Routes to display details about publications should always have the same endpoint, e.g., /publications/{RID}.
I’ve made a parent Publication class (which has no corresponding database table and isn't really instantiable, but defines all the properties and methods common to all types of publications) plus four child classes – Book, Journal, Chapter, Article – which are all instantiable (and in some cases override the parent class’s methods for certain things). The goal here is to be able to ask for/inject a Publication class or object, and get an object of the appropriate child class back.
For routes, I’ve made use of explicit model binding (through Route::bind()) to determine which model type should be returned, and that works fine: go to /publications/55555 and it resolves to an article object, while /publications/33333 resolves to a book object. So far, so good.
But what to do when the fetching is relation-based, rather than route-based?
For example, all publications have one or more contributors (authors, editors, etc.) – and vice versa, all contributors have one or more publications. There’s a sort of pivot table which links contributors to publications and shows which role the contributor plays for the given publication:
- table: books
- rid: 31234
- title: 'Great Book'
- table: journals
- rid: 51234
- title: 'Funny Journal 15'
- table: contributors
- id: 1234
- name: 'John Doe'
- ‘pivot’ table: royalties
- id: 1
- contributor_id: 1234
- rid: 31234
- role: 'author'
- id: 2
- contributor_id: 1234
- rid: 51234
- role: 'editor'
So in this case, John Doe is author of Great Book and editor of Funny Journal 15. Going from a Book or Journal object and loading contributors shouldn’t be too hard to do through a pivot table (though I haven’t actually implemented it yet)
What I cannot for the life of me figure out, though, is how I can go from a Contributor and load their publications. Essentially, I want to do this:
// In \App\Models\Contributor
public function publications() {
return $this->belongsToMany(Publication::class, 'royalties', 'contributor_id', 'rid')->withPivot('role');
}
But obviously this won’t work, since it wil try to select from the table publications, which doesn’t exist. The logic used to determine what Publication::class should ultimately resolve to is bound in routing, but not inside models like here. Models have a resolveRouteBinding function, which is defined on the model itself – but again, only for routing (as far as I can tell – but I couldn’t get that to work at all when I tried, so perhaps I’m wrong there?).
Is there some way to achieve this sort of ‘lazy-loading’, one that isn’t limited to routing, but will work as automatically as possible in all contexts where the parent model might be dependency-injected?