Mis à jour le lundi 8 janvier 2018
  • 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Le backend

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Notre application est composée d'un système de news avec commentaires. Or, nous ne pouvons actuellement pas ajouter de news, ni modérer les commentaires. Pour ce faire, nous allons créer un espace d'administration, qui n'est autre ici que le backend. Cet espace d'administration sera ainsi composé d'un système de gestion de news (ajout, modification et suppression) ainsi que d'un système de gestion de commentaires (modification et suppression).

Ayant déjà créé le frontend, ce chapitre devrait être plus facile pour vous. Néanmoins, une nouveauté fait son apparition : celle d'interdire le contenu de l'application aux visiteurs. Ne traînons donc pas, nous avons pas mal de travail qui nous attend !

L'application

Comme pour l'application Frontend, nous aurons besoin de créer les fichiers de base : la classe représentant l'application, le layout, les deux fichiers de configuration et le fichier qui instanciera notre classe. Assurez-vous également d'avoir créé le dossier /App/Backend.

La classe BackendApplication

Cette classe ne sera pas strictement identique à la classe FrontendApplication. En effet, nous devons sécuriser l'application afin que seuls les utilisateurs authentifiés y aient accès.

Pour rappel, voici le fonctionnement de la méthode run() de la classe FrontendApplication :

  • Obtention du contrôleur grâce à la méthode parente getController().

  • Exécution du contrôleur.

  • Assignation de la page créée par le contrôleur à la réponse.

  • Envoi de la réponse.

La classe BackendApplication fonctionnera de la même façon, à la différence près que la première instruction ne sera exécutée que si l'utilisateur est authentifié. Sinon, nous allons récupérer le contrôleur du module de connexion que nous allons créer dans ce chapitre. Voici donc le fonctionnement de la méthode run() de la classe BackendApplication :

  • Si l'utilisateur est authentifié :

    • obtention du contrôleur grâce à la méthode parente getController().

  • Sinon :

    • instanciation du contrôleur du module de connexion.

  • Exécution du contrôleur.

  • Assignation de la page créée par le contrôleur à la réponse.

  • Envoi de la réponse.

Vous devriez avoir une classe de ce type :

<?php
namespace App\Backend;

use \OCFram\Application;

class BackendApplication extends Application
{
  public function __construct()
  {
    parent::__construct();

    $this->name = 'Backend';
  }

  public function run()
  {
    if ($this->user->isAuthenticated())
    {
      $controller = $this->getController();
    }
    else
    {
      $controller = new Modules\Connexion\ConnexionController($this, 'Connexion', 'index');
    }

    $controller->execute();

    $this->httpResponse->setPage($controller->page());
    $this->httpResponse->send();
  }
}

Le layout

Le layout est le même que celui du frontend. Sachez qu'en pratique, cela est rare et vous aurez généralement deux layouts différents (chaque application a ses spécificités). Cependant, ici il n'est pas nécessaire de faire deux fichiers différents. Vous pouvez donc soit copier/coller le layout du frontend dans le dossier /App/Backend/Templates, soit créer le layout et inclure celui du frontend :

<?php require __DIR__.'/../../Frontend/Templates/layout.php';

Les deux fichiers de configuration

Là aussi il faut créer les deux fichiers de configuration. Mettez-y, comme nous l'avons fait au précédent chapitre, les structures de base.

<?xml version="1.0" encoding="utf-8" ?>
<definitions>
</definitions>
<?xml version="1.0" encoding="utf-8" ?>
<routes>
</routes>

Réécrire les URL

Nous allons maintenant modifier le fichier .htaccess. Actuellement, toutes les URL sont redirigées vers bootstrap.php?app=Frontend. Nous garderons toujours cette règle, mais nous allons d'abord en ajouter une autre : nous allons rediriger toutes les URL commençant par admin/ vers bootstrap.php?app=Backend. Vous êtes libres de choisir un autre préfixe, mais il faut bien choisir quelque chose pour sélectionner l'application concernée par l'URL.

Le .htaccess ressemble donc à ceci :

RewriteEngine On

RewriteRule ^admin/ bootstrap.php?app=Backend [QSA,L]

RewriteCond %{REQUEST_FILENAME} !-f

