Basics Blade

{{-- resources/views/articles/index.blade.php --}}

{{-- Afficher une variable (échappée HTML) --}}
{{ $article->title }}

{{-- HTML non échappé (dangereux avec contenu utilisateur !) --}}
{!! $article->body_html !!}

{{-- Commentaires Blade (non rendus dans le HTML) --}}
{{-- Ce commentaire n'apparaît pas dans la source --}}

{{-- PHP inline --}}
@php
    $formattedDate = $article->created_at->format('d/m/Y');
    $isAdmin = auth()->user()?->isAdmin();
@endphp

{{-- Escape les accolades pour JS --}}
@{{ variable_javascript }}

{{-- Raccourci for JSON --}}
<script>var data = @json($articles);</script>

Layouts

{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>@yield('title', 'Mon Blog') — MonSite</title>
    <link rel="stylesheet" href="/css/app.css">
    @stack('styles')
</head>
<body>
    @include('partials.navbar')

    <main class="container mx-auto py-8">
        @yield('content')
    </main>

    @include('partials.footer')
    <script src="/js/app.js"></script>
    @stack('scripts')
</body>
</html>
{{-- resources/views/articles/show.blade.php --}}
@extends('layouts.app')

@section('title', $article->title)

@section('content')
    <article>
        <h1>{{ $article->title }}</h1>
        <p>Par {{ $article->author->name }}</p>
        <div>{!! $article->body_html !!}</div>
    </article>
@endsection

@push('scripts')
    <script>// Script spécifique à cette page</script>
@endpush

Components

# Composant de classe (avec logique PHP)
php artisan make:component Alert

# Composant anonyme (juste un fichier Blade)
# → créer resources/views/components/alert.blade.php
{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info', 'dismissible' => false])

@php
$classes = match($type) {
    'success' => 'bg-green-100 border-green-500 text-green-700',
    'error'   => 'bg-red-100 border-red-500 text-red-700',
    'warning' => 'bg-yellow-100 border-yellow-500 text-yellow-700',
    default   => 'bg-blue-100 border-blue-500 text-blue-700',
};
@endphp

<div {{ $attributes->merge(['class' => "border-l-4 p-4 {$classes}"]) }}>
    {{ $slot }}
    @if($dismissible)
        <button onclick="this.parentElement.remove()">✕</button>
    @endif
</div>
{{-- Utilisation du composant --}}
<x-alert type="success">
    Article créé avec succès !
</x-alert>

<x-alert type="error" :dismissible="true" class="mt-4">
    Une erreur est survenue.
</x-alert>

{{-- Passer des variables PHP --}}
<x-card :title="$article->title" :author="$article->author">
    {{ $article->excerpt }}
</x-card>

Directives

{{-- Conditionnelles --}}
@if($article->isPublished())
    <span class="badge">Publié</span>
@elseif($article->isDraft())
    <span class="badge">Brouillon</span>
@else
    <span class="badge">Archivé</span>
@endif

@unless($user->isAdmin())
    <p>Accès restreint</p>
@endunless

{{-- Boucles --}}
@foreach($articles as $article)
    <div>
        {{ $loop->iteration }}. {{ $article->title }}
        @if($loop->first) ← Premier @endif
        @if($loop->last)  ← Dernier @endif
    </div>
@endforeach

@forelse($articles as $article)
    <li>{{ $article->title }}</li>
@empty
    <li>Aucun article</li>
@endforelse

{{-- Auth --}}
@auth
    Bonjour, {{ auth()->user()->name }} !
@endauth

@guest
    <a href="/login">Se connecter</a>
@endguest

{{-- Permissions (avec Gate/Policy) --}}
@can('update', $article)
    <a href="/articles/{{ $article->id }}/edit">Modifier</a>
@endcan

@cannot('delete', $article)
    <p>Vous ne pouvez pas supprimer cet article.</p>
@endcannot

{{-- Environnement --}}
@env('local')
    <div class="debug-bar">ENV: local</div>
@endenv

Slots nommés

{{-- resources/views/components/card.blade.php --}}
@props(['title' => null, 'footer' => null])

<div class="card">
    @if($title)
    <div class="card-header">
        {{ $title }}
    </div>
    @endif

    <div class="card-body">
        {{ $slot }}
    </div>

    @if(!$footer->isEmpty())
    <div class="card-footer">
        {{ $footer }}
    </div>
    @endif
</div>
{{-- Utilisation avec slots nommés --}}
<x-card>
    <x-slot:title>
        <h2>Mon Article</h2>
    </x-slot:title>

    <p>Contenu principal de la carte.</p>
    <p>Peut contenir du HTML complexe.</p>

    <x-slot:footer>
        <a href="#">Lire la suite</a>
    </x-slot:footer>
</x-card>

Stacks

{{-- Dans le layout : définir le point d'injection --}}
<head>
    @stack('styles')  {{-- les vues enfants pushent ici --}}
</head>
<body>
    @yield('content')
    @stack('scripts') {{-- les vues enfants pushent ici --}}
</body>
{{-- Dans une vue enfant : pousser du contenu --}}
@extends('layouts.app')

@push('styles')
    <link rel="stylesheet" href="/css/chart.css">
@endpush

@section('content')
    <canvas id="myChart"></canvas>
@endsection

@push('scripts')
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script>
        new Chart(document.getElementById('myChart'), { type: 'bar', data: @json($chartData) });
    </script>
@endpush

{{-- prepend : insérer au début du stack --}}
@prepend('scripts')
    <script>// Ce script sera avant les autres </script>
@endprepend

Formulaires & Validation

{{-- Formulaire de création --}}
<form action="{{ route('articles.store') }}" method="POST">
    @csrf  {{-- Token CSRF obligatoire pour POST/PUT/DELETE --}}

    <div>
        <label for="title">Titre</label>
        <input
            type="text"
            id="title"
            name="title"
            value="{{ old('title') }}"
            class="{{ $errors->has('title') ? 'is-invalid' : '' }}"
        >
        @error('title')
            <span class="error">{{ $message }}</span>
        @enderror
    </div>

    <div>
        <label for="body">Contenu</label>
        <textarea name="body">{{ old('body') }}</textarea>
        @error('body')
            <span class="error">{{ $message }}</span>
        @enderror
    </div>

    <button type="submit">Créer</button>
</form>

{{-- Formulaire de modification (PUT) --}}
<form action="{{ route('articles.update', $article) }}" method="POST">
    @csrf
    @method('PUT')  {{-- Simule la méthode PUT --}}
    {{-- champs... --}}
</form>

{{-- Formulaire de suppression (DELETE) --}}
<form action="{{ route('articles.destroy', $article) }}" method="POST">
    @csrf
    @method('DELETE')
    <button type="submit" onclick="return confirm('Supprimer ?')">Supprimer</button>
</form>
old('field') re-remplit le formulaire après une erreur de validation. Sans ça, l'utilisateur perd tout ce qu'il a tapé.

Intro Livewire

composer require livewire/livewire
php artisan make:livewire SearchArticles
// app/Livewire/SearchArticles.php
namespace App\Livewire;

use App\Models\Article;
use Livewire\Component;

class SearchArticles extends Component
{
    public string $search = '';

    public function render()
    {
        return view('livewire.search-articles', [
            'articles' => Article::where('title', 'like', "%{$this->search}%")->get(),
        ]);
    }
}
{{-- resources/views/livewire/search-articles.blade.php --}}
<div>
    <input wire:model.live="search" placeholder="Rechercher...">

    @foreach($articles as $article)
        <div>{{ $article->title }}</div>
    @endforeach
</div>
{{-- Utilisation dans une vue Blade normale --}}
<livewire:search-articles />

{{-- ou --}}
@livewire('search-articles')
wire:model.live met à jour le composant à chaque frappe (live). wire:model sans .live met à jour à la perte de focus. Livewire gère automatiquement les requêtes HTTP entre le client et le serveur.
← Module 02 ▶ Mini-projet 🧠 QCM Module 04 →