Skip to content

preload relations when resolving route model bindings #2110

@laylatichy

Description

@laylatichy

so i keep running into this thing where i have a route like /products/{product} and the model gets resolved through a bindable class, but then in the controller i immediately need to call ->load('images', 'tags') because the resource needs those relations. feels kinda wasteful because the model was just fetched 2 lines ago and now we're doing more queries.

i ended up making this attribute thing that goes on the controller method:

#[Preload(param: ProductBySlug::class, relations: ['images', 'tags', 'categories'])]
public function handle(ProductBySlug $product): Response
{
    // product already has images, tags, categories loaded
    return view('products/show', product: new ProductShowResource($product));
}

and then in the resolve method of the bindable class it reads the matched route's handler attributes and chains ->with() on the query before fetching. so instead of select * from products then select * from images where product_id = ? etc, it does the joins upfront.

the way it works is basically:

  1. attribute stores which param class it applies to and which relations to load, I imagine this will have to change to pass param name instead of class for cases where people have /categories/parentCategory/subcategories/subCategory and need different prefetching for both
  2. when resolve() runs on the bindable, it grabs the current MatchedRoute from the container
  3. reads #[Preload] attributes from the route handler
  4. filters for ones matching static::class
  5. adds ->with(...$relations) to the query

it's not a lot of code honestly. the attribute is like 15 lines and the resolve logic is maybe 20 lines. but it's really nice because you declare what you need right where you need it and the binding does the rest.

i don't know if this is something that would make sense in tempest itself? like maybe on the #[Get] or #[Post] attributes directly, or as a separate attribute like i have it. just thought i'd throw it out there because it saved us a bunch of n+1 queries and it feels like the kind of thing other people would want too.

happy to make a pr if there's interest, i just wanted to check first because i'm not sure if there's a better way to do this that i'm missing or if you already have something planned for this.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions