Skip to main content

Overview

Javaabu Forms registers custom Blade directives in the service provider to enhance form functionality. These directives provide syntactic sugar for common form operations.

Registered Directives

All directives are registered in FormsServiceProvider (src/FormsServiceProvider.php:35).

@model and @endmodel

The model binding directives bind data sources to form components within their scope.

Registration

Blade::directive('model', function ($model) {
    return '<?php app(\Javaabu\Forms\FormsDataBinder::class)->bind(' . $model . '); ?>';
});

Blade::directive('endmodel', function ($model) {
    return '<?php app(\Javaabu\Forms\FormsDataBinder::class)->pop(); ?>';
});

How It Works

1

@model Pushes Data

The @model directive resolves the FormsDataBinder singleton from the container and calls bind() to push the data onto the binding stack
2

Components Retrieve Data

Form components within the directive scope automatically retrieve values from the bound data
3

@endmodel Pops Data

The @endmodel directive calls pop() to remove the data from the binding stack

Usage

Binding an Eloquent Model

@model($user)
    <x-forms::input name="name" label="Name" />
    <x-forms::input name="email" label="Email" type="email" />
    <x-forms::textarea name="bio" label="Bio" />
@endmodel
Compiled output:
<?php app(\Javaabu\Forms\FormsDataBinder::class)->bind($user); ?>
    <x-forms::input name="name" label="Name" />
    <x-forms::input name="email" label="Email" type="email" />
    <x-forms::textarea name="bio" label="Bio" />
<?php app(\Javaabu\Forms\FormsDataBinder::class)->pop(); ?>

Binding an Array

@php
    $data = [
        'title' => 'My Article',
        'content' => 'Article content...',
    ];
@endphp

@model($data)
    <x-forms::input name="title" label="Title" />
    <x-forms::textarea name="content" label="Content" />
@endmodel

Nested Model Bindings

@model($company)
    <x-forms::input name="name" label="Company Name" />
    
    @model($company->address)
        <x-forms::input name="street" label="Street" />
        <x-forms::input name="city" label="City" />
        <x-forms::input name="postal_code" label="Postal Code" />
    @endmodel
    
    <x-forms::input name="phone" label="Phone" />
@endmodel
The binding stack handles nested models correctly, with inner bindings taking precedence.

Real Example from Tests

From tests/TestSupport/views/text-entry-status.blade.php:
@php
    $article = new \Javaabu\Forms\Tests\TestSupport\Models\Article();
    $article->status = \Javaabu\Forms\Tests\TestSupport\Enums\ArticleStatuses::Published;
@endphp

@model($article)
<x-forms::text-entry name="status"/>
@endmodel
From tests/TestSupport/views/text-entry-multiline.blade.php:
@php
    $org = [
        'name' => "Javaabu\nCompany"
    ];
@endphp

@model($org)
<x-forms::text-entry label="Name" name="name" multiline />
@endmodel

Component Namespace Registration

While not a directive, the service provider also registers the component namespace (src/FormsServiceProvider.php:44):
Blade::componentNamespace('Javaabu\\Forms\\Views\\Components', 'forms');
This allows you to use components with the forms:: prefix:
<x-forms::input name="title" label="Title" />
<x-forms::select name="category" :options="$categories" />
<x-forms::checkbox name="agree" label="I agree" />

Directive Compilation

Blade compiles directives at view compilation time, not at runtime. This means:
  • Directives are converted to PHP code once and cached
  • The compiled views are stored in storage/framework/views/
  • Directive logic executes on every page render after compilation

Example Compilation

Original Blade:
@model($user)
    <x-forms::input name="name" label="Name" />
@endmodel
Compiled PHP (simplified):
<?php app(\Javaabu\Forms\FormsDataBinder::class)->bind($user); ?>
<?php
    $component = $__componentOriginal->resolve(['name' => 'name', 'label' => 'Name']);
    $component->render();
?>
<?php app(\Javaabu\Forms\FormsDataBinder::class)->pop(); ?>

Singleton Data Binder

The FormsDataBinder is registered as a singleton (src/FormsServiceProvider.php:55):
$this->app->singleton(FormsDataBinder::class, fn () => new FormsDataBinder());
This ensures:
  • Only one instance exists per request
  • All components access the same binding stack
  • Model bindings persist throughout view rendering
  • The binding stack is cleared after the request

Best Practices

Always Close Directives

<!-- Good -->
@model($user)
    <x-forms::input name="name" label="Name" />
@endmodel

<!-- Bad: Unclosed directive -->
@model($user)
    <x-forms::input name="name" label="Name" />
Unclosed @model directives leave data on the binding stack, which can cause unexpected behavior in subsequent components.

Use @model for Multiple Fields

When binding data to many fields, use @model instead of repeating :model on each component:
<!-- Good: Clean and maintainable -->
@model($user)
    <x-forms::input name="name" label="Name" />
    <x-forms::input name="email" label="Email" />
    <x-forms::input name="phone" label="Phone" />
    <x-forms::textarea name="bio" label="Bio" />
@endmodel

<!-- Verbose: Works but repetitive -->
<x-forms::input name="name" label="Name" :model="$user" />
<x-forms::input name="email" label="Email" :model="$user" />
<x-forms::input name="phone" label="Phone" :model="$user" />
<x-forms::textarea name="bio" label="Bio" :model="$user" />

Use :model for Single Fields or Overrides

Use the :model prop when:
  • Binding a single field
  • Overriding the directive binding
  • Disabling model binding with :model="false"
@model($user)
    <x-forms::input name="name" label="Name" />
    
    <!-- Override with different model -->
    <x-forms::select name="country_id" :options="$countries" :model="$address" />
    
    <!-- Disable binding -->
    <x-forms::input name="search" label="Search" :model="false" />
@endmodel

Nested Binding Scope

Inner bindings automatically take precedence:
@model($user)
    {{-- Uses $user->name --}}
    <x-forms::input name="name" label="Name" />
    
    @model($user->profile)
        {{-- Uses $user->profile->name --}}
        <x-forms::input name="name" label="Profile Name" />
    @endmodel
    
    {{-- Back to $user->name --}}
    <x-forms::input name="email" label="Email" />
@endmodel

Directive Variables

The directives accept any PHP expression:
{{-- Variable --}}
@model($user)

{{-- Object property --}}
@model($company->address)

{{-- Array access --}}
@model($data['user'])

{{-- Method call --}}
@model($this->getUser())

{{-- Inline array --}}
@model(['name' => 'John', 'email' => '[email protected]'])
The expression passed to @model must be valid PHP that evaluates to a model, array, or any data source. It’s injected directly into the compiled PHP code.

Extending with Custom Directives

You can register additional directives in your own service provider:
use Illuminate\Support\Facades\Blade;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // Custom directive for read-only forms
        Blade::directive('readonly', function () {
            return '<?php config(["forms.readonly" => true]); ?>';
        });
        
        Blade::directive('endreadonly', function () {
            return '<?php config(["forms.readonly" => false]); ?>';
        });
    }
}
Usage:
@readonly
    <x-forms::input name="name" label="Name" disabled />
    <x-forms::input name="email" label="Email" disabled />
@endreadonly

Clearing the View Cache

After modifying directives or upgrading the package, clear the view cache:
php artisan view:clear
This forces Laravel to recompile all Blade views with the updated directive logic.

Build docs developers (and LLMs) love