Blade Directives and Control Structures

Module 19: PHP Backend - Laravel

Introduction to Blade Directives

Blade directives are powerful commands that extend the templating engine's capabilities, providing shortcuts for common tasks and control structures. They all begin with the @ symbol followed by the directive name.

Think of directives as specialized tools in a craftsperson's toolkit - each one designed for a specific purpose, making your work more efficient and expressive.

mindmap root((Blade Directives)) Control Flow if/else/endif switch/case for/foreach while forelse Template Structure extends section/show yield include component Authentication auth/guest can/cannot Assets & URLs asset route url Utility csrf method php json Custom user-defined

This lecture explores these directives in depth, showcasing how they streamline template development and help maintain clean, maintainable views.

Advanced Conditional Directives

Authentication Conditionals

Blade provides convenient directives for authentication-related checks:


<!-- Check if user is authenticated -->
@auth
    <p>Welcome, {{ Auth::user()->name }}</p>
    <a href="{{ route('logout') }}">Logout</a>
@endauth

<!-- Check if user is a guest (not authenticated) -->
@guest
    <a href="{{ route('login') }}">Login</a>
    <a href="{{ route('register') }}">Register</a>
@endguest
            

You can also specify which authentication guard to check:


@auth('admin')
    <!-- Admin is logged in... -->
@endauth
            

These directives are like specialized bouncers for your application - they quickly check if someone belongs to a specific group and direct them accordingly.

Environment Directives

Conditionally display content based on the application environment:


<!-- Only in production environment -->
@production
    <script src="{{ mix('js/app.js') }}"></script>
@endproduction

<!-- Only in non-production environments -->
@env('local', 'staging')
    <div class="env-banner">{{ config('app.env') }} Environment</div>
@endenv
            

Think of these directives as context-aware assistants that understand where your application is running and adapt accordingly - like how you might dress differently for a work environment versus a casual setting.

Switch Statements

For multi-way conditionals, Blade offers a cleaner alternative to multiple if/elseif blocks:


@switch($user->role)
    @case('admin')
        <span class="badge badge-danger">Administrator</span>
        @break

    @case('manager')
        <span class="badge badge-warning">Manager</span>
        @break

    @default
        <span class="badge badge-secondary">User</span>
@endswitch
            

The switch directive works just like PHP's switch statement but with a cleaner syntax. It's like a well-organized filing system that quickly directs items to the correct location based on a key attribute.

Authorization Directives

Laravel's authentication system pairs with powerful authorization directives in Blade:

Policy Checks


<!-- Check if the current user can perform an action -->
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Edit Post</a>
@elsecan('delete', $post)
    <a href="{{ route('posts.destroy', $post) }}">Delete Post</a>
@endcan

<!-- Check if the current user cannot perform an action -->
@cannot('update', $post)
    <p>You don't have permission to edit this post.</p>
@endcannot
            

For more complex conditions, you can use @canany to check multiple abilities:


@canany(['update', 'delete'], $post)
    <div class="post-actions">
        @can('update', $post)
            <a href="{{ route('posts.edit', $post) }}">Edit</a>
        @endcan
        
        @can('delete', $post)
            <a href="{{ route('posts.destroy', $post) }}">Delete</a>
        @endcan
    </div>
@endcanany
            

These directives work with Laravel's Gate and Policy systems, providing a clean way to implement permission checks. They're like having a security system that automatically shows or hides controls based on the user's clearance level.

Form Directives

CSRF Protection

Laravel protects your forms against Cross-Site Request Forgery (CSRF) attacks. The @csrf directive inserts a hidden field with a security token:


<form method="POST" action="/profile">
    @csrf
    <!-- Form fields -->
</form>
            

This generates:


<input type="hidden" name="_token" value="randomly_generated_token">
            

Think of the CSRF token as a unique seal or stamp that proves a form submission is legitimate - like how official documents often need a notary's seal to be considered valid.

Method Field

HTML forms only support GET and POST methods, but RESTful applications often need PUT, PATCH, or DELETE requests. The @method directive helps with this:


<form method="POST" action="/profile">
    @csrf
    @method('PUT')
    <!-- Form fields -->
</form>
            

This generates:


<input type="hidden" name="_method" value="PUT">
            

Laravel will interpret this as a PUT request even though the actual HTTP method is POST. It's like having a package forwarding service - the package gets delivered to one address (POST) but is then rerouted to its actual destination (PUT).

Error Handling

Blade provides convenient directives for displaying validation errors:


<!-- Check if there are any validation errors -->
@error('email')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