RewriteRule ^(.*)$ bootstrap.php?app=Frontend [QSA,L]

Le module de connexion

Ce module est un peu particulier. En effet, aucune route ne sera définie pour pointer vers ce module. De plus, ce module ne nécessite aucun stockage de données, nous n'aurons donc pas de modèle. La seule fonctionnalité attendue du module est d'afficher son index. Cet index aura pour rôle d'afficher le formulaire de connexion et de traiter les données de ce formulaire.

Avant de commencer à construire le module, nous allons préparer le terrain. Où stocker l'identifiant et le mot de passe permettant d'accéder à l'application ? Je vous propose tout simplement d'ajouter deux définitions dans le fichier de configuration de l'application :

<?xml version="1.0" encoding="utf-8" ?>
<definitions>
  <define var="login" value="admin" ></define>
  <define var="pass" value="mdp" ></define>
</definitions>

Vous pouvez maintenant, si ce n'est pas déjà fait, créer le dossier /App/Backend/Modules/Connexion.

La vue

Nous allons débuter par créer la vue correspondant à l'index du module. Ce sera un simple formulaire demandant le nom d'utilisateur et le mot de passe à l'internaute :

<h2>Connexion</h2>

<form action="" method="post">
  <label>Pseudo</label>
  <input type="text" name="login" /><br />
  
  <label>Mot de passe</label>
  <input type="password" name="password" /><br /><br />
  
  <input type="submit" value="Connexion" />
</form>

Le contrôleur

Procédons maintenant à l'élaboration du contrôleur. Ce contrôleur implémentera une seule méthode : executeIndex(). Cette méthode devra, si le formulaire a été envoyé, vérifier si le pseudo et le mot de passe entrés sont corrects. Si c'est le cas, l'utilisateur est authentifié, sinon un message d'erreur s'affiche.

<?php
namespace App\Backend\Modules\Connexion;

use \OCFram\BackController;
use \OCFram\HTTPRequest;

class ConnexionController extends BackController
{
  public function executeIndex(HTTPRequest $request)
  {
    $this->page->addVar('title', 'Connexion');
    
    if ($request->postExists('login'))
    {
      $login = $request->postData('login');
      $password = $request->postData('password');
      
      if ($login == $this->app->config()->get('login') && $password == $this->app->config()->get('pass'))
      {
        $this->app->user()->setAuthenticated(true);
        $this->app->httpResponse()->redirect('.');
      }
      else
      {
        $this->app->user()->setFlash('Le pseudo ou le mot de passe est incorrect.');
      }
    }
  }
}

Ce fut court, mais essentiel. Nous venons de sécuriser en un rien de temps l'application toute entière. De plus, ce module est réutilisable dans d'autres projets ! En effet, rien ne le lie à cette application. À partir du moment où l'application aura un fichier de configuration adapté (c'est-à-dire qu'il a déclaré les variables login et pass), alors elle pourra s'en servir !

Le module de news

Nous allons maintenant attaquer le module de news sur notre application. Comme d'habitude, nous commençons par la liste des fonctionnalités attendues.

Fonctionnalités

Ce module doit nous permettre de gérer le contenu de la base de données. Par conséquent, nous devons avoir quatre actions :

  • L'action index qui nous affiche la liste des news avec des liens pour les modifier ou supprimer.

  • L'action insert pour ajouter une news.

  • L'action update pour modifier une news.

  • L'action delete pour supprimer une news.

L'action index

La route

Tout d'abord, définissons l'URL qui pointera vers cette action. Je vous propose tout simplement que ce soit l'accueil de l'espace d'administration :

<?xml version="1.0" encoding="utf-8" ?>
<routes>
  <route url="/admin/" module="News" action="index" ></route>
</routes>
Le contrôleur

Le contrôleur se chargera uniquement de passer la liste des news à la vue ainsi que le nombre de news présent. Le contenu de la méthode est donc assez simple :

<?php
namespace App\Backend\Modules\News;

use \OCFram\BackController;
use \OCFram\HTTPRequest;

class NewsController extends BackController
{
  public function executeIndex(HTTPRequest $request)
  {
    $this->page->addVar('title', 'Gestion des news');

    $manager = $this->managers->getManagerOf('News');

    $this->page->addVar('listeNews', $manager->getList());
    $this->page->addVar('nombreNews', $manager->count());
  }
}

