Content Resolution
Content resolution is the process of transforming raw JSON from the database into structured, typed objects that frontend components can consume.
The Resolution Pipeline
Section titled “The Resolution Pipeline”graph LR
A[Database JSON] -->|BlockCaster| B[BlockCollection]
B -->|first| C[BlockData]
B -->|all| D["BlockData[]"]
C -->|get| E[Field Value]
C -->|getSourceMap| F[CmsSourceMap]
C -->|getFieldLabels| G["Labels[]"]
How Content is Stored
Section titled “How Content is Stored”In the database, a section’s content column stores flat JSON:
[ { "type": "hero", "data": { "label": "Welcome", "headline": "Build Amazing Sites", "primary_cta_text": "Get Started", "primary_cta_url": "/contact" } }, { "type": "paragraph", "data": { "content": "We create digital experiences..." } }]Important: Data uses flat string keys, NOT nested arrays.
BlockCaster (Eloquent Cast)
Section titled “BlockCaster (Eloquent Cast)”JFA\FilamentCMSCore\CMS\BlockCaster is an Eloquent cast that automatically transforms JSON to BlockCollection:
// ContentModel (base class for Section) has:protected function casts(): array{ return [ 'content' => BlockCaster::class, ];}
// Reading the content attribute automatically returns a BlockCollection:$section = Section::find(1);$collection = $section->content; // BlockCollection (via BlockCaster)The caster:
- Decodes the JSON content array
- Creates
BlockDataDTOs for each block usingBlockRegistry - Returns a
BlockCollectionwith all blocks in original order - Handles serialization back to JSON on save
Section::resolve()
Section titled “Section::resolve()”For source-mapped content (used by frontend rendering):
use JFA\FilamentCMSCore\Models\Section;
$section = Section::find(1);$collection = $section->resolve(pageId: 5);// Returns: BlockCollection with source maps attachedThe resolver:
- Reads raw attributes (bypasses BlockCaster to avoid double-casting)
- Creates
BlockDataDTOs withBlockRegistry - Attaches source map context (section_id, page_id, block_type, block_index)
- Returns a
BlockCollectioninstance
BlockCollection
Section titled “BlockCollection”JFA\FilamentCMSCore\CMS\BlockCollection is the main container:
$collection = $section->resolve();
// Get first block (any type)$firstBlock = $collection->first();
// Get first block of a specific type$hero = $collection->first('hero');
// Get all blocks of a type$allParagraphs = $collection->all('paragraph');
// Check if a type existsif ($collection->has('hero')) { // ...}
// Count blocks$count = $collection->count();
// Iterate all blocksforeach ($collection->all() as $block) { echo $block->get('headline');}
// Convert to array (for serialization)$array = $collection->toArray();BlockData
Section titled “BlockData”JFA\FilamentCMSCore\CMS\BlockData is an abstract base class wrapping a single block:
$hero = $collection->first('hero');
// Field access via get()$label = $hero->get('label');$headline = $hero->get('headline');
// Convert to array$array = $hero->toArray();
// Get source map (for visual editing)$sourceMap = $hero->getSourceMap();
// Get field labels (auto-extracted from ContentBlock schema)$labels = $hero->getFieldLabels();GenericBlockData
Section titled “GenericBlockData”The default concrete implementation of BlockData:
use JFA\FilamentCMSCore\CMS\GenericBlockData;
// GenericBlockData provides get() for raw field access$block = $collection->first('hero');$value = $block->get('headline'); // 'Build Amazing Sites'BlockSchemaExtractor
Section titled “BlockSchemaExtractor”JFA\FilamentCMSCore\Forms\Blocks\BlockSchemaExtractor auto-extracts field labels from ContentBlock schemas:
use JFA\FilamentCMSCore\Forms\Blocks\BlockSchemaExtractor;
// Get labels for a block type$labels = BlockSchemaExtractor::getLabels('hero');// Returns: ['label' => 'Label', 'headline' => 'Headline', ...]
// Labels are automatically attached to BlockData via BlockCollection$hero = $collection->first('hero');$hero->getFieldLabels(); // ['label' => 'Label', ...]This eliminates the need for HasFieldLabels interface — labels are extracted from the ->label() calls on your Filament form components.
Source Map Injection
Section titled “Source Map Injection”During resolution via Section::resolve(), source maps are attached to each BlockData:
// Section::resolve() injects:$blockData = $blockData->withSourceMap([ 'sectionId' => $section->id, 'sectionSlug' => $section->slug, 'blockType' => $block['type'], 'blockIndex' => $index, 'pageId' => $pageId,]);This provenance metadata is what enables visual editing — it tells the system exactly which database record each rendered HTML element came from.
Complete Example
Section titled “Complete Example”namespace App\Livewire;
use JFA\FilamentCMSLivewire\Livewire\SectionComponent;use JFA\VeFilamentCMSLivewire\Concerns\InteractsWithVisualEditing;
class Hero extends SectionComponent{ use InteractsWithVisualEditing;
public string $label = ''; public string $headline = ''; public string $primaryCtaText = ''; public string $primaryCtaUrl = ''; public array $cmsSourceMap = [];
// No hydrateFromContent() needed — autoHydrate() handles it! // No fieldLabels() needed — BlockSchemaExtractor handles it!
protected function initializeVisualEditing(): void { $block = $this->block?->first(); if ($block !== null) { $this->cmsSourceMap = $block->getSourceMap(); } }
public function render(): \Illuminate\Contracts\View\View { return view('livewire.components.hero'); }}How Pages Resolve Content
Section titled “How Pages Resolve Content”The abstract Page class handles resolution automatically:
// In JFA\FilamentCMSLivewire\Livewire\Page::render()$page = \JFA\FilamentCMSCore\Models\Page::bySlug($this->slug());$sections = $page->sectionsForUI();
// sectionsForUI() returns an array of:// [// 'component' => 'App\Livewire\Hero',// 'block' => BlockCollection,// ]The page blade passes BlockCollection to each section component:
@foreach ($components as $key => $component) @livewire($component['class'], ['block' => $component['block']], key($key))@endforeachLivewire Serialization
Section titled “Livewire Serialization”BlockCollection is a complex object that can’t be serialized by Livewire directly. The base SectionComponent handles this:
// public array $blockData stores raw data (Livewire-serializable)public array $blockData = [];
// hydrate() reconstructs BlockCollection after Livewire restorepublic function hydrate(): void{ if (! empty($this->blockData)) { $this->block = new BlockCollection( $this->blockData, registry: app(BlockRegistry::class), ); }}Critical: protected properties are not serialized by Livewire. Always use public for properties that need to survive re-renders.
Performance Considerations
Section titled “Performance Considerations”- BlockCaster caching — Eloquent cast handles serialization efficiently
- BlockSchemaExtractor caching — Labels are cached per block type (static cache)
- Page slug caching —
Page::bySlug()caches lookups - Eager loading — The Page model eager-loads sections and their pivot data
- Reflection caching —
autoHydrate()caches property mappings per class