Advanced Laravel

Advanced Blade & View Composers

15 min Lesson 15 of 40

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>&times;</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>&times;</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.