Comme vous le voyez, nous nous resservons de la méthode getList() que nous avions implémentée au cours du précédent chapitre au cours de la construction du frontend. Cependant, il nous reste à implémenter une méthode dans notre manager : count().

Le modèle

La méthode count() est très simple : elle ne fait qu'exécuter une requête pour renvoyer le résultat.

<?php
namespace Model;

use \OCFram\Manager;

abstract class NewsManager extends Manager
{
  /**
   * Méthode renvoyant le nombre de news total.
   * @return int
   */
  abstract public function count();
  
  // ...
}
<?php
namespace Model;

class NewsManagerPDO extends NewsManager
{
  public function count()
  {
    return $this->dao->query('SELECT COUNT(*) FROM news')->fetchColumn();
  }
  
  // ...
}
La vue

La vue se contente de parcourir le tableau de news pour en afficher les données. Faites dans la simplicité. ;)

<p style="text-align: center">Il y a actuellement <?= $nombreNews ?> news. En voici la liste :</p>

<table>
  <tr><th>Auteur</th><th>Titre</th><th>Date d'ajout</th><th>Dernière modification</th><th>Action</th></tr>
<?php
foreach ($listeNews as $news)
{
  echo '<tr><td>', $news['auteur'], '</td><td>', $news['titre'], '</td><td>le ', $news['dateAjout']->format('d/m/Y à H\hi'), '</td><td>', ($news['dateAjout'] == $news['dateModif'] ? '-' : 'le '.$news['dateModif']->format('d/m/Y à H\hi')), '</td><td><a href="news-update-', $news['id'], '.html"><img src="/images/update.png" alt="Modifier" /></a> <a href="news-delete-', $news['id'], '.html"><img src="/images/delete.png" alt="Supprimer" /></a></td></tr>', "\n";
}
?>
</table>

L'action insert

La route

Je vous propose que l'URL qui pointera vers cette action soit /admin/news-insert.html. Je pense que maintenant vous savez comment définir une route, mais je remets le fichier pour que tout le monde suive bien :

<?xml version="1.0" encoding="utf-8" ?>
<routes>
  <route url="/admin/" module="News" action="index" ></route>
  <route url="/admin/news-insert\.html" module="News" action="insert" ></route>
</routes>
Le contrôleur

Le contrôleur vérifie si le formulaire a été envoyé. Si c'est le cas, alors il procédera à la vérification des données et insérera la news en BDD si tout est valide. Cependant, il y a un petit problème : lorsque nous implémenterons l'action update, nous allons devoir réécrire la partie « traitement du formulaire » car la validation des données suit la même logique. Nous allons donc créer une autre méthode au sein du contrôleur, nommée processForm(), qui se chargera de traiter le formulaire et d'enregistrer la news en BDD.

<?php
namespace App\Backend\Modules\News;

use \OCFram\BackController;
use \OCFram\HTTPRequest;
use \Entity\News;

class NewsController extends BackController
{
  // ...
  
  public function executeInsert(HTTPRequest $request)
  {
    if ($request->postExists('auteur'))
    {
      $this->processForm($request);
    }
    
    $this->page->addVar('title', 'Ajout d\'une news');
  }
  
  public function processForm(HTTPRequest $request)
  {
    $news = new News([
      'auteur' => $request->postData('auteur'),
      'titre' => $request->postData('titre'),
      'contenu' => $request->postData('contenu')
    ]);
    
    // L'identifiant de la news est transmis si on veut la modifier.
    if ($request->postExists('id'))
    {
      $news->setId($request->postData('id'));
    }
    
    if ($news->isValid())
    {
      $this->managers->getManagerOf('News')->save($news);
      
      $this->app->user()->setFlash($news->isNew() ? 'La news a bien été ajoutée !' : 'La news a bien été modifiée !');
    }
    else
    {
      $this->page->addVar('erreurs', $news->erreurs());
    }
    
    $this->page->addVar('news', $news);
  }
}
Le modèle

Nous allons implémenter les méthodes save() et add() dans notre manager afin que notre contrôleur puisse être fonctionnel.

<?php
namespace Model;

use \OCFram\Manager;
use \Entity\News;

