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:
- 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
- when
resolve() runs on the bindable, it grabs the current MatchedRoute from the container
- reads
#[Preload] attributes from the route handler
- filters for ones matching
static::class
- 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.
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:
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 ofselect * from productsthenselect * from images where product_id = ?etc, it does the joins upfront.the way it works is basically:
resolve()runs on the bindable, it grabs the currentMatchedRoutefrom the container#[Preload]attributes from the route handlerstatic::class->with(...$relations)to the queryit'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.