<!-- Check for errors on any field -->
@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
            

This is like having a proofreader who highlights mistakes in a document, making them easy to spot and correct.

Asset Directives

URL Generation

Blade helps generate proper URLs for your assets and routes:


<!-- Generate URL for an asset in the public directory -->
<img src="{{ asset('images/logo.png') }}">

<!-- Generate a secure HTTPS URL -->
<img src="{{ secure_asset('images/logo.png') }}">

<!-- Generate URL for a named route -->
<a href="{{ route('users.show', ['id' => 1]) }}">View Profile</a>

<!-- Generate URL for a controller action -->
<a href="{{ action([UserController::class, 'show'], ['id' => 1]) }}">View Profile</a>
            

These helpers ensure your URLs are always correct, even if your application's base URL changes or you move it to a different server. They're like having a smart GPS that always knows the correct path, regardless of where you start from.

Mix Directive

When using Laravel Mix for asset compilation, the @mix directive generates the correct URLs with versioning:


<link rel="stylesheet" href="{{ mix('css/app.css') }}">
<script src="{{ mix('js/app.js') }}"></script>
            

This automatically includes the version hash for cache-busting:


<link rel="stylesheet" href="/css/app.css?id=1a2b3c4d5e6f">
            

It's like having a document versioning system that ensures everyone is looking at the most current version of a file, not an outdated cached copy.

Vite Directive

In Laravel 10, Vite is the default asset bundler. The @vite directive helps integrate with it:


<!-- Load CSS and JS from Vite -->
@vite(['resources/css/app.css', 'resources/js/app.js'])

<!-- Load only CSS -->
@vite('resources/css/app.css')
            

During development, this connects to the Vite dev server for hot module replacement. In production, it generates optimized asset links.

JSON Directive

When you need to pass PHP data to JavaScript, the @json directive is invaluable:


<script>
    var user = @json($user);
    var settings = @json($settings, JSON_PRETTY_PRINT);
</script>
            

This properly serializes PHP data structures to JSON and automatically escapes any dangerous characters. It's like having a skilled translator who not only converts between languages but also ensures the translated message is safe and accurately represents the original.

The second parameter allows you to pass options to PHP's json_encode() function.

Once Directive

For content that should only be evaluated once per rendering cycle, use @once:


@once
    <!-- This will only be evaluated once -->
    @push('scripts')
    <script>
        // Some JavaScript code
    </script>
    @endpush
@endonce
            

This is particularly useful when including the same component multiple times, but you only want to include its assets (CSS/JS) once. It's like having a smart delivery system that makes sure you don't receive duplicate packages, even if the same order is placed multiple times.

Advanced Loops and Iterations

Loop Variable Properties

Let's explore more properties of the $loop variable:


@foreach ($users as $user)
    <!-- Properties for any loop -->
    {{ $loop->index }}       <!-- 0-based iteration counter -->
    {{ $loop->iteration }}   <!-- 1-based iteration counter -->
    {{ $loop->remaining }}   <!-- Iterations remaining -->
    {{ $loop->count }}       <!-- Total items in the loop -->
    {{ $loop->first }}       <!-- Whether this is the first iteration -->
    {{ $loop->last }}        <!-- Whether this is the last iteration -->
    {{ $loop->even }}        <!-- Whether this is an even iteration -->
    {{ $loop->odd }}         <!-- Whether this is an odd iteration -->
    {{ $loop->depth }}       <!-- Nesting level of the current loop -->
    {{ $loop->parent }}      <!-- Parent loop for nested loops -->
@endforeach
            

These properties provide remarkable flexibility for creating dynamic, responsive interfaces. Consider a real-world example of a product grid that adjusts its layout based on position:


<div class="product-grid">
    @foreach ($products as $product)
        <div class="product-item 
            {{ $loop->first ? 'featured' : '' }}
            {{ $loop->iteration <= 3 ? 'top-row' : '' }}
            {{ $loop->odd ? 'odd' : 'even' }}">
            
            <h3>{{ $product->name }}</h3>
            <div class="product-details">{{ $product->description }}</div>
            
            @if ($loop->last)
                <div class="final-item-note">You've viewed all {{ $loop->count }} products</div>
            @endif
        </div>
        
        @if ($loop->iteration % 4 === 0)
            <div class="row-divider">Row {{ $loop->iteration / 4 }} complete</div>
        @endif
    @endforeach
</div>
            

This example uses loop properties to:

The $loop variable is like having a smart assistant that constantly tracks your position in a task, giving you contextual information that helps you make appropriate decisions at each step.

