Z3d0X / filament-fabricator

Block-Based Page Builder Skeleton for your Filament Apps
https://filamentphp.com/plugins/fabricator
MIT License
269 stars 52 forks source link

Add a hook to allow mass-preload/batch-load of related data when rendering a page's blocks #166

Open Voltra opened 3 months ago

Voltra commented 3 months ago

Some components, like media-based components, accept either an ID or a Model instance as their input. In the latter case, they simply use the instance as is. In the former, they need to load the model from the database using the ID.

As such, using said components with Fabricator (or any kind of builder / repeater) gives us an N+1 query problem. To fix this we could batch the data loading. That is the feature this PR provides.

Block::preloadRelatedData(Page $page, array &$blocks) is a public static method that is called once per block type when rendering a page. It's meant to be used for batch loading data or any kind of action that is meant to be done once per page per block type.

For instance, let's say we have a TextImageBlock class that extends from PageBlock and contains an image field (e.g. file picker, curator, media-library). Said field only stores the ID of the media model in the database, and the display component accepts either an ID or a Model instance.

In this scenario, we can implement the block's preloadRelatedData to batch-load the media models:

class TextImageBlock extends PageBlock {
  // [...]

  #[\Override]
  public static function preloadRelatedData(Page $page, array &$blocks): void {
    parent::preloadRelatedData($page, $blocks);

    $ids = collect($blocks)
      ->lazy()
      ->map(fn ($block) => $block['data']['image'])
      ->flatten()
      ->filter()
      ->unique()
      ->toArray();

    $images = Media::query()
      ->whereIn('id', $ids)
      ->get();

    $images = collect($images)->groupBy('id');

    foreach ($blocks as &$block) {
      $block['data']['image'] = data_get($images, (string) $block['data']['image'])->first();
    }
  }

  // [...]
}

This way we only have 1 database query for the medias for the whole page.

what-the-diff[bot] commented 3 months ago

PR Summary