Advanced Blade & View Composers
While basic Blade templating is straightforward, Laravel offers powerful advanced features for building complex, maintainable view layers. In this lesson, we'll explore view composers, view creators, shared data patterns, custom Blade directives, advanced component techniques, and anonymous components to build sophisticated user interfaces.
View Composers
View composers allow you to bind data to views whenever they are rendered, keeping your controllers clean and avoiding code repetition.
// App\Providers\ViewServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Models\Category;
use App\Models\Setting;
class ViewServiceProvider extends ServiceProvider
{
public function boot()
{
// Composer for a single view
View::composer('products.index', function ($view) {
$view->with('categories', Category::all());
});
// Composer for multiple views
View::composer(['products.*', 'shop.*'], function ($view) {
$view->with([
'categories' => Category::active()->get(),
'brands' => Brand::popular()->get(),
]);
});
// Composer using wildcard
View::composer('partials.*', function ($view) {
$view->with('settings', Setting::cached());
});
// Composer for all views
View::composer('*', function ($view) {
$view->with('siteName', config('app.name'));
});
// Using a dedicated class
View::composer('layouts.sidebar', 'App\Http\View\Composers\SidebarComposer');
// Multiple composers for one view
View::composer('dashboard', [
'App\Http\View\Composers\StatsComposer',
'App\Http\View\Composers\NotificationsComposer',
]);
}
}
// Dedicated composer class
namespace App\Http\View\Composers;
use Illuminate\View\View;
use App\Models\Category;
use App\Models\Tag;
class SidebarComposer
{
public function compose(View $view)
{
$view->with([
'recentPosts' => Post::latest()->take(5)->get(),
'popularTags' => Tag::withCount('posts')
->orderBy('posts_count', 'desc')
->take(10)
->get(),
'categories' => Category::withCount('posts')
->get(),
]);
}
}
Note: View composers are resolved from the service container, so you can type-hint dependencies in their constructors for automatic dependency injection.
View Creators
View creators are similar to composers but execute immediately when the view is instantiated, rather than just before rendering.
// In ViewServiceProvider
public function boot()
{
// Creator runs immediately when view is instantiated
View::creator('layouts.app', function ($view) {
$view->with('appVersion', config('app.version'));
});
// Use case: Set default data before any manipulation
View::creator('admin.*', function ($view) {
$view->with([
'user' => auth()->user(),
'permissions' => auth()->user()->permissions ?? [],
'adminMenu' => $this->buildAdminMenu(),
]);
});
}
// Practical example: Form defaults
namespace App\Http\View\Creators;
class FormDefaultsCreator
{
public function create($view)
{
// Set default form configuration
$view->with([
'formMethod' => 'POST',
'formEnctype' => 'multipart/form-data',
'submitText' => 'Submit',
'cancelUrl' => url()->previous(),
]);
}
}
View::creator('forms.*', FormDefaultsCreator::class);
Sharing Data Across All Views
Share common data across all views efficiently using various methods.
// Method 1: View::share() in service provider
public function boot()
{
View::share('siteName', config('app.name'));
View::share('currentYear', date('Y'));
// Dynamic shared data
View::share('cartCount', function () {
return auth()->check() ? auth()->user()->cart->count() : 0;
});
}
// Method 2: Middleware for authenticated user data
namespace App\Http\Middleware;
class ShareAuthenticatedUserData
{
public function handle($request, Closure $next)
{
if (auth()->check()) {
View::share('currentUser', auth()->user());
View::share('notifications', auth()->user()->unreadNotifications);
}
return $next($request);
}
}
// Method 3: View composer for complex shared data
View::composer('*', function ($view) {
$view->with([
'navigation' => Cache::remember('main-navigation', 3600, function () {
return Menu::where('location', 'main')->with('items')->first();
}),
'footerLinks' => Cache::remember('footer-links', 3600, function () {
return Menu::where('location', 'footer')->with('items')->first();
}),
]);
});
// Method 4: Using config for global view data
// config/view.php
return [
'shared' => [
'site_name' => env('APP_NAME', 'Laravel'),
'contact_email' => env('CONTACT_EMAIL', 'info@example.com'),
'social_links' => [
'twitter' => 'https://twitter.com/example',
'facebook' => 'https://facebook.com/example',
],
],
];
// In service provider
View::share(config('view.shared'));
Tip: For expensive operations like database queries in shared data, always use caching to avoid performance issues. Use Cache::remember() with appropriate TTL values.
Custom Blade Directives
Create reusable Blade directives for common patterns and complex logic.
// In AppServiceProvider or dedicated BladeServiceProvider
use Illuminate\Support\Facades\Blade;
public function boot()
{
// Simple directive
Blade::directive('datetime', function ($expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
// Directive with default value
Blade::directive('money', function ($expression) {
return "<?php echo '$' . number_format($expression, 2); ?>";
});
// Conditional directive
Blade::directive('admin', function () {
return "<?php if(auth()->check() && auth()->user()->isAdmin()): ?>";
});
Blade::directive('endadmin', function () {
return "<?php endif; ?>";
});
// Role-based directive
Blade::directive('role', function ($role) {
return "<?php if(auth()->check() && auth()->user()->hasRole({$role})): ?>";
});
Blade::directive('endrole', function () {
return "<?php endif; ?>";
});
// Complex directive with parameters
Blade::directive('icon', function ($expression) {
return "<?php echo '<i class=\"fas fa-' . {$expression} . '\"></i>'; ?>";
});
// Directive for inline SVG
Blade::directive('svg', function ($expression) {
return "<?php echo file_get_contents(public_path('icons/' . {$expression} . '.svg')); ?>";
});
// Cache directive
Blade::directive('cache', function ($expression) {
return "<?php if(app('cache')->has({$expression})): echo app('cache')->get({$expression}); else: ob_start(); ?>";
});
Blade::directive('endcache', function ($expression) {
return "<?php \$content = ob_get_clean(); app('cache')->put({$expression}, \$content, 3600); echo \$content; endif; ?>";
});
// HTML class directive
Blade::directive('class', function ($expression) {
return "<?php echo 'class=\"' . implode(' ', array_filter({$expression})) . '\"'; ?>";
});
// Active route directive
Blade::directive('routeactive', function ($expression) {
return "<?php echo request()->routeIs({$expression}) ? 'active' : ''; ?>";
});
}
// Usage in Blade templates
@@datetime($post->created_at)
@@money(1234.56) // Outputs: $1,234.56
@@admin
<a href="/admin">Admin Panel</a>
@@endadmin
@@role('editor')
<button>Edit Post</button>
@@endrole
@@icon('home')
@@svg('logo')
@@cache('sidebar-content')
<!-- Expensive content here -->
@@endcache
<div @@class(['btn', 'btn-primary', $active ? 'active' : ''])>
<a class="@@routeactive('home')">Home</a>
Warning: Be cautious with custom directives that execute complex logic. They can make templates harder to debug. Use them for repetitive patterns, not business logic.
Advanced Blade Components
Build sophisticated, reusable components with slots, attributes, and dynamic behavior.
// resources/views/components/alert.blade.php
@@props([
'type' => 'info',
'dismissible' => false,
'icon' => null,
])
@@php
$classes = [
'alert',
'alert-' . $type,
$dismissible ? 'alert-dismissible' : '',
];
$icons = [
'success' => 'check-circle',
'error' => 'times-circle',
'warning' => 'exclamation-triangle',
'info' => 'info-circle',
];
$iconName = $icon ?? $icons[$type] ?? 'info-circle';
@@endphp
<div {{ $attributes->merge(['class' => implode(' ', $classes)]) }}>
@@if($dismissible)
<button type="button" class="close" data-dismiss="alert">
<span>×</span>
</button>
@@endif
<i class="fas fa-{{ $iconName }}"></i>
<div class="alert-content">
@@if(isset($title))
<h4 class="alert-title">{{ $title }}</h4>
@@endif
{{ $slot }}
@@if(isset($actions))
<div class="alert-actions">
{{ $actions }}
</div>
@@endif
</div>
</div>
// Usage
<x-alert type="success" dismissible>
<x-slot:title>Success!</x-slot>
Your changes have been saved.
<x-slot:actions>
<a href="/dashboard">Go to Dashboard</a>
</x-slot>
</x-alert>
// Component with class-based logic
// app/View/Components/DataTable.php
namespace App\View\Components;
use Illuminate\View\Component;
class DataTable extends Component
{
public $columns;
public $rows;
public $sortable;
public $searchable;
public function __construct($columns, $rows, $sortable = true, $searchable = true)
{
$this->columns = $columns;
$this->rows = $rows;
$this->sortable = $sortable;
$this->searchable = $searchable;
}
public function shouldShowPagination()
{
return method_exists($this->rows, 'links');
}
public function render()
{
return view('components.data-table');
}
}
// resources/views/components/data-table.blade.php
<div class="data-table-wrapper">
@@if($searchable)
<input type="text" class="table-search" placeholder="Search...">
@@endif
<table class="table">
<thead>
<tr>
@@foreach($columns as $column)
<th>
{{ $column['label'] }}
@@if($sortable && ($column['sortable'] ?? true))
<i class="sort-icon"></i>
@@endif
</th>
@@endforeach
</tr>
</thead>
<tbody>
@@forelse($rows as $row)
<tr>
@@foreach($columns as $column)
<td>{{ data_get($row, $column['field']) }}</td>
@@endforeach
</tr>
@@empty
<tr>
<td colspan="{{ count($columns) }}">No data available</td>
</tr>
@@endforelse
</tbody>
</table>
@@if($this->shouldShowPagination())
{{ $rows->links() }}
@@endif
</div>
// Usage
<x-data-table
:columns="[
['label' => 'Name', 'field' => 'name'],
['label' => 'Email', 'field' => 'email'],
['label' => 'Status', 'field' => 'status', 'sortable' => false],
]"
:rows="$users"
/>
Anonymous Components
Create simple components without dedicated classes for quick reusable UI elements.
// resources/views/components/card.blade.php
@@props([
'title' => null,
'footer' => null,
'padding' => true,
])
<div {{ $attributes->merge(['class' => 'card']) }}>
@@if($title)
<div class="card-header">
<h3>{{ $title }}</h3>
</div>
@@endif
<div class="card-body {{ $padding ? 'p-4' : 'p-0' }}">
{{ $slot }}
</div>
@@if($footer)
<div class="card-footer">
{{ $footer }}
</div>
@@endif
</div>
// resources/views/components/button.blade.php
@@props([
'variant' => 'primary',
'size' => 'md',
'type' => 'button',
'icon' => null,
])
<button
type="{{ $type }}"
{{ $attributes->merge(['class' => "btn btn-{$variant} btn-{$size}"]) }}
>
@@if($icon)
<i class="fas fa-{{ $icon }}"></i>
@@endif
{{ $slot }}
</button>
// resources/views/components/modal.blade.php
@@props([
'id',
'title',
'size' => 'md', // sm, md, lg, xl
])
<div class="modal fade" id="{{ $id }}" tabindex="-1">
<div class="modal-dialog modal-{{ $size }}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $title }}</h5>
<button type="button" class="close" data-dismiss="modal">
<span>×</span>
</button>
</div>
<div class="modal-body">
{{ $slot }}
</div>
@@if(isset($footer))
<div class="modal-footer">
{{ $footer }}
</div>
@@endif
</div>
</div>
</div>
// Usage examples
<x-card title="User Profile">
<p>User information goes here</p>
</x-card>
<x-button variant="success" icon="check">
Save Changes
</x-button>
<x-modal id="confirmModal" title="Confirm Action" size="sm">
Are you sure you want to proceed?
<x-slot:footer>
<x-button variant="secondary" data-dismiss="modal">Cancel</x-button>
<x-button variant="danger">Confirm</x-button>
</x-slot>
</x-modal>
Exercise 1: Create a view composer system for an e-commerce site. Implement composers for: (1) shopping cart data on all pages, (2) product categories in the sidebar, (3) recently viewed products. Use caching to optimize performance and ensure composers only run when necessary.
Exercise 2: Build a custom Blade directive library with: (1) @@currency for formatting money with locale support, (2) @@permission/@@endpermission for role-based content, (3) @@truncate for text truncation with "read more" links, (4) @@markdown for rendering markdown content. Test each directive thoroughly.
Exercise 3: Develop a comprehensive UI component library using Blade components. Create: (1) DataTable with sorting, filtering, and pagination, (2) Form builder with validation display, (3) TabPanel with dynamic tabs, (4) Notification system with different types (success, error, warning). Make all components highly customizable with props and slots.