Custom Directives

Laravel allows you to create your own Blade directives, extending the templating engine to suit your specific needs:

Defining Custom Directives

Custom directives are registered in the boot method of your AppServiceProvider or a dedicated service provider:


// In AppServiceProvider.php
public function boot()
{
    // Simple directive 
    Blade::directive('datetime', function ($expression) {
        return "format('m/d/Y H:i'); ?>";
    });
    
    // More complex directive
    Blade::directive('routeIs', function ($expression) {
        return "routeIs($expression)): ?>";
    });
    
    Blade::directive('endrouteIs', function () {
        return "";
    });
}
            

The first parameter to the directive method is the directive name (without the @ symbol). The second parameter is a closure that returns the PHP code to be substituted.

Using Custom Directives

Once defined, you can use your custom directives in Blade templates:


<!-- Using the datetime directive -->
<p>Created: @datetime($post->created_at)</p>

<!-- Using the routeIs directive -->
@routeIs('posts.index')
    <p>You are viewing the posts index.</p>
@endrouteIs
            

Custom directives are like creating your own specialized tools for repeated tasks - once built, they save time and ensure consistency across your application.

Real-World Custom Directive Examples

Markdown Directive


// In AppServiceProvider.php
Blade::directive('markdown', function ($expression) {
    return "convert($expression); ?>";
});

// In your view
@markdown($post->content)
            

Currency Formatting


// In AppServiceProvider.php
Blade::directive('currency', function ($expression) {
    return "";
});

// In your view
<p>Product price: @currency($product->price)</p>
            

Role-Based Content


// In AppServiceProvider.php
Blade::directive('role', function ($expression) {
    return "check() && auth()->user()->hasRole($expression)): ?>";
});

Blade::directive('endrole', function () {
    return "";
});

// In your view
@role('admin')
    <div class="admin-controls">
        <!-- Admin-only controls -->
    </div>
@endrole
            

Advanced Control Structures

Complex Conditionals

Blade handles complex conditionals elegantly, making templates more readable:


<!-- Multiple conditions -->
@if (($user->isAdmin() || $user->isEditor()) && !$post->isPublished())
    <button class="publish-btn">Publish Post</button>
@endif

<!-- Using PHP functions in conditionals -->
@if (count($users) === 0)
    <p>No users found.</p>
@endif

<!-- Using model relationships in conditionals -->
@if ($post->comments->isNotEmpty())
    <h3>{{ $post->comments->count() }} Comments</h3>
    <!-- Comment list -->
@endif
            

Loop Control

Blade supports special directives for controlling loop flow:


<!-- Skip the current iteration -->
@foreach ($users as $user)
    @continue($user->isDeleted())
    
    <li>{{ $user->name }}</li>
@endforeach

<!-- Exit the loop early -->
@foreach ($users as $user)
    @break($user->id === 10)
    
    <li>{{ $user->name }}</li>
@endforeach

<!-- Conditional continue/break with syntax sugar -->
@foreach ($users as $user)
    @continue(if: $user->isDeleted())
    @break(if: $user->id === 10)
    
    <li>{{ $user->name }}</li>
@endforeach
            

These directives provide fine-grained control over your loops, similar to how a conductor precisely controls which sections of an orchestra play at any given moment.

Real-World Example: Dashboard Interface

Let's examine a more complex example that demonstrates many of the directives we've covered - a user dashboard that shows different content based on user role and authentication status:


<!-- resources/views/dashboard.blade.php -->
<div class="dashboard-container">
    <div class="sidebar">
        <div class="user-profile">
            @auth
                <img src="{{ asset('images/avatars/' . Auth::user()->avatar) }}" alt="Profile">
                <h3>{{ Auth::user()->name }}</h3>
                <p>{{ Auth::user()->email }}</p>
                
                @switch(Auth::user()->role)
                    @case('admin')
                        <span class="role-badge admin">Administrator</span>
                        @break
                    @case('moderator')
                        <span class="role-badge moderator">Moderator</span>
                        @break
                    @default
                        <span class="role-badge user">User</span>
                @endswitch
            @else
                <p>Welcome, Guest</p>
                <a href="{{ route('login') }}">Login</a> or 
                <a href="{{ route('register') }}">Register</a>
            @endauth
        </div>
        
        <nav class="sidebar-nav">
            <ul>
                <li class="{{ request()->routeIs('dashboard') ? 'active' : '' }}">
                    <a href="{{ route('dashboard') }}">Dashboard</a>
                </li>
                
                <li class="{{ request()->routeIs('profile.*') ? 'active' : '' }}">
                    <a href="{{ route('profile.show') }}">Profile</a>
                </li>
                
                @can('view-reports')
                    <li class="{{ request()->routeIs('reports.*') ? 'active' : '' }}">
                        <a href="{{ route('reports.index') }}">Reports</a>
                    </li>
                @endcan
                
                @role('admin')
                    <li class="{{ request()->routeIs('admin.*') ? 'active' : '' }}">
                        <a href="{{ route('admin.index') }}">Admin Panel</a>
                    </li>
                @endrole
            </ul>
        </nav>
    </div>
    
    <div class="main-content">
        @if (session('status'))
            <div class="alert alert-success">
                {{ session('status') }}
            </div>
        @endif
        
        <section class="stats-cards">
            @forelse ($statsCards as $card)
                <div class="stat-card {{ $loop->first ? 'primary' : '' }}">
                    <h3>{{ $card->title }}</h3>
                    <div class="stat-value">{{ $card->value }}</div>
                    @if ($card->change !== null)
                        <div class="stat-change {{ $card->change > 0 ? 'positive' : 'negative' }}">
                            {{ $card->change }}% from last period
                        </div>
                    @endif
                </div>
            @empty
                <div class="no-stats">No statistics available at this time.</div>
            @endforelse
        </section>
        
        <section class="recent-activity">
            <h2>Recent Activity</h2>
            
            @if ($activities->isNotEmpty())
                <ul class="activity-list">
                    @foreach ($activities as $activity)
                        <li class="activity-item">
                            <div class="activity-icon 
                                {{ match($activity->type) {
                                    'login' => 'login-icon',
                                    'purchase' => 'purchase-icon',
                                    'comment' => 'comment-icon',
                                    default => 'default-icon'
                                } }}">
                            </div>
                            
                            <div class="activity-details">
                                <p>{{ $activity->description }}</p>
                                <time datetime="{{ $activity->created_at->toIso8601String() }}">
                                    @datetime($activity->created_at)
                                </time>
                            </div>
                            
                            @if ($loop->iteration >= 5 && !$loop->last)
                                @break
                            @endif
                        </li>
                    @endforeach
                </ul>
                
                @if ($activities->count() > 5)
                    <a href="{{ route('activities.index') }}" class="view-all">
                        View all {{ $activities->count() }} activities
                    </a>
                @endif
            @else
                <p class="no-activity">No recent activity.</p>
            @endif
        </section>
        
        @role('admin')
            <section class="admin-quick-actions">
                <h2>Quick Actions</h2>
                <div class="action-buttons">
                    <a href="{{ route('users.create') }}" class="btn">Add User</a>
                    <a href="{{ route('reports.generate') }}" class="btn">Generate Report</a>
                    <a href="{{ route('system.logs') }}" class="btn">View Logs</a>
                </div>
            </section>
        @endrole
        
        @production
            <script src="{{ mix('js/dashboard.js') }}"></script>
        @else
            <script src="{{ asset('js/dashboard.js') }}"></script>
        @endproduction
    </div>
</div>

@push('scripts')
    <script>
        var dashboardConfig = @json([
            'refreshInterval' => config('dashboard.refresh_interval'),
            'userId' => Auth::id(),
            'notifications' => ['enabled' => true]
        ]);
    </script>
@endpush
            

This example demonstrates:

Best Practices for Blade Directives

Practice Activity

Custom Directives Exercise

Create three custom Blade directives:

  1. A @timeago directive that formats dates as relative time (e.g., "3 hours ago")
  2. A @truncate directive that limits text to a specific length with an ellipsis
  3. A @badge directive that wraps text in a styled badge element

Test each directive in a Blade template with real data.

Dashboard Interface Implementation

Based on the dashboard example provided, create your own simplified dashboard that includes:

  1. Authentication-based display differences
  2. A navigation section with active link highlighting
  3. A stats section with @forelse and $loop variable usage
  4. Role-based content sections (create a custom @role directive if needed)
  5. Environment-specific asset loading

Advanced Control Flow Challenge

Create a template for a product catalog page that demonstrates mastery of control structures:

  1. Use @forelse to display products or a "no products" message
  2. Use @switch for displaying different product badges based on status
  3. Implement @break and @continue for filtering certain products
  4. Use $loop properties to create a responsive grid layout
  5. Add authorization checks for edit/delete buttons

Summary

In the next lecture, we'll explore Blade layouts, template inheritance, and components, building on our understanding of directives and control structures.