abstract class NewsManager extends Manager
{
  /**
   * Méthode permettant d'ajouter une news.
   * @param $news News La news à ajouter
   * @return void
   */
  abstract protected function add(News $news);
  
  /**
   * Méthode permettant d'enregistrer une news.
   * @param $news News la news à enregistrer
   * @see self::add()
   * @see self::modify()
   * @return void
   */
  public function save(News $news)
  {
    if ($news->isValid())
    {
      $news->isNew() ? $this->add($news) : $this->modify($news);
    }
    else
    {
      throw new \RuntimeException('La news doit être validée pour être enregistrée');
    }
  }
  
  // ...
}
<?php
namespace Model;
  
use \Entity\News;

class NewsManagerPDO extends NewsManager
{
  protected function add(News $news)
  {
    $requete = $this->dao->prepare('INSERT INTO news SET auteur = :auteur, titre = :titre, contenu = :contenu, dateAjout = NOW(), dateModif = NOW()');
    
    $requete->bindValue(':titre', $news->titre());
    $requete->bindValue(':auteur', $news->auteur());
    $requete->bindValue(':contenu', $news->contenu());
    
    $requete->execute();
  }
  
  // ...
}
La vue

Là aussi, nous utiliserons de la duplication de code pour afficher le formulaire. En effet, la vue correspondant à l'action update devra également afficher ce formulaire. Nous allons donc créer un fichier qui contiendra ce formulaire et qui sera inclus au sein des vues. Je vous propose de l'appeler _form.php (le _ est ici utilisé pour bien indiquer qu'il ne s'agit pas d'une vue mais d'un élément à inclure).

Notre vue insert.php contiendra ainsi :

<h2>Ajouter une news</h2>
<?php require '_form.php';

Le contenu fichier _form.php sera celui-ci :

<form action="" method="post">
  <p>
    <?= isset($erreurs) && in_array(\Entity\News::AUTEUR_INVALIDE, $erreurs) ? 'L\'auteur est invalide.<br />' : '' ?>
    <label>Auteur</label>
    <input type="text" name="auteur" value="<?= isset($news) ? $news['auteur'] : '' ?>" /><br />
    
    <?= isset($erreurs) && in_array(\Entity\News::TITRE_INVALIDE, $erreurs) ? 'Le titre est invalide.<br />' : '' ?>
    <label>Titre</label><input type="text" name="titre" value="<?= isset($news) ? $news['titre'] : '' ?>" /><br />
    
    <?= isset($erreurs) && in_array(\Entity\News::CONTENU_INVALIDE, $erreurs) ? 'Le contenu est invalide.<br />' : '' ?>
    <label>Contenu</label><textarea rows="8" cols="60" name="contenu"><?= isset($news) ? $news['contenu'] : '' ?></textarea><br />
<?php
if(isset($news) && !$news->isNew())
{
?>
    <input type="hidden" name="id" value="<?= $news['id'] ?>" />
    <input type="submit" value="Modifier" name="modifier" />
<?php
}
else
{
?>
    <input type="submit" value="Ajouter" />
<?php
}
?>
  </p>
</form>

L'action update

La route

Pas d'originalité ici, nous allons choisir une URL basique : /admin/news-update-id.html.

<?xml version="1.0" encoding="utf-8" ?>
<routes>
  <route url="/admin/" module="News" action="index" ></route>
  <route url="/admin/news-insert\.html" module="News" action="insert" ></route>
  <route url="/admin/news-update-([0-9]+)\.html" module="News" action="update" vars="id" ></route>
</routes>
Le contrôleur

La méthode executeUpdate() est quasiment identique à executeInsert(). La seule différence est qu'il faut passer la news à la vue si le formulaire n'a pas été envoyé.

<?php
namespace App\Backend\Modules\News;

use \OCFram\BackController;
use \OCFram\HTTPRequest;
use \Entity\News;

class NewsController extends BackController
{
  // ...
  
  public function executeUpdate(HTTPRequest $request)
  {
    if ($request->postExists('auteur'))
    {
      $this->processForm($request);
    }
    else
    {
      $this->page->addVar('news', $this->managers->getManagerOf('News')->getUnique($request->getData('id')));
    }
    
    $this->page->addVar('title', 'Modification d\'une news');
  }
  
