
Créer un blog – modifier ou supprimer un article
Nous avons dans le précédent article codé la création d’un article. On en a profité pour ajouter des composants qu’on réutilisera pour les autres entités. A présent nous allons voir comment modifier un article, ce qui sera grandement facilité par notre précédent travail puisque le formulaire est pratiquement le même que celui de la création, surtout que nous nous sommes arrangés pour préparer le terrain, de même pour la partie validation. Nous verrons ensuite la suppression d’un article, là il nous faudra un peu de Javascript parce qu’on ne va pas procéder à une suppression immédiate mais plutôt demander la confirmation au rédacteur pour éviter un regrettable accident.
On doit aussi se poser des questions concernant la sécurité. Il ne faut pas qu’un rédacteur puisse modifier ou supprimer l’article d’un autre rédacteur.
Vous pouvez télécharger le code final de cet article ici.
Modifier un article
Dans le tableau des articles on dispose d’un bouton pour la modification d’un article :
Chacun de ces boutons comporte dans l’attribut href l’url correspondant à l’article concerné.
Le repository
Dans le repository PostRepository on crée une méthode update :
public function update($post, $request) { $request->merge([ 'active' => $request->has('active'), 'image' => basename($request->image), ]); $post->update($request->all()); $this->saveCategoriesAndTags($post, $request); }
On se rend compte que le code est très proche de celui que nous avons écrit pour la méthode store. On bénéficie de la méthode saveCategoriesAndTags que nous avons aussi créée précédemment.
Le contrôleur
Ce sont les méthodes edit et update du contrôleur Back/PostController qui sont concernées pour cette modification.
edit
On retrouve là de code déjà utilisé dans la méthode create, d’autant qu’on utilise la même vue :
public function edit(Post $post) { $categories = Category::all()->pluck('title', 'id'); return view('back.posts.form', compact('post', 'categories')); }
On pourrait d’ailleurs refactoriser un peu le code mais on ne gagnerait pas forcément en clarté.
update
La méthode update est elle aussi très légère grâce au repository :
public function update(PostRequest $request, PostRepository $repository, Post $post) { $repository->update($post, $request); return back()->with('ok', __('The post has been successfully updated')); }
La vue
On a déjà prévu le cas de la modification dans la vue (back.posts.form) :
<form method="post" action="{{ Route::currentRouteName() === 'posts.edit' ? route('posts.update', $post->id) : route('posts.store') }}"> @if(Route::currentRouteName() === 'posts.edit') @method('PUT') @endif
Comme on a déjà créé les routes, les titres et les données du menu tout devrait fonctionner :
On n’a pas d’item spécifique dans le menu alors on se contente de mettre en valeur Articles.
Si la modification se passe bien on a une alerte :
Supprimer un article
Dans le tableau des articles on dispose d’un bouton pour la suppression d’un article :
Chacun de ces boutons comporte dans l’attribut href l’url correspondant à l’article concerné.
Le contrôleur
Dans le contrôleur c’est la méthode destroy qui est concernée :
public function destroy(Post $post) { $post->delete(); return response()->json(); }
Comme on va faire cette suppression en Ajax on renvoie une réponse JSON.
Le Javascript
On doit ajouter du Javascript dans la vue back/shared/index.blade.php pour :
- réagir au clic sur un bouton de suppression
- demander confirmation de suppression
- envoyer la requête à Laravel
- supprimer la ligne du tableau en cas de retour positif de Laravel
On place le nouveau code à la fin de celui qu’on a déjà prévu :
{{ $dataTable->scripts() }} <script> (() => { // Variables const headers = { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Content-Type': 'application/json', 'Accept': 'application/json' } // Delete const deleteElement = async e => { e.preventDefault(); Swal.fire({ title: e.target.dataset.name, icon: 'warning', showCancelButton: true, confirmButtonColor: '#DD6B55', confirmButtonText: '@lang('Yes')', cancelButtonText: '@lang('No')', preConfirm: () => { return fetch(e.target.getAttribute('href'), { method: 'DELETE', headers: headers }) .then(response => { if (response.ok) { e.target.parentNode.parentNode.remove(); } else { Swal.fire({ icon: 'error', title: '@lang('Whoops!')', text: '@lang('Something went wrong!')' }); } }); } }); } // Listener wrapper const wrapper = (selector, type, callback, condition = 'true', capture = false) => { const element = document.querySelector(selector); if(element) { document.querySelector(selector).addEventListener(type, e => { if(eval(condition)) { callback(e); } }, capture); } }; // Set listeners window.addEventListener('DOMContentLoaded', () => { wrapper('table', 'click', deleteElement, "e.target.matches('.btn-danger')"); }); })() </script> @endsection
J’ai remis la fonction universelle wrapper, que j’ai déjà utilisé pour le frontend, pour la mise en place de l’écoute, de même que la constante pour les headers. On se passe de JQuery pour avoir du code plus propre.
Comme on a potentiellement (et sûrement) plusieurs boutons de suppression dans le tableau on écoute le click au niveau de ce tableau. On regarde ensuite si l’élément déclenchant possède la classe .btn-danger.
Si c’est le cas on utilise SweetAlert pour afficher un message :
Si on répond « Oui » alors on utilise fetch pour envoyer la requête. AU niveau de chaque bouton l’attribut href comporte l’url correcte qu’on récupère :
fetch(e.target.getAttribute('href'), {
Cette façon de procéder rend le code utilisable pour toutes les entités que nous aurons à traiter et pas seulement les articles.
Si la réponse est OK on supprime la ligne du tableau :
.then(response => { if (response.ok) { e.target.parentNode.parentNode.remove();
Sinon on affiche une erreur :
La sécurité
Comme je le disais dans l’introduction il ne faudrait pas qu’un rédacteur modifie ou supprime l’article d’un autre rédacteur. Bien que ce soit peu probable on doit prendre des dispositions pour l’éviter.
On crée une classe d’autorisation pour les articles :
php artisan make:policy PostPolicy --model=Post
Le fait d’utiliser l’option –model prépare les méthodes de la classe pour les actions viewAny, view, create, update, delete, restore et forceDelete. On n’a évidemment pas besoin de tout ça. Voilà le code adapté à nos besoins :
<?php namespace App\Policies; use App\Models\{ Post, User }; use Illuminate\Auth\Access\HandlesAuthorization; class PostPolicy { use HandlesAuthorization; protected function manage(User $user, Post $post) { return $user->isAdmin() ?: $user->id === $post->user_id; } public function viewAny(User $user) { return true; } public function view(User $user, Post $post) { return $this->manage($user, $post); } public function create(User $user) { return true; } public function update(User $user, Post $post) { return $this->manage($user, $post); } public function delete(User $user, Post $post) { return $this->manage($user, $post); } }
L’administrateur peut tout faire, mais pour les rédacteurs on les limite à leurs articles.
Il ne reste plus qu’à déclarer ça dans le contrôleur :
class PostController extends Controller { public function __construct() { $this->authorizeResource(Post::class, 'post'); }
Maintenant pour une action non autorisée on a cette page :
Conclusion
Nous en avons terminé avec la gestion des articles. Mais il nous reste encore plein de choses à voir dans l’administration : les commentaires, les contacts, les utilisateurs, les pages, les liens sociaux.


15 commentaires
DIM
salut best , j’ai un souci je veux mettre à jour les informations sur ma table neophyte
voici le controlleur qui étend du ResourceController comme dans ce projet :
j’ai juste saturé la fonction update :
namespace App\Http\Controllers;
use App\Models\Neophyte;
use Illuminate\Http\Request;
use App\Http\Requests\NeophyteRequest;
use App\Http\Controllers\ResourceController;
class NeophyteController extends ResourceController
{
/**
* Display a listing of the resource.
*/
/**
* Show the form for creating a new resource.
*/
/**
* Store a newly created resource in storage.
*/
/**
* Display the specified resource.
*/
/**
* Show the form for editing the specified resource.
*/
/**
* Update the specified resource in storage.
*/
public function update($id)
{
$request = app()->make(NeophyteRequest::class);
Neophyte::findOrFail($id)->update($request->all());
return back()->with(‘ok’, __(‘Informations modifiées avec succès’));
}
/**
* Remove the specified resource from storage.
*/
}
et voici le NeophyteRequest qui me donne l’erreur :
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class NeophyteRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$id = $this->neophyte ? ‘,’ . $this->neophyte->id : »; ( l’erreur se situe sur cette ligne).
return [
‘name’=> ‘required|max:255’,
‘firstname’=>’required|max:255’,
‘phone’=>’required|numeric|min:11′,
’email’ => ‘required|email|max:255|unique:neophytes,email’ .$id,
‘nationality’=>’required|max:255’,
‘status’ =>’required|max:255′,
‘year’ => [‘required’, ‘numeric’, ‘min:0000’, ‘max:’ . date(‘Y’)],
];
}
}
j’ai cette erreur quand je tente de modifier une entrée
Attempt to read property « id » on string
Peux-tu m’éclairer stp
bestmomo
Salut,
Si tu utilises
dd($this->neophyte)
tu obtiens quoi comme type ?DIM
salut best , désolé pour la question qui peut sembler basique pour toi mais je suis toujours l’apprentissage
je dois faire le dd($this->neophyte) à quel niveau ? au niveau de ma NeophyteRequest ?
DIM
salut best , j’obtiens le type null
bestmomo
Je pense qu’il faut plutôt utiliser
$this->neophyte()
DIM
salut best aujourdh’ui quand j’ai refais un dd ça me retourne bien l’id en question.
dans ce cas quel peut etre la solution .
j’ai éssayé ce que tu as suggérer mais ça ne marche pas .
DIM
salut best , vu que $this-> neophyte me donne bien l’id alors j’ai enlevé le ->id et ça marche
lafia
Bonjour Best ! Tout d’abord recevez mes remerciements pour ces services rendu à l’humanité !
Au fait je suis vos tutos et arrivé à ce stade, j’ai remarqué qu’un Admin ne peut voir le button de Dupliquer. Donc, j’ai commenté dans PostDataTable cette ligne : if($post->user_id === auth()->id()) pour pouvoir l’avoir. Mais cliquant le button on recoit tous les champs vides:http: //127.0.0.1:8000/admin/posts/create/1.
Et dans Nouveaux articles, seul le button voir apparait, comment avoir tous ?
merci !
bestmomo
Bonjour,
Je ne pense pas judicieux de faire apparaître ce bouton pour l’administrateur, mais il suffit de faire ça :
if($post->user_id === auth()->id() || auth()->user()->isAdmin()) {
lafia
merci beaucoup
J’ai pu aussi faire apparaitre les buttons dans Nouveaux articles en deplaçant ce code if(Route::currentRouteName() === ‘posts.indexnew’) {
return $buttons;
}
vers la fin de ediColumn action.
bensa
Salut,
En fait le click sur le button delete ne fonctionne pas même en cliquant sur « OUI » je recois toujours le message:
Oooops 🙁
Il y a eu une erreur inattendue !
bestmomo
Salut,
Regarde le contenu de la réponse qui arrive.
braice
Salut,
J’ai eu un problème avec mon projet et j’ai du le recloner, aucun problème avec les différentes commandes sauf l’affichage des images qui ne fonctionne plus (tout marchait nickel avant et écran noir à la place de l’image depuis le gitclone) pour les anciens et nouveauxc articles et je n’ai aucune erreur dans la console. Il ne me semble pas avoir lu ce type de problème.
Merci pour ce tuto en tout cas !
bestmomo
Salut,
Tu as bien recréé le lien symbolique ?
braice
Au temps pour moi, il fallait republier les liens et recréer le lien symbolique.