Projet - Larablog
Sommaire
Introduction
Le projet consiste à créer une plateforme de blogging collaborative où les utilisateurs peuvent s'inscrire, se connecter et créer des articles. Chaque utilisateur peut écrire, éditer et supprimer ses propres articles. Les articles peuvent être associés à des catégories et des tags pour faciliter la recherche. Les utilisateurs peuvent également commenter les articles et interagir avec d'autres utilisateurs à travers les commentaires.
Voici un aperçu de la plateforme que vous allez réaliser:
Authentification des Utilisateurs:
- Inscription, connexion et déconnexion des utilisateurs.
- Réinitialisation de mot de passe par email.
Gestion des Articles:
- Création, édition et suppression d'articles.
- Association d'articles à des catégories et des tags.
- Fonctionnalité de brouillon pour enregistrer les articles non publiés.
Gestion des Commentaires:
- Ajout, édition et suppression de commentaires sur les articles.
Interface Utilisateur Conviviale:
- Interface utilisateur intuitive pour la lecture des articles, l'écriture de commentaires et l'interaction avec d'autres utilisateurs.
Sécurité:
- Validation des données côté serveur pour prévenir les attaques CSRF et XSS.
- Modération des commentaires pour prévenir le spam et le contenu inapproprié.
Prérequis
Pour réaliser ce projet, nous allons utiliser Laravel 10. Pour réaliser le projet, vous aurez besoin des connaissainces en :
- PHP (HTML et CSS).
- Structure MVC.
- Requêtage SQL.
Avant propos
Ce TP conclut l'apprentissage des bases de Laravel. Nous referrons d'autres TP pour entretenir ces bases. Mais ce que vous allez réaliser dans ce TP est un projet complet. Vous allez mettre en pratique toutes les connaissances que vous avez acquises jusqu'à présent.
Ce TP sera noté, vous pouvez vous entraider, mais vous devez réaliser le projet seul. Lors de mon évaluation, je tiendrai compte de :
- La qualité du code (respect des conventions, indentation, nommage des variables, etc.).
- L'utilisation du versionning (Git).
- Votre prise en compte des consignes.
- Votre prise d'initiative (ajout de fonctionnalités, amélioration de l'interface, etc.).
Je ne vais pas évaluer les points suivants :
- Le fait que votre projet « fonctionne ». Ça va de soi, ce que vous me rendez doit être fonctionnel.
- Le fait que votre projet soit « beau ». Je ne suis pas designer, je ne vais pas vous demander de faire un site web digne d'un professionnel. Je vais juste m'assurer que votre interface est lisible et que vous avez fait un effort pour la rendre agréable à utiliser.
- Le code fourni dans ce document. Ce code est juste un exemple, vous pouvez le suivre ou non. Je ne vais pas vous pénaliser si vous ne suivez pas ce code à la lettre. Je vais juste m'assurer que vous avez compris les concepts et que vous les avez appliqués correctement.
Création du projet
Pour commencer, nous allons créer un nouveau projet Laravel. Pour cela, nous allons utiliser la commande :
composer create-project laravel/laravel larablog
Attention à l'emplacement du projet
Lorsque vous exécutez la commande composer create-project
, le projet est créé dans un dossier portant le nom du projet. Dans notre cas, le dossier s'appelle larablog
.
Placez-vous dans un dossier vous appartenant avant de réaliser la commande
N'oubliez pas la debugbar
N'oubliez pas d'installer la debugbar dans votre projet. Elle vous sera très utile pour débugger votre projet.
La base de données
Dans l'univers de Laravel, il est possible de réaliser la base de données de deux façons :
- En utilisant les migrations, donc complètement en code.
- En utilisant du SQL puis en générant les modèles relatifs à la base de données.
Dans ce TP, nous allons utiliser la deuxième méthode. Avant d'aller plus loin, nous allons créer la base de données de notre projet. Je vous la fournis :
-- Table des utilisateurs
CREATE TABLE `users` (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) NOT NULL,
`remember_token` varchar(100) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Table des catégories
CREATE TABLE categories (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Table des tags
CREATE TABLE tags (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Table des articles
CREATE TABLE articles (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
`user_id` bigint(20),
`title` VARCHAR(255) NOT NULL,
`content` TEXT NOT NULL,
`draft` BOOLEAN DEFAULT 1,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Table pivot pour la relation many-to-many entre articles et catégories
CREATE TABLE article_category (
`article_id` bigint(20),
`category_id` bigint(20),
FOREIGN KEY (article_id) REFERENCES articles(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
);
-- Table pivot pour la relation many-to-many entre articles et tags
CREATE TABLE article_tag (
`article_id` bigint(20),
`tag_id` bigint(20),
FOREIGN KEY (article_id) REFERENCES articles(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
-- Table des commentaires
CREATE TABLE comments (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
`user_id` bigint(20),
`article_id` bigint(20),
`content` TEXT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (article_id) REFERENCES articles(id)
);
Avant de continuer étudiont un peu la base de données :
Remarque
Vous pouvez utiliser votre machine en local pour mettre la base de données, mais mieux encore ! Vous pouvez utiliser bdd.dombtsig.local pour créer votre base de données.
Configuration de votre projet
Avant de continuer, n'oubliez pas de configurer votre projet. Pour cela, vous devez modifier le fichier .env
à la racine de votre projet.
Je vous laisse renseigner les paramètres de connexion à la base de données et le nom de l'application.
Remarque
Le fichier .env
est un fichier qui ne sera pas versionné. Il est propre à votre environnement de développement. Vous pouvez donc le modifier sans risque.
Vous avez un fichier .env.example
qui vous donne un exemple de configuration. Vous pouvez le dupliquer en .env
pour commencer à travailler.
Authentification des utilisateurs
Pour l'authentification des utilisateurs nous n'allons pas réinventer la roue. Nous allons utiliser l'excellent package « Breeze » de Laravel. Pour l'installer, nous allons utiliser la commande :
composer require laravel/breeze --dev
Une fois le package installé, nous allons l'installer dans notre projet :
php artisan breeze:install
php artisan migrate
npm install
npm run build
Remarque
Pour l'installation de Breeze, nous allons choisir « Laravel with Alpine ».
Voilà, nous avons maintenant un système d'authentification complet. Vous pouvez tester en vous inscrivant et en vous connectant. Vous pouvez également tester la réinitialisation de mot de passe.
Je vous laisse valider le bon fonctionnement avant de continuer.
Rappel
Pour lancer votre projet, vous devez utiliser la commande :
php artisan serve
Si tout fonctionne, vous devriez avoir quelque chose comme ça :
L'interface
Pour réaliser l'interface de notre projet, nous allons utiliser le framework CSS TailwindCSS. Celui-ci sera automatiquement installé dans notre projet grace à l'ajout de Breeze.
Si vous souhaitez en savoir plus sur TailwindCSS, je vous invite à consulter la documentation.
Sachez juste que TailwindCSS est un framework CSS qui permet de créer des interfaces rapidement. Il est très utilisé dans le monde du développement web. Il repose cependant sur l'utilisation de NPM, pour que votre CSS fonctionne correctement deux solutions :
- Éxecuter périodiquement la commande
npm run build
pour compiler votre CSS. - Observer les changements de votre CSS avec la commande
npm run dev
.
Comment moi je fais ?
Personnellement, j'utilise la commande npm run dev
pour observer les changements de mon CSS. Je n'ai pas besoin de compiler mon CSS à chaque fois que je fais une modification.
Génération des modèles
Maintenant que nous avons notre base de données, nous allons générer les modèles correspondants. Pour cela, nous allons utiliser la commande :
composer require reliese/laravel --dev
Cette commande va installer le package reliese/laravel
qui va nous permettre de générer les modèles. Une fois le package installé.
php artisan vendor:publish --tag=reliese-models
php artisan config:clear
Puis nous allons générer les modèles en lien avec notre base de données :
php artisan code:models --table=comments
php artisan code:models --table=articles
php artisan code:models --table=categories
php artisan code:models --table=tags
php artisan code:models --table=article_category
php artisan code:models --table=article_tag
Dans mon cas :
Je vous laisse maintenant observer les modèles générés. Vous pouvez voir que les relations entre les modèles ont été générées. C'est très pratique !
Dans le cas de la table articles
vous pouvez observer :
Quelques remarques sur la classe Article
:
- La classe
Article
hérite de la classeModel
de Laravel. - La classe
Article
possède une propriété$table
qui indique le nom de la table associée au modèle. (Dans notre cas, la tablearticles
). Vous observerez que la classe est au singulier et que la table est au pluriel. C'est une convention de Laravel. - La classe
Article
possède une propriété$fillable
qui indique les champs de la table qui peuvent être modifiés par l'utilisateur. (Dans notre cas, les champsuser_id
,title
,content
etdraft
). - La classe
Article
possède une propriété$casts
qui indique le type des champs de la table. (Dans notre cas, le champdraft
est un booléen).
Quelques remarques sur les relations générées:
- Le modèle
Article
possède une relationbelongsTo
vers le modèleUser
. C'est la relationuser
qui a été générée. (Un article appartient à un utilisateur). - Le modèle
Article
possède une relationbelongsToMany
vers le modèleCategory
. C'est la relationcategories
qui a été générée. (Un article peut avoir plusieurs catégories). - Le modèle
Article
possède une relationbelongsToMany
vers le modèleTag
. C'est la relationtags
qui a été générée. (Un article peut avoir plusieurs tags). - Le modèle
Article
possède une relationhasMany
vers le modèleComment
. C'est la relationcomments
qui a été générée. (Un article peut avoir plusieurs commentaires).
Point étape
Nous avons maintenant la base de notre projet. Nous allons pouvoir commencer à coder. N'oubliez pas de faire un premier commit de votre projet.
Pssssttt! Bien entendu, vous pouvez faire des commits au fur et à mesure de votre avancement.
Les fonctionnalités de base.
Avant d'entrer dans le coeur de la création de notre plateforme, nous allons découper en grandes étapes les fonctionnalités que nous allons réaliser.
- Espace utilisateur
- [x] Inscription
- [x] Connexion
- [x] Déconnexion
- [x] Réinitialisation de mot de passe
- [ ] Création d'un article.
- [ ] Liste des articles de l'utilisateur.
- [ ] Édition d'un article.
- [ ] Suppression d'un article.
- Partie publique
- [ ] Liste des articles publiés d'un utilisateur.
- [ ] Consultation d'un article si publié.
- [ ] Liste des commentaires d'un article.
- [ ] Ajout d'un commentaire sur un article.
Ces étapes sont la base de notre projet. Nous avons effectivement d'autres éléments à réaliser, mais nous allons commencer par ces étapes.
L'organisation
Dans un gros projet comme celui-ci, il est important de bien s'organiser. Découper le projet en étape est une bonne chose, ces différentes étapes seront vos objectifs. Vous pouvez les noter sur un papier ou dans un fichier texte. Vous pouvez également utiliser un outil de gestion de projet comme Trello.
Espace utilisateur
Dans cette partie, nous allons réaliser les fonctionnalités liées à l'espace utilisateur.
Nous allons dans un premier temps réaliser notre contrôleur UserController
. Pour cela, nous allons utiliser la commande :
php artisan make:controller UserController
Ce controller va nous permettre de gérer les fonctionnalités liées à l'espace utilisateur. Breeze nous a déjà généré toute la partie authentification, nous allons donc nous concentrer sur les fonctionnalités suivantes.
Création d'un article
Pour commencer, nous allons réaliser la création d'un article. Pour cela, nous allons créer deux routes qui vont nous permettre d'afficher le formulaire de création d'un article.
Route::get('/articles/create', [UserController::class, 'create'])->name('articles.create');
Route::post('/articles/store', [UserController::class, 'store'])->name('articles.store');
Attention
N'oubliez pas la sécurité ! Seul un utilisateur connecté doit pouvoir accéder à ces routes. À vous de placer la route au bon endroit dans le fichier web.php
.
Avant de continuer, vous pouvez vérifier le bon ajout de vos routes en utilisant la commande :
php artisan route:list
Puis nous allons créer la méthode create
dans notre contrôleur UserController
.
public function create()
{
return view('articles.create');
}
Nous allons maintenant créer la vue articles.create
qui va nous permettre d'afficher le formulaire de création d'un article.
Rappel sur les vues
Dans Laravel, le .
dans le nom de la vue correspond à un dossier. Dans notre cas, la vue articles.create
correspond au fichier resources/views/articles/create.blade.php
.
Pour commencer, nous allons créer le fichier resources/views/articles/create.blade.php
. Puis nous allons ajouter le code suivant :
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Créer un article
</h2>
</x-slot>
<form method="post" action="{{ route('articles.store') }}" class="py-12">
@csrf
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900 ">
<!-- Input de titre de l'article -->
<input type="text" name="title" id="title" placeholder="Titre de l'article" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
</div>
<div class="p-6 pt-0 text-gray-900 ">
<!-- Contenu de l'article -->
<textarea rows="30" name="content" id="content" placeholder="Contenu de l'article" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"></textarea>
</div>
<div class="p-6 text-gray-900 flex items-center">
<!-- Action sur le formulaire -->
<div class="grow">
<input type="checkbox" name="draft" id="draft" class="rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<label for="draft">Article en brouillon</label>
</div>
<div>
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Créer l'article
</button>
</div>
</div>
</div>
</div>
</form>
</x-app-layout>
Un peu de détail sur le code :
- Nous utilisons le composant
x-app-layout
pour avoir le même design que les autres pages de notre application (header, footer, etc.). - Le
x-slot
permet d'ajouter un titre à la page. - Nous avons dans la page un formulaire qui va nous permettre de créer un article. L'action du formulaire est la route
articles.store
que nous avons créée précédemment. @csrf
permet de générer un token CSRF pour sécuriser notre formulaire.- Nous avons deux champs dans notre formulaire, un champ
title
et un champcontent
. - Nous avons également un champ
draft
qui va nous permettre de savoir si l'article est un brouillon ou non. - Nous avons un bouton
submit
qui va nous permettre d'envoyer le formulaire.
Le menu
Pour l'instant nous ne pouvons pas accéder à la page de création d'un article. Nous allons donc ajouter un lien dans le menu de notre application. Pour cela, nous allons modifier le fichier resources/views/layouts/navigation.blade.php
. Pour y ajouter l'élément suivant
<x-nav-link :href="route('articles.create')" :active="request()->routeIs('article.create')">
Créer un article
</x-nav-link>
Vous devez ajouter cet élément après le dashboard
déjà présent dans le fichier.
Tester
Je vous laisse tester votre code. Vous devriez avoir quelque chose comme ça :
Enregistrer l'article
Maintenant que nous avons notre formulaire, nous allons pouvoir enregistrer notre article. Pour cela nous allons devoir faire le code derrère la route articles.store
que nous avons créée précédemment.
Notre méthode a pour but de récupérer les données du formulaire et de les enregistrer dans la base de données. Pour cela, nous allons utiliser la méthode store
du contrôleur UserController
.
public function store(Request $request)
{
// On récupère les données du formulaire
$data = $request->only(['title', 'content', 'draft']);
// Créateur de l'article (auteur)
$data['user_id'] = Auth::user()->id;
// Gestion du draft
$data['draft'] = isset($data['draft']) ? 1 : 0;
// On crée l'article
$article = Article::create($data); // $Article est l'objet article nouvellement créé
// Exemple pour ajouter la catégorie 1 à l'article
// $article->categories()->sync(1);
// Exemple pour ajouter des catégories à l'article
// $article->categories()->sync([1, 2, 3]);
// Exemple pour ajouter des catégories à l'article en venant du formulaire
// $article->categories()->sync($request->input('categories'));
// On redirige l'utilisateur vers la liste des articles
return redirect()->route('dashboard');
}
Un peu de détail sur le code :
- Nous récupérons les données du formulaire avec la méthode
only
de la classeRequest
. Cette méthode nous permet de récupérer les données du formulaire en fonction de leur nom. - Nous ajoutons l'id de l'utilisateur connecté dans les données du formulaire. Nous utilisons la classe
Auth
de Laravel pour récupérer l'utilisateur connecté. - Nous utilisons la méthode
create
du modèleArticle
pour créer l'article dans la base de données. - Nous redirigeons l'utilisateur vers la liste des articles.
Je vous laisse tester votre code. Si tout fonctionne, vous devriez avoir un article dans votre base de données.
Liste des articles de l'utilisateur
Nous allons maintenant réaliser la liste des articles de l'utilisateur. Vous l'avez peut-être remarqué dans la méthode store
de notre contrôleur UserController
, nous redirigeons l'utilisateur vers dashboard
. Nous allons donc modifier la vue dashboard
pour afficher la liste des articles de l'utilisateur.
Notre route dashboard
est la route par défaut de Breeze. Elle est définie dans le fichier routes/web.php
. Nous allons la modifier pour qu'elle utilise la méthode index
de notre contrôleur UserController
.
Route::get('/dashboard', [UserController::class, 'index'])->name('dashboard');
⚠️ Je vous laisse supprimer la route par défaut de Breeze.
Nous allons maintenant créer la méthode index
dans notre contrôleur UserController
.
public function index()
{
// On récupère l'utilisateur connecté.
$user = Auth::user();
// On retourne la vue.
return view('dashboard', []);
}
Pour l'instant, nous n'avons pas encore récupéré les articles de l'utilisateur. Pour cela nous allons utiliser un where
sur le modèle Article
.
$articles = Article::where('user_id', $user->id)->get();
Nous allons maintenant passer les articles à la vue.
return view('dashboard', [
'articles' => $articles
]);
Nous allons maintenant modifier la vue dashboard
pour afficher les articles de l'utilisateur. Éditer le fichier resources/views/dashboard.blade.php
et ajouter le code suivant :
<!-- Articles -->
@foreach ($articles as $article)
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg mt-4">
<div class="p-6 text-gray-900">
<h2 class="text-2xl font-bold">{{ $article->title }}</h2>
<p class="text-gray-700">{{ substr($article->content, 0, 30) }}...</p>
</div>
</div>
@endforeach
Un peu de détail sur le code :
- Nous utilisons une boucle
foreach
pour parcourir les articles. - Nous affichons le titre de l'article avec la variable
$article->title
. - Nous affichons le contenu de l'article avec la variable
$article->content
. Nous utilisons la fonctionsubstr
pour ne récupérer que les 30 premiers caractères.
Je vous laisse tester votre code. Vous devriez avoir quelque chose comme ça :
Je vous laisse également valider que vous pouvez créer plusieurs articles et que la liste s'adapte.
tester régulièrement et dans plusieurs situations
Il est important de tester régulièrement votre code. Vous devez tester dans plusieurs situations. Vous devez tester avec un article, avec deux articles, avec un article en brouillon, etc.
Vous pouvez également tester avec plusieurs utilisateurs. Vous pouvez créer un utilisateur avec un compte différent pour tester.
Édition d'un article
Nous allons maintenant réaliser l'édition d'un article. Pour cela, nous allons créer deux routes qui vont nous permettre d'afficher le formulaire d'édition d'un article.
Route::get('/articles/{article}/edit', [UserController::class, 'edit'])->name('articles.edit');
Route::post('/articles/{article}/update', [UserController::class, 'update'])->name('articles.update');
Attention
N'oubliez pas la sécurité ! Seul un utilisateur connecté doit pouvoir accéder à ces routes. À vous de placer la route au bon endroit dans le fichier web.php
.
Pour la suite, nous allons réaliser les étapes suivantes :
- Créer la méthode
edit
dans le contrôleurUserController
. - Créer la vue
articles.edit
qui va nous permettre d'afficher le formulaire d'édition d'un article. - Créer la méthode
update
dans le contrôleurUserController
. - Modifier la vue
dashboard
pour ajouter un lien d'édition sur chaque article.
Voici le code nécessaire :
Des méthodes :
public function edit(Article $article)
{
// On vérifie que l'utilisateur est bien le créateur de l'article
if ($article->user_id !== Auth::user()->id) {
abort(403);
}
// On retourne la vue avec l'article
return view('articles.edit', [
'article' => $article
]);
}
public function update(Request $request, Article $article)
{
// On vérifie que l'utilisateur est bien le créateur de l'article
if ($article->user_id !== Auth::user()->id) {
abort(403);
}
// On récupère les données du formulaire
$data = $request->only(['title', 'content', 'draft']);
// Gestion du draft
$data['draft'] = isset($data['draft']) ? 1 : 0;
// On met à jour l'article
$article->update($data);
// On redirige l'utilisateur vers la liste des articles (avec un flash)
return redirect()->route('dashboard')->with('success', 'Article mis à jour !');
}
Une page :
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Modifier l'article {{ $article->id }}
</h2>
</x-slot>
<form method="post" action="{{ route('articles.update', $article->id) }}" class="py-12">
@csrf
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<!-- Input de titret de l'article -->
<input type="text" value="{{ $article->title }}" name="title" id="title" placeholder="Titre de l'article" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
</div>
<div class="p-6 pt-0 text-gray-900">
<!-- Contenu de l'article -->
<textarea rows="30" name="content" id="content" placeholder="Contenu de l'article" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">{{ $article->content }}</textarea>
</div>
<div class="p-6 text-gray-900 flex items-center">
<!-- Action sur le formulaire -->
<div class="grow">
<input type="checkbox" name="draft" id="draft" {{ $article->draft ? 'checked' : '' }} class="rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<label for="draft">Article en brouillon</label>
</div>
<div>
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Modifier l'article
</button>
</div>
</div>
</div>
</div>
</form>
</x-app-layout>
Lien permettant l'édition :
<div class="text-right">
<a href="{{ route('articles.edit', $article->id) }}" class="text-red-500 hover:text-red-700">Modifier</a>
</div>
div pour le message flash (message qui s'affiche une seule fois) :
<!-- Message flash -->
@if (session('success'))
<div class="bg-green-500 text-white p-4 rounded-lg mt-6 mb-6 text-center">
{{ session('success') }}
</div>
@endif
Avant que vous ne placiez le code dans votre projet. Observons le code ensemble.
- Nous utilisons la classe
Article
dans les méthodesedit
etupdate
de notre contrôleurUserController
. Nous utilisons le typeArticle
dans les paramètres des méthodes. Laravel va automatiquement récupérer l'article en fonction de l'id passé dans l'URL. - Nous utilisons la méthode
update
du modèleArticle
pour mettre à jour l'article dans la base de données. - Nous utilisons la méthode
with
de la classeRedirectResponse
pour ajouter un message flash à la redirection. Nous utilisons la méthodesession
pour récupérer la session de l'utilisateur. Nous utilisons la méthodesuccess
pour ajouter un message flash de typesuccess
. Leflash
est un message qui ne s'affiche qu'une seule fois. Il est très pratique pour afficher des messages de succès ou d'erreur après une action. - Nous utilisons la méthode
route
pour générer une URL. Nous utilisons la méthodearticles.edit
pour générer l'URL de la routearticles.edit
. Nous passons l'id de l'article en paramètre de la méthoderoute
. - Nous avons fait attention à bien sécuriser les routes. Nous avons vérifié que l'utilisateur connecté est bien le créateur de l'article. Si ce n'est pas le cas, nous retournons une erreur 403. (Accès interdit).
C'est à vous de jouer ! Je vous laisse implémenter le code dans votre projet. Une fois réalisé, vous devriez avoir quelque chose comme ça :
Amélioration de l'interface
Nous l'avons vu lors de l'édition d'un article, il est possible d'ajouter un message flash. Je vous laisse faire la même chose pour la création d'un article.
Amélioration de l'interface 2
Dans le même esprit que l'amélioration précédente, nous allons ajouter un message flash lorsqu’un utilisateur tente d'accéder à un article qui ne lui appartient pas.
Le message flash doit être de type error
et doit contenir le message suivant : « Vous n'avez pas le droit d'accéder à cet article », il doit s'afficher sur un fond rouge.
un peu d'aide ?
@if (session('error'))
<div class="bg-red-500 text-white p-4 rounded-lg mt-6 mb-6 text-center">
{{ session('error') }}
</div>
@endif
Suppression d'un article
Je vous laisse réaliser la partie suppression d'un article. Voici les étapes à réaliser :
- Créer la route
articles.remove
qui va nous permettre de supprimer un article. - Créer la méthode
remove
dans le contrôleurUserController
. - Écrire le code de la méthode
remove
pour supprimer l'article. - Après la suppression de l'article, rediriger l'utilisateur vers la liste des articles avec un message flash.
- Ajouter un lien de suppression sur chaque article dans la vue
dashboard
.
Rappel
Pour supprimer un article, vous pouvez utiliser la méthode delete
du modèle Article
.
$article = Article::find($id);
$article->delete();
Si vous avez un Article
en paramètre de votre méthode, vous pouvez utiliser la méthode delete
directement sur l'objet.
$article->delete();
⚠️ N'oubliez pas de vérifier que l'utilisateur connecté est bien le créateur de l'article. Vous pouvez utiliser la même méthode que pour l'édition d'un article.
Point étape
Vous avez la base de la partie utilisateur. Vous pouvez maintenant créer des articles, les modifier ainsi que les supprimer. Je vous propose de faire un nouveau commit de votre projet.
Partie publique
Nous allons maintenant nous attaquer à la partie publique de notre site Internet. Pour rappel, cette partie va nous permettre de consulter les articles publiés pour chaque utilisateur.
Le contrôleur public
Pour commencer, nous allons créer un contrôleur PublicController
qui va nous permettre de gérer la partie publique de notre site Internet.
php artisan make:controller PublicController
Liste des articles publiés d'un utilisateur
Nous allons maintenant créer une route qui va nous permettre d'afficher la liste des articles publiés d'un utilisateur.
Route::get('/{user}', [PublicController::class, 'index'])->name('public.index');
Route::get('/{user}/{article}', [PublicController::class, 'show'])->name('public.show');
Cette route
Cette route doit être la dernière route de votre fichier web.php
. En effet, elle doit être la dernière route de votre fichier pour ne pas interférer avec les autres routes.
Elle doit permettre d'afficher la liste des articles publiés d'un utilisateur.
Nous allons maintenant créer la méthode index
dans notre contrôleur PublicController
.
public function index(User $user)
{
// On récupère les articles publiés de l'utilisateur
$articles = Article::where('user_id', $user->id)->where('draft', 0)->get();
// On retourne la vue
return view('public.index', [
'articles' => $articles,
'user' => $user
]);
}
Un peu de détail sur le code :
- Nous utilisons la classe
User
dans les paramètres de la méthodeindex
. Laravel va automatiquement récupérer l'utilisateur en fonction de l'id passé dans l'URL. - Nous utilisons la méthode
where
du modèleArticle
pour récupérer les articles publiés de l'utilisateur. - Nous passons les articles et l'utilisateur à la vue.
Nous allons maintenant créer la vue public.index
qui va nous permettre d'afficher la liste des articles publiés d'un utilisateur. Créer le fichier resources/views/public/index.blade.php
et ajouter le code suivant :
<x-guest-layout>
<div class="text-center">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Liste des articles publiés de {{ $user->name }}
</h2>
</div>
<div>
<!-- Articles -->
@foreach ($articles as $article)
<div>
<div class="p-6 text-gray-900 dark:text-gray-100">
<h2 class="text-2xl font-bold">{{ $article->title }}</h2>
<p class="text-gray-700 dark:text-gray-300">{{ substr($article->content, 0, 30) }}...</p>
<a href="{{ route('public.show', [$article->user_id, $article->id]) }}" class="text-red-500 hover:text-red-700">Lire la suite</a>
</div>
</div>
<hr>
@endforeach
</div>
</x-guest-layout>
Un peu de détail sur le code :
- Nous utilisons le composant
x-guest-layout
pour avoir une page vide. - Nous utilisons la classe
User
dans la vue pour récupérer le nom de l'utilisateur. - Nous utilisons la classe
Article
dans la vue pour récupérer le titre et le contenu de l'article. - Nous affichons une liste d'articles. Celle-ci est construite avec une boucle
foreach
.
Je vous laisse tester votre code. Vous devriez avoir quelque chose comme ça :
Consultation d'un article publié
Dans votre routeur vous avez déjà créé la route public.show
qui va nous permettre d'afficher un article. Nous allons maintenant créer la méthode show
dans notre contrôleur PublicController
.
public function show(User $user, Article $article)
{
// $user est l'utilisateur de l'article
// $article est l'article à afficher
// Je vous laisse faire le code
// N'oubliez pas de vérifier que l'article est publié (draft == 0)
}
<x-guest-layout>
<div class="text-center">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ $article->title }}
</h2>
</div>
<div class="text-gray-500 text-sm">
Publié le {{ $article->created_at->format('d/m/Y') }} par <a href="{{ route('public.index', $article->user->id) }}">{{ $article->user->name }}</a>
</div>
<div>
<div class="p-6 text-gray-900 dark:text-gray-100">
<p class="text-gray-700 dark:text-gray-300">{{ $article->content }}</p>
</div>
</div>
</x-guest-layout>
Un peu de détail sur le code :
- Nous utilisons la classe
User
dans les paramètres de la méthodeshow
. Laravel va automatiquement récupérer l'utilisateur en fonction de l'id passé dans l'URL. - Nous utilisons la classe
Article
dans les paramètres de la méthodeshow
. Laravel va automatiquement récupérer l'article en fonction de l'id passé dans l'URL.
Je vous laisse écrire le code de la méthode show
et de la vue public.show
. Une fois réalisé, vous devriez avoir quelque chose comme ça :
Amélioration de l'interface
Modifier le composant x-guest-layout
pour que celui ne soit plus centré. Profitez-en également pour le modifier afin d'avoir un header et un footer.
Le composant se trouve dans le fichier resources/views/layouts/guest.blade.php
.
Vous pouvez également retirer le logo de la page (ou mettre le vôtre).
Pour vous inspirer, vous pouvez aller voir le template Tailwind Awesome qui propose des templates gratuits.
Rappel
N'oubliez pas d'inclure le {{ $slot }}
dans votre template. Sinon, vous n'aurez pas le contenu de vos pages.
Rappel, le {{ $slot }}
correspond au contenu de votre composant.
Dans mon cas, j'ai ajusté quelques éléments pour avoir quelque chose comme ça :
Point étape
Vous avez maintenant la base de la partie publique de votre site Internet. Vous pouvez maintenant consulter les articles publiés de chaque utilisateur.
Je vous laisse faire un nouveau commit de votre projet.
Les commentaires
Les commentaires sont une fonctionnalité importante d'un blog. Nous allons donc les ajouter à notre projet.
Chaque article doit avoir une liste de commentaires. Chaque utilisateur doit pouvoir ajouter un commentaire sur un article. L'ajout de commentaire ne doit être possible que pour les utilisateurs connectés.
Comment allons-nous procéder ?
Pour traiter cet ajout de fonctionnalité, nous allons procéder de la manière suivante :
- Création d'un contrôleur
CommentController
qui va nous permettre de gérer les commentaires. - Création d'une route
comments.store
qui va nous permettre d'ajouter un commentaire. - Modification de la vue
public.show
pour ajouter :- Une liste des commentaires.
- Un formulaire pour ajouter un commentaire. (Si l'utilisateur est connecté).
- Nous avons à notre disposition un modèle
Comment
qui va nous permettre de stocker les commentaires. - Nous avons une relation entre les modèles
Article
etComment
qui va nous permettre de récupérer les commentaires d'un article.
Création du contrôleur
Nous l'avons vu précédemment, nous allons créer un contrôleur CommentController
qui va nous permettre de gérer les commentaires.
Je vous laisse retrouver la commande pour créer un contrôleur (vous l'avez déjà utilisée plusieurs fois, celle-ci est également disponible dans l'aide mémoire).
Ajout de la route
Maintenant que nous avons notre contrôleur, nous allons créer la route comments.store
qui va nous permettre d'ajouter un commentaire.
un peu d'aide ?
Vous ne devriez pas avoir de difficulté à créer la route. Vous pouvez vous inspirer des routes déjà présentes dans le fichier routes/web.php
. Mais voici un petit rappel.
Route::post('/comments/store', [CommentController::class, 'store'])->name('comments.store');
Ajouter la méthode store
Pour rappel, la méthode store
va nous permettre d'ajouter un commentaire en base de données. Je vous laisse écrire la méthode store
dans le contrôleur CommentController
. Voici les étapes à réaliser :
- Récupérer les données du formulaire (commentaire, articleId).
- Créer le commentaire dans la base de données.
- Rediriger l'utilisateur vers la page de l'article qu'il vient de commenter.
C'est à vous de jouer ! Je vous laisse implémenter le code dans votre projet.
Rappel
Pour créer un commentaire dans la base de données, vous pouvez utiliser la méthode create
du modèle Comment
.
Comment::create([
'content' => $content,
'article_id' => $articleId,
'user_id' => Auth::user()->id
]);
Attention
N'oubliez pas de vérifier que la personne est connectée avant d'ajouter un commentaire. Vous pouvez utiliser la classe Auth
de Laravel pour vérifier si un utilisateur est connecté.
if (Auth::check()) {
// L'utilisateur est connecté
}
Si l'utilisateur n'est pas connecté, vous devez rediriger l'utilisateur vers la page de connexion (return redirect()->route('login');
).
L'astuce du chef
En Laravel, il existe une méthode redirect qui permet de rediriger l'utilisateur vers la page précédente.
return redirect()->back();
Ajouter le formulaire
Nous allons maintenant ajouter le formulaire qui va nous permettre d'ajouter un commentaire. Pour cela, nous allons modifier la vue public.show
. Je ne vais pas vous donner le code en entier du formulaire, je vais vous donner les grandes lignes.
<!-- Ajout d'un commentaire -->
<form action="{{ route('comments.store') }}" method="post" class="mt-6">
@csrf
<input type="hidden" name="articleId" value="{{ $article->id }}">
<!-- Ajouter le reste de votre formulaire -->
</form>
Un peu de détail sur le code :
- Nous utilisons la méthode
route
pour générer une URL. Nous utilisons la méthodecomments.store
pour générer l'URL de la routecomments.store
. - Nous utilisons la méthode
csrf
pour générer un token CSRF pour sécuriser notre formulaire. - Nous utilisons un champ
hidden
pour stocker l'id de l'article (pour lequel nous ajoutons un commentaire).
Attention
N'oubliez pas qu'il ne doit être possible d'ajouter un commentaire que pour les utilisateurs connectés. Vous devez donc ajouter une condition pour afficher le formulaire uniquement si l'utilisateur est connecté. En Laravel vous pouvez utiliser la classe Auth
pour vérifier si un utilisateur est connecté.
@auth
<!-- Le code affiché si la personne est connecté -->
@endauth
Ajouter la liste des commentaires
Maintenant que nous avons la possibilité d'ajouter un commentaire, nous allons ajouter la liste des commentaires. Pour cela, nous allons modifier la vue public.show
. Je ne vais pas vous donner le code en entier de la liste des commentaires, je vais vous donner les grandes lignes.
<!-- Liste des commentaires -->
@foreach ($article->comments as $comment)
<!-- $comment représente un commentaire -->
@endforeach
Le résultat
Vous avez maintenant la possibilité d'ajouter des commentaires sur vos articles. Dans mon cas, voici le résultat :
Point étape
Vous avez maintenant la base de votre site Internet. Vous pouvez maintenant créer des articles, les modifier, les supprimer et les consulter. Vous pouvez également consulter les articles publiés de chaque utilisateur.
Je vous laisse faire un nouveau commit de votre projet.
Les catégories
Chaque article peut avoir une ou plusieurs catégories (technologie, sport, etc.). Actuellement, nous n'avons pas de catégorie. Je vous propose donc d'ajouter cette fonctionnalité à notre site Internet.
La liste des catégories sera fixe, c'est-à-dire que nous allons les mettre en dur dans notre base de données. Nous n'allons pas créer une interface pour les gérer.
Vu que nous avons généré nos modèles à partir de la base de données, nos relations en code sont déjà présentes. Nous allons avoir uniquement les modifications de code et d'interface à faire.
Remplissage de la base de données
Pour commencer, nous allons remplir notre base de données avec les catégories. Pour cela, je vous laisse insérer les catégories suivantes dans la table categories
:
- Technologie
- Sport
- Jeux vidéo
- Cinéma
- Musique
- IA
- Développement
- Informatique
Comment allons-nous procéder ?
Pour traiter cet ajout de fonctionnalité, nous allons procéder de la manière suivante :
Partie publique :
- Modifier la page d'affichage de la liste des articles pour afficher les catégories.
- Modifier la page d'affichage d'un article pour afficher les catégories.
Comment tester ?
Avant de faire la partie Utilisateur, vous pourrez tester votre code en insérant manuellement des catégories dans la base de données pour les articles que vous avez déjà créés.
Partie utilisateur :
- Modifier la page de création d'un article pour permettre de choisir une ou des catégories.
- Modifier le code qui sauvegarde l'article pour sauvegarder les catégories.
- Modifier la page d'édition d'un article pour permettre de choisir une ou des catégories.
- Modifier le code qui sauvegarde l'article pour sauvegarder les catégories.
C'est à vous de jouer !
Ici je ne vais pas vous guider pas à pas. Je vais vous laisser réaliser le code par vous-même. L'important dans une fonctionnalité comme celle-ci, c'est de se projeter dans le résultat final.
Je vous donne juste une petite aide :
Si vous souhaitez avoir la liste des catégories depuis un article, vous pouvez utiliser la propriété categories
de l'article.
@foreach ($article->categories as $category)
{{ $category->name }}
@endforeach
Si vous souhaitez avoir l'ensemble des catégories, vous pouvez utiliser la méthode all
du modèle Category
.
$categories = Category::all();
Si vous souhaitez sauvegarder les catégories d'un article, vous pouvez utiliser la méthode sync
du modèle Article
.
// $article est l'article sauvé en base de données (resultat de la méthode create ou d'un update)
// Exemple pour ajouter des catégories à l'article en venant du formulaire
$article->categories()->sync($request->input('categories'));
Le résultat
Amélioration de l'interface
Nous avons un projet complètement fonctionnel. Je vous propose d'ajouter quelques éléments pour simplifier la navigation :
Ajout d'un lien « voir mon blog » dans le menu de l'utilisateur. Ce lien doit rediriger vers la liste des articles publiés de l'utilisateur connecté.
un peu d'aide ?
<x-nav-link :href="route('public.index', Auth::id())" :active="request()->routeIs('article.index')">
Voir mon blog
</x-nav-link>
Amélioration de l'interface 2
Pour l'instant il est probable que votre page « publique » ne possède pas de menu de navigation. Je vous propose de l'ajouter dans le fichier guest.blade.php
:
@auth
@include('layouts.navigation')
@endauth
@guest
@if (Route::has('login'))
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
<a href="{{ route('login') }}" class="text-sm text-gray-700 underline">Login</a>
</div>
@endif
@endguest
Voilà le résultat chez moi :
Point étape
Votre projet avance bien !
Je vous laisse faire un nouveau commit de votre projet.
Les likes
Vous arrivez à la fin de votre projet. Il ne vous reste plus qu'une fonctionnalité à réaliser. Les likes.
Sur chaque article, vous devez ajouter un bouton qui va permettre de liker l'article. Lorsque l'utilisateur clique sur le bouton, vous devez ajouter un like à l'article. Ce compteur doit être à l'infini. C'est-à-dire que l'utilisateur peut liker plusieurs fois le même article.
Vous devez également afficher le nombre de like sur chaque article.
Rappel
Pour réaliser cette fonctionnalité, vous devrez ajouter une nouvelle colonne dans la table articles
. Cette colonne de type intégré va nous permettre de stocker le nombre de like de l'article.
Une fois la colonne ajoutée, vous devrez modifier le modèle Article
pour ajouter la colonne dans la propriété $fillable
et $casts
.
Pour vous aider dans votre réalisation, voici les étapes à réaliser :
- Ajouter une colonne
likes
dans la tablearticles
. - Modifier le modèle
Article
pour ajouter la colonne dans la propriété$fillable
et$casts
. - Modifier la vue
public.show
pour ajouter un bouton « like » et le nombre de like. - Ajouter une route
articles.like
qui va nous permettre de liker un article. - Ajouter la méthode
like
dans le contrôleurArticleController
.
Voici un peu de HTML pour vous aider :
@auth
<a href="{{ route('article.like', $article->id) }}" class="block bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9.719,17.073l-6.562-6.51c-0.27-0.268-0.504-0.567-0.696-0.888C1.385,7.89,1.67,5.613,3.155,4.14c0.864-0.856,2.012-1.329,3.233-1.329c1.924,0,3.115,1.12,3.612,1.752c0.499-0.634,1.689-1.752,3.612-1.752c1.221,0,2.369,0.472,3.233,1.329c1.484,1.473,1.771,3.75,0.693,5.537c-0.19,0.32-0.425,0.618-0.695,0.887l-6.562,6.51C10.125,17.229,9.875,17.229,9.719,17.073 M6.388,3.61C5.379,3.61,4.431,4,3.717,4.707C2.495,5.92,2.259,7.794,3.145,9.265c0.158,0.265,0.351,0.51,0.574,0.731L10,16.228l6.281-6.232c0.224-0.221,0.416-0.466,0.573-0.729c0.887-1.472,0.651-3.346-0.571-4.56C15.57,4,14.621,3.61,13.612,3.61c-1.43,0-2.639,0.786-3.268,1.863c-0.154,0.264-0.536,0.264-0.69,0C9.029,4.397,7.82,3.61,6.388,3.61" clip-rule="evenodd" />
</svg>
<span>{{$article->like}}</span>
</a>
@endauth
- Nous avons un lien qui pointe vers la route
articles.like
. - Nous avons un bouton avec un SVG pour afficher un cœur.
- Nous avons un span qui affiche le nombre de like.
Voici le rendu :
La page d'accueil
Maintenant que vous avez toutes les fonctionnalités de votre site Internet, vous allez pouvoir créer une page d'accueil. Cette page va afficher les articles le plus liké de la plateforme.
Rappel
Pour réaliser cette fonctionnalité, vous allez devoir modifier :
- Le contrôleur
PublicController
pour ajouter la méthodehome
. - Le fichier
routes/web.php
pour ajouter la route/
. (Vous allez devoir remplacer déjà présente par la votre).
Déploiement
Maintenant que votre projet est terminé, vous allez pouvoir le déployer sur un serveur. Pour cela, je vous invite à suivre l'un des deux TP suivants :
Deux solutions sont possibles
Pour déployer votre projet, vous avez deux solutions :
- Installation classique de Laravel, via un serveur web (Apache).
- Installation de Laravel via Docker <- Je vous conseille cette solution, c'est simple et rapide (il faut juste ajouter deux fichiers dans votre projet)
Pour moi, la solution Docker est celle à privilégier. Même si vous ne connaissez pas Docker, vous pouvez suivre le TP. Il est très simple et vous permettra de déployer votre projet rapidement.
Avoir docker sur un serveur va vous permettre de déployer votre architecture très rapidement, sans n'avoir rien à installer (autre que Docker) sur votre serveur. Pratique.
PS: Déployer le site aveck Docker prend 2min et 20seconde (en comptant le temps de téléchargement de l'image Docker). C'est très rapide.
PS: N'oubliez pas de faire un commit de votre projet avant de le déployer. Pour finaliser votre projet, vous devez le déployer sur un serveur. Vous devez me partager l'URL de votre projet déployé.
Bonus
Vous avez terminé votre projet ? Vous avez encore du temps ? Voici quelques idées de fonctionnalités supplémentaires.
- Permettre l'ajout de tags sur les articles.
- Ajouter un système de pagination sur la liste des articles.
- Ajouter un système de recherche sur la liste des articles.
- Voir les articles en fonction d'une catégorie.
- Voir les articles en fonction d'un tag.
Conclusion
Vous avez terminé votre projet. Vous avez maintenant un blog fonctionnel. Vous pouvez maintenant le personnaliser à votre guise.
Je vous laisse faire un dernier commit de votre projet. Puis vous pouvez me le partager sur Github / Gitlab (valentin brosseau
ou c4software
).
Restitution
Vous avez atteint la fin du TP (avec ou sans bonus). Il est temps de rendre votre travail. Pour cela, vous remplissez le formulaire suivant :