  // ...
}
Le modèle

Ce code fait appel à deux méthodes : getUnique() et modify(). La première a déjà été implémentée au cours du précédent chapitre et la seconde avait volontairement été laissée de côté. Il est maintenant temps de l'implémenter.

<?php
namespace Model;

use \OCFram\Manager;
use \Entity\News;

abstract class NewsManager extends Manager
{
  // ...
  
  /**
   * Méthode permettant de modifier une news.
   * @param $news news la news à modifier
   * @return void
   */
  abstract protected function modify(News $news);
  
  // ...
}
<?php
namespace Model;
  
use \Entity\News;

class NewsManagerPDO extends NewsManager
{
  // ...
  
  protected function modify(News $news)
  {
    $requete = $this->dao->prepare('UPDATE news SET auteur = :auteur, titre = :titre, contenu = :contenu, dateModif = NOW() WHERE id = :id');
    
    $requete->bindValue(':titre', $news->titre());
    $requete->bindValue(':auteur', $news->auteur());
    $requete->bindValue(':contenu', $news->contenu());
    $requete->bindValue(':id', $news->id(), \PDO::PARAM_INT);
    
    $requete->execute();
  }
  
  // ...
}
La vue

De la même façon que pour l'action insert, ce procédé tient en deux lignes : il s'agit seulement d'inclure le formulaire, c'est tout !

<h2>Modifier une news</h2>
<?php require '_form.php';

L'action delete

La route

Pour continuer dans l'originalité, l'URL qui pointera vers cette action sera du type /admin/news-delete-id.html.

<?xml version="1.0" encoding="utf-8" ?>
<routes>
  <route url="/admin/" module="News" action="index" ></route>
  <route url="/admin/news-insert\.html" module="News" action="insert" ></route>
  <route url="/admin/news-update-([0-9]+)\.html" module="News" action="update" vars="id" ></route>
  <route url="/admin/news-delete-([0-9]+)\.html" module="News" action="delete" vars="id" ></route>
</routes>
Le contrôleur

Le contrôleur se chargera d'invoquer la méthode du manager qui supprimera la news. Ensuite, il redirigera l'utilisateur à l'accueil de l'espace d'administration en ayant pris soin de spécifier un message qui s'affichera au prochain chargement de page. Ainsi, cette action ne possèdera aucune vue.

<?php
namespace App\Backend\Modules\News;

use \OCFram\BackController;
use \OCFram\HTTPRequest;
use \Entity\News;

class NewsController extends BackController
{
  // ...
  
  public function executeDelete(HTTPRequest $request)
  {
    $this->managers->getManagerOf('News')->delete($request->getData('id'));
    
    $this->app->user()->setFlash('La news a bien été supprimée !');
    
    $this->app->httpResponse()->redirect('.');
  }
  
  // ...
}
Le modèle

Ici, une simple requête de type DELETE suffit.

<?php
namespace Model;

use \OCFram\Manager;
use \Entity\News;

abstract class NewsManager extends Manager
{
  // ...
  
  /**
   * Méthode permettant de supprimer une news.
   * @param $id int L'identifiant de la news à supprimer
   * @return void
   */
  abstract public function delete($id);
  
  // ...
}
<?php
namespace Model;
  
use \Entity\News;

class NewsManagerPDO extends NewsManager
{
  // ...
  
  public function delete($id)
  {
    $this->dao->exec('DELETE FROM news WHERE id = '.(int) $id);
  }
  
  // ...
}

N'oublions pas les commentaires !

Finissons de construire notre application en implémentant les dernières fonctionnalités permettant de gérer les commentaires.

Fonctionnalités

Nous allons faire simple et implémenter deux fonctionnalités :

  • La modification de commentaires.

  • La suppression de commentaires.

L'action updateComment

La route

Commençons, comme d'habitude, par définir l'URL qui pointera vers ce module. Je vous propose quelque chose de simple comme /admin/comments-update-id.html.

<?xml version="1.0" encoding="utf-8" ?>
<routes>
  <route url="/admin/" module="News" action="index" ></route>
  <route url="/admin/news-insert\.html" module="News" action="insert" ></route>
  <route url="/admin/news-update-([0-9]+)\.html" module="News" action="update" vars="id" ></route>
  <route url="/admin/news-delete-([0-9]+)\.html" module="News" action="delete" vars="id" ></route>
  <route url="/admin/comment-update-([0-9]+)\.html" module="News" action="updateComment" vars="id" ></route>
