Dans les articles précèdent nous avons vu :
- Installer Laravel et concevoir nos templates
- Mettre en place des routes
- Créer des tables SQL et les remplir avec des données tests
Maintenant il est temps de donner un peu de vie à tout ça avec des Controller. Nous les avions déjà créé avec la commande tout en un php artisan make:model Nom --controller -- migration. Mais il et toujours possible de le faire avec la commande dédiée php artisan make:controller NomController. Ils sont créés dans le dossier app/Http/Controller et nous devrions avoir ArticleController.php, GalleryController.php, PhotoController.php, TagController.php.
On s’occupe de la galerie
Nous allons devoir modifier 3 fichiers pour afficher notre galerie :
routes/web.php: pour définir la route (en principe c’est déjà fait, mais on va vérifier)app/Http/Controllers/GalleryController.php: c’est ici qu’on va gérer la logique et les donnéesresources/views/pages/galleries.blabe.php: la page HTML renvoyée aux visiteurs
Dans notre fichier routes/web.php on doit retrouver les 2 lignes ci-dessous. Elles servent à indiquer à Laravel quel Controller utiliser si on affiche l’index de la galerie ou une galerie spécifique :
Route::get('/galleries', [GalleryController::class,'index'])->name('galleries');
Route::get('/gallery/{slug}', [ GalleryController::class, 'index' ])->name('gallery');Ensuite notre fichier app/Http/Controller/GalleryController.php va chercher toutes les galeries et les renvois à la vue (avec ajout du titre de la page : $title). Si slug n’est pas définit, j’ai choisi d’afficher la première galerie de la liste, mais tu es libre de coder un autre comportement.
namespace App\Http\Controllers;
use App\Models\Gallery;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class GalleryController extends Controller
{
/**
* Affiche la liste des galeries et la galerie active.
* Si aucun slug n'est fourni, redirige vers la première galerie.
*
* @param string|null $slug
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function index($slug = null)
{
// On va chercher toutes les galeries triées par date de création décroissante.
$galleries = Gallery::latest()->get();
// --- CAS 1 : Aucun slug n'est fourni (URL: /galleries) ---
if (!$slug) {
// On prend la 1ère galerie de la liste
$firstGallery = $galleries->first();
// S'il n'y a AUCUNE galerie en base de données, on redirige vers l'accueil.
if (!$firstGallery) {
return redirect('/');
}
// S'il y a des galeries, on redirige vers la route 'gallery/slug'
return redirect()->route('gallery', ['slug' => $firstGallery->slug]);
}
// --- CAS 2 : Un slug EST fourni (URL: /gallery/slug) ---
// On cherche la galerie active.
// On utilise 'with('photos')' pour charger la relation en même temps (Eager Loading).
// 'firstOrFail()' lèvera automatiquement une erreur 404 si le slug est introuvable.
$currentGallery = Gallery::where('slug', $slug)->with('photos')->firstOrFail();
// Si on arrive ici, le slug est valide.
// On retourne la vue en lui passant TOUTES les galeries (pour la navigation)
// et la galerie COURANTE (pour l'affichage principal).
return view("pages.galleries", [
"title" => $currentGallery->name . " - Galeries de photo amateur - [SK]Photo",
"galleries" => $galleries, // La liste complète pour la navigation
"currentGallery" => $currentGallery // La galerie active à afficher (elle contient maintenant les photos)
]);
}
}
Et finalement l’affiche de la vue :
<x-layouts.app :title="$title">
<section id="page-galleries" class="bg-[#18181a]">
<div class="container mx-auto px-6">
<div class="fixed z-50 w-full bg-[#18181a] pt-32">
<h1 class="py-4 text-5xl font-medium text-white">Galeries</h1>
<div id="gallery-filters" class="scrollbar-hide relative mt-4 flex cursor-grab select-none flex-nowrap space-x-6 overflow-x-auto border-b border-neutral-700 pb-2 text-xl font-light text-gray-400">
<!-- Conteneur pour les filtres de la galerie -->
@forelse ($galleries as $gallery)
<a href="#" class="gallery-filter-link whitespace-nowrap transition-colors hover:text-white">{{ $gallery['name'] }}</a>
@empty
Il n'y a aucune galerie disponible.
@endforelse
<!-- Bordure animée -->
<div id="active-filter-border" class="absolute bottom-[-1px] h-[2px] bg-orange-500"></div>
</div>
</div>
<div id="gallery-grid" class="relative z-40 flex flex-wrap gap-4 pt-[300px] md:gap-8">
@foreach ($gallery['photos'] as $photo)
<a href="{{ $photo['filename'] }}" data-title="{{ $photo['title'] }}"
data-description="{{ $photo['alt'] }}"
class="group relative block h-[200px] flex-grow overflow-hidden rounded-lg">
<img src="{{ $photo['filename'] }}" alt="{{ $photo['alt'] }}"
class="h-full w-full transform object-cover transition-transform duration-300 group-hover:scale-105">
<div
class="absolute inset-x-0 bottom-0 translate-y-full transform bg-black/75 p-4 text-white transition-transform duration-300 ease-in-out group-hover:translate-y-0">
<h3 class="text-lg font-semibold">{{ $photo['title'] }}</h3>
</div>
</a>
@endforeach
</div>
</div>
</section>
</x-layouts.app>Pour afficher simplement une photo, tu peux appliquer la même logique en modifiant les fichiers :
routes/web.php: toujours, pour indiquer le chemin de la page de ta photoapp/Http/Controller/PhotoController.php: pour la logique et les données de la photoresources/views/pages/photo.blade.php: pour le HTML
Moi j’ai choisi d’afficher les images avec https://www.lightgalleryjs.com/.
Au tour des articles et des tags
Et bien pour les articles… c’est la même logique! Simple non ?
Alors à ton tour de bosser en éditant les fichiers :
routes/web.php: toujours, pour indiquer le chemin de la page de ta photoapp/Http/Controller/ArticleController.php: pour la logique et les données des articlesresources/views/pages/blog.blade.phpetresources/views/pages/article.blade.php: pour le HTML
Mais comme je suis sympa, je te mets le contenu des fichiers ci-dessous.
Fichier routes/web.php :
Route::get('/blog', [ArticleController::class, 'index'])->name('blog');
Route::get('/blog/{slug}', [ArticleController::class, 'show'])->name('article');app/Http/Controller/ArticleController.php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class ArticleController extends Controller
{
// 'title' => 'Les articles du blog de photographie - [SK]Photo'
/*
* Affiche la liste des articles
*/
public function index()
{
$articles = Article::with("tags")->latest()->paginate(10);
return view("pages.blog", [
"articles" => $articles,
"title" => "Les articles du blog de photographie - [SK]Photo"
]);
}
/*
* Affiche un article en particulier
*/
public function show($slug)
{
$article = Article::whereSlug($slug)->with("tags")->firstOrFail();
return view("pages.article", [
"article" => $article,
"title" => $article->title
]);
}
}resources/views/pages/blog.blade.php
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
@forelse ($articles as $article)
<div
class="posts-item flex transform flex-col overflow-hidden rounded-xl bg-neutral-800 shadow-lg transition-transform duration-300 hover:-translate-y-2">
<a href="{{ $article->url }}" class="block">
<img src="{{ $article->featured_image }}" alt="Image de l'article {{ $article->title }}"
class="h-48 w-full object-cover">
</a>
<div class="flex flex-grow flex-col p-6">
<div>
<div class="flex space-x-4 pb-4">
@forelse ($article->tags as $tag)
<span
style="color: {{ $tag->color }}; background-color: {{ $tag->color }}33;"
class="flex rounded-md px-3 py-1 text-sm font-semibold">
{{ $tag->name }}
</span>
@empty
<p class="text-sm text-gray-500">Pas de tags pour ce post.</p>
@endforelse
</div>
<h3 class="mb-2 text-xl font-bold leading-tight text-white">
<a href="{{ $article->url }}"
class="transition-colors hover:text-orange-500">{{ $article->title }}</a>
</h3>
<p class="flex-grow text-base text-gray-400">
{{ Str::words($article->content, 30, '...') }}</p>
</div>
<div class="pt-6">
<p class="text-xs text-gray-500">{{ $article->created_at->format('d.m.Y') }}</p>
</div>
</div>
</div>
@empty
<p class="text-sm text-gray-500">Il n'y a aucune article disponible.</p>
@endforelse
</div>
<!-- Pagination -->
<div class="py-16">
{{ $articles->links() }}
</div> Il y a une petite particularité dans ce fichier. L’attribut url du Model Article n’existe pas. Nous devons le créer manuellement grâce à un assesseur.
app/Model/Article.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Article extends Model
{
use HasFactory;
//
public function tags() : BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
protected function url(): Attribute
{
return Attribute::make(
// La fonction 'get' est appelée quand on fait $photo->url
get: function ($value, $attributes) {
return route('article', ['slug' => $attributes['slug']]);;
}
);
}
}
Si tu souhaite customiser la partie pagination fournie par Laravel, tu peux taper cette commande :
php artisan vendor:publish --tag=laravel-paginationGrâce à cette commande, les fichiers de template de pagination ont été copié dans le dossier resources/views/vendor/pagination, tu peux ensuite les modifier à ta guise. Celui qui nous intéresse est tailwind.blade.php.
Il y a encore une subtilité. Tu auras remarqué que les quelques textes sont en anglais. Pour traduire en français tu devras créer les fichiers resources/lang/fr.json pour les termes génériques et resources/lang/fr/pagination.php pour les éléments propres au template de pagination.
fr.json :
{
"Showing": "Affichage de",
"to": "à",
"of": "sur",
"results": "résultats"
}fr/pagination.php :
<?php
return [
'previous' => '« Précédent',
'next' => 'Suivant »',
];Et finalement dans le fichier resources/views/pages/articles.php (pour l’affichage d’un seul article) :
<x-layouts.app :title="$title">
<h1>{{ $article->title }}</h1>
{{-- TODO: image à la une --}}
<p>Publié le
{{ $article->created_at->format('d.m.Y') }}</p>
</div>
<div class="text-xl font-normal text-white">
{{ $article->content }}
</div>
<div class="flex space-x-4 py-12">
@forelse ($article->tags as $tag)
<span
style="color: {{ $tag->color }}; background-color: {{ $tag->color }}33;"
class="flex rounded-md px-3 py-1 text-sm font-semibold">
{{ $tag->name }}
</span>
@empty
<p class="text-sm text-gray-500">Pas de tags pour ce post.</p>
@endforelse
</div>
</section>
</x-layouts.app>
Dans le prochain article, on s’attaquera à un gros morceau : le backend. On verra comment mettre en place une interface avec connexion sécurisée pour ajouter du contenu (photo et article) à notre site.