</routes>
Le contrôleur

La méthode que l'on implémentera aura pour rôle de contrôler les valeurs du formulaire et de modifier le commentaire en BDD si tout est valide. Vous devriez avoir quelque chose de semblable à ce que nous avons dans l'application Frontend. Il faudra ensuite rediriger l'utilisateur sur la news qu'il lisait.

<?php
namespace App\Backend\Modules\News;

use \OCFram\BackController;
use \OCFram\HTTPRequest;
use \Entity\News;
use \Entity\Comment;

class NewsController extends BackController
{
  // ...
  
  public function executeUpdateComment(HTTPRequest $request)
  {
    $this->page->addVar('title', 'Modification d\'un commentaire');
    
    if ($request->postExists('pseudo'))
    {
      $comment = new Comment([
        'id' => $request->getData('id'),
        'auteur' => $request->postData('pseudo'),
        'contenu' => $request->postData('contenu')
      ]);
      
      if ($comment->isValid())
      {
        $this->managers->getManagerOf('Comments')->save($comment);
        
        $this->app->user()->setFlash('Le commentaire a bien été modifié !');
        
        $this->app->httpResponse()->redirect('/news-'.$request->postData('news').'.html');
      }
      else
      {
        $this->page->addVar('erreurs', $comment->erreurs());
      }
      
      $this->page->addVar('comment', $comment);
    }
    else
    {
      $this->page->addVar('comment', $this->managers->getManagerOf('Comments')->get($request->getData('id')));
    }
  }
  
  // ...
}
Le modèle

Nous avons ici besoin d'implémenter deux méthodes : modify() et get(). La première se contente d'exécuter une requête de type UPDATE et la seconde une requête de type SELECT.

<?php
namespace Model;
  
use \OCFram\Manager;
use \Entity\Comment;

abstract class CommentsManager extends Manager
{
  // ...
  
  /**
   * Méthode permettant de modifier un commentaire.
   * @param $comment Le commentaire à modifier
   * @return void
   */
  abstract protected function modify(Comment $comment);
  
  /**
   * Méthode permettant d'obtenir un commentaire spécifique.
   * @param $id L'identifiant du commentaire
   * @return Comment
   */
  abstract public function get($id);
}
<?php
namespace Model;
    
use \Entity\Comment;

class CommentsManager_PDO extends CommentsManager
{
  // ...
  
  protected function modify(Comment $comment)
  {
    $q = $this->dao->prepare('UPDATE comments SET auteur = :auteur, contenu = :contenu WHERE id = :id');
    
    $q->bindValue(':auteur', $comment->auteur());
    $q->bindValue(':contenu', $comment->contenu());
    $q->bindValue(':id', $comment->id(), \PDO::PARAM_INT);
    
    $q->execute();
  }
  
  public function get($id)
  {
    $q = $this->dao->prepare('SELECT id, news, auteur, contenu FROM comments WHERE id = :id');
    $q->bindValue(':id', (int) $id, \PDO::PARAM_INT);
    $q->execute();
    
    $q->setFetchMode(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE, '\Entity\Comment');
    
    return $q->fetch();
  }
}
La vue

La vue ne fera que contenir le formulaire et afficher les erreurs s'il y en a.

<form action="" method="post">
  <p>
    <?= isset($erreurs) && in_array(\Entity\Comment::AUTEUR_INVALIDE, $erreurs) ? 'L\'auteur est invalide.<br />' : '' ?>
    <label>Pseudo</label><input type="text" name="pseudo" value="<?= htmlspecialchars($comment['auteur']) ?>" /><br />
    
    <?= isset($erreurs) && in_array(\Entity\Comment::CONTENU_INVALIDE, $erreurs) ? 'Le contenu est invalide.<br />' : '' ?>
    <label>Contenu</label><textarea name="contenu" rows="7" cols="50"><?= htmlspecialchars($comment['contenu']) ?></textarea><br />
    
    <input type="hidden" name="news" value="<?= $comment['news'] ?>" />
    <input type="submit" value="Modifier" />
  </p>
</form>
Modification de la vue de l'affichage des commentaires

Pour des raisons pratiques, il serait préférable de modifier l'affichage des commentaires afin d'ajouter un lien à chacun menant vers la modification du commentaire. Pour cela, il faudra modifier la vue correspondant à l'action show du module news de l'application Frontend.

<p>Par <em><?= $news['auteur'] ?></em>, le <?= $news['dateAjout']->format('d/m/Y à H\hi') ?></p>
<h2><?= $news['titre'] ?></h2>
<p><?= nl2br($news['contenu']) ?></p>

<?php if ($news['dateAjout'] != $news['dateModif']) { ?>
  <p style="text-align: right;"><small><em>Modifiée le <?= $news['dateModif']->format('d/m/Y à H\hi') ?></em></small></p>
<?php } ?>

<p><a href="commenter-<?= $news['id'] ?>.html">Ajouter un commentaire</a></p>

<?php
if (empty($comments))
{
?>
<p>Aucun commentaire n'a encore été posté. Soyez le premier à en laisser un !</p>
<?php
}

foreach ($comments as $comment)
{
?>
<fieldset>
  <legend>
    Posté par <strong><?= htmlspecialchars($comment['auteur']) ?></strong> le <?= $comment['date']->format('d/m/Y à H\hi') ?>
    <?php if ($user->isAuthenticated()) { ?> -
      <a href="admin/comment-update-<?= $comment['id'] ?>.html">Modifier</a> |
    <?php } ?>
  </legend>
  <p><?= nl2br(htmlspecialchars($comment['contenu'])) ?></p>
</fieldset>
<?php
}
?>

<p><a href="commenter-<?= $news['id'] ?>.html">Ajouter un commentaire</a></p>

L'action deleteComment

La route

Faisons là aussi très simple : nous n'avons qu'à prendre une URL du type /admin/comments-delete-id.html.

<?xml version="1.0" encoding="utf-8" ?>
<routes>
  <route url="/admin/" module="News" action="index" ></route>
  <route url="/admin/news-insert\.html" module="News" action="insert" ></route>
  <route url="/admin/news-update-([0-9]+)\.html" module="News" action="update" vars="id" ></route>
  <route url="/admin/news-delete-([0-9]+)\.html" module="News" action="delete" vars="id" ></route>
  <route url="/admin/comment-update-([0-9]+)\.html" module="News" action="updateComment" vars="id" ></route>
  <route url="/admin/comment-delete-([0-9]+)\.html" module="News" action="deleteComment" vars="id" ></route>
</routes>
Le contrôleur

Il faut dans un premier temps invoquer la méthode du manager permettant de supprimer un commentaire. Redirigez ensuite l'utilisateur sur l'accueil de l'espace d'administration.

<?php
namespace App\Backend\Modules\News;

use \OCFram\BackController;
use \OCFram\HTTPRequest;
use \Entity\News;
use \Entity\Comment;

class NewsController extends BackController
{
  // ...
  
  public function executeDeleteComment(HTTPRequest $request)
  {
    $this->managers->getManagerOf('Comments')->delete($request->getData('id'));
    
    $this->app->user()->setFlash('Le commentaire a bien été supprimé !');
    
    $this->app->httpResponse()->redirect('.');
  }
}

Aucune vue n'est donc nécessaire ici.

Le modèle

Il suffit ici d'implémenter la méthode delete() exécutant une simple requête DELETE.

<?php
namespace Model;

use \OCFram\Manager;
use \Entity\Comment;

abstract class CommentsManager extends Manager
{
  // ...
  
  /**
   * Méthode permettant de supprimer un commentaire.
   * @param $id L'identifiant du commentaire à supprimer
   * @return void
   */
  abstract public function delete($id);
  
  // ...
}
<?php
namespace Model;

use \Entity\Comment;

class CommentsManagerPDO extends CommentsManager
{
  // ...
  
  public function delete($id)
  {
    $this->dao->exec('DELETE FROM comments WHERE id = '.(int) $id);
  }
  
  // ...
}
Modification de l'affichage des commentaires

Nous allons là aussi insérer le lien de suppression de chaque commentaire afin de nous faciliter la tâche. Modifiez donc la vue de l'action show du module news de l'application frontend :

<p>Par <em><?= $news['auteur'] ?></em>, le <?= $news['dateAjout']->format('d/m/Y à H\hi') ?></p>
<h2><?= $news['titre'] ?></h2>
<p><?= nl2br($news['contenu']) ?></p>

<?php if ($news['dateAjout'] != $news['dateModif']) { ?>
  <p style="text-align: right;"><small><em>Modifiée le <?= $news['dateModif']->format('d/m/Y à H\hi') ?></em></small></p>
<?php } ?>

<p><a href="commenter-<?= $news['id'] ?>.html">Ajouter un commentaire</a></p>

<?php
if (empty($comments))
{
?>
<p>Aucun commentaire n'a encore été posté. Soyez le premier à en laisser un !</p>
<?php
}

foreach ($comments as $comment)
{
?>
<fieldset>
  <legend>
    Posté par <strong><?= htmlspecialchars($comment['auteur']) ?></strong> le <?= $comment['date']->format('d/m/Y à H\hi') ?>
    <?php if ($user->isAuthenticated()) { ?> -
      <a href="admin/comment-update-<?= $comment['id'] ?>.html">Modifier</a> |
      <a href="admin/comment-delete-<?= $comment['id'] ?>.html">Supprimer</a>
    <?php } ?>
  </legend>
  <p><?= nl2br(htmlspecialchars($comment['contenu'])) ?></p>
</fieldset>
<?php
}
?>

<p><a href="commenter-<?= $news['id'] ?>.html">Ajouter un commentaire</a></p>

Suppression des commentaires associés à une news

Il reste un dernier détail à régler. Si l'on supprime une news, qu'advient-il des commentaires ? Actuellement, ces commentaires sont toujours présents en base de données. Ceci est un problème récurrent dans la gestion de données. Pour pallier ce problème, il est possible de dire à MySQL lors de la création de la table comments que la colonne news_id est en réalité une référence à la colonne id de la table news. Ainsi, si l'on supprime une news, on peut dire très simplement à MySQL de supprimer toutes les entrées de la table comments liées à la news supprimée.

Cela impose par conséquent une contrainte sur notre table comments : chaque news_id de chaque commentaire doit contenir un identifiant valide, c'est-à-dire un identifiant pointant sur une news existante. Ainsi, il devient impossible de laisser en BDD un commentaire faisant référence à une news inexistante. Ce n'est peut-être pas le cas ici, mais dans d'autres situations on peut être amené à vouloir se laisser cette liberté, nous conduisant ainsi à ne pas imposer de telle contrainte sur la table concernée. Dans ce cas-là, c'est à nous de nous occuper des suppressions souhaitées.

Cela se fera très simplement. Dans un premier temps, nous allons créer une méthode deleteFromNews prenant un argument : l'identifiant de la news à supprimer. Cette méthode sera chargée de supprimer tous les commentaires liés à la news concernée.

<?php
namespace Model;

use \OCFram\Manager;
use \Entity\Comment;

abstract class CommentsManager extends Manager
{
  /**
   * Méthode permettant de supprimer tous les commentaires liés à une news
   * @param $news L'identifiant de la news dont les commentaires doivent être supprimés
   * @return void
   */
  abstract public function deleteFromNews($news);
}
<?php
namespace Model;

use \Entity\Comment;

class CommentsManagerPDO extends CommentsManager
{
  // ...
  
  public function deleteFromNews($news)
  {
    $this->dao->exec('DELETE FROM comments WHERE news = '.(int) $news);
  }
}

Il ne reste plus qu'à modifier l'action delete de notre NewsController.

<?php
namespace App\Backend\Modules\News;

use \OCFram\BackController;
use \OCFram\HTTPRequest;
use \Entity\Comment;
use \Entity\News;

class NewsController extends BackController
{
  public function executeDelete(HTTPRequest $request)
  {
    $newsId = $request->getData('id');
    
    $this->managers->getManagerOf('News')->delete($newsId);
    $this->managers->getManagerOf('Comments')->deleteFromNews($newsId);

    $this->app->user()->setFlash('La news a bien été supprimée !');

    $this->app->httpResponse()->redirect('.');
  }
}
Exemple de certificat de réussite
Exemple de certificat de réussite