Créer framework PHP : Contrôleurs

Les contrôleurs sont responsables de la coordination des opérations, de la réception de la requête à sa réponse HTTP au navigateur.

Icône de calendrier
Avancé
10 chapitres

Qu’est-ce qu’un contrôleur ?

Les contrôleurs (en anglais « controllers ») constituent le « C » de MVC.

Le rôle d’un contrôleur est de coordonner les actions à mettre en place pour retourner au client une réponse HTTP adaptée.

Pour cela, il travaille en étroite collaboration avec les modèles (données) et les vues (templates). Il joue en quelque sorte le rôle de chef d’orchestre du site.

Dans une application traditionnelle (site web et non API), cette réponse retournée par le serveur se traduit généralement par :

  • Une page web
  • Une redirection

Son schéma d’exécution type se résume à :

  1. Intercepter la requête du client
  2. Effectuer potentiellement des opérations intermédiaires (interactions avec la BDD, envoi de mail…)
  3. Retourner une page web / effectuer une redirection

Contrôleur global

Implémenter des fonctions génériques

La mécanique de traitement de la requête HTTP pour y retourner une réponse HTTP adaptée sera contenue au sein d’une seconde classe, nommée contrôleur global.

Ainsi, ce contrôleur particulier va implémenter 2 méthodes génériques de notre framework pour retourner une page HTML et effectuer des redirections.

AbstractController.php
copié !
namespace Plugo\Controller;

abstract class AbstractController {}

Tout d’abord, on définit un namespace pour cette classe. Ici, il s’agit de Plugo\Controller, faisant directement référence au dossier 📁 lib/Controller (cf. autoload). Cette classe est déclarée avec le mot-clé abstract, cela signifie qu’elle ne sera jamais instanciée. Il s’agit donc plutôt d’une super-classe (parent) dont le rôle va être de mettre à disposition des méthodes.

Méthode 1 : retourner une vue

Ajoutons à cette classe une première méthode renderView(), dont le rôle sera de retourner une page HTML.

AbstractController.php
copié !
namespace Plugo\Controller;

abstract class AbstractController {

	protected function renderView(string $template, array $data = []): string {
		$templatePath = dirname(__DIR__, 2) . '/templates/' . $template;
		return require_once dirname(__DIR__, 2) . '/templates/layout.php';
	}

}

La méthode renderView() est déclarée comme protected, cela signifie qu’elle ne sera accessible que depuis les classes qui en héritent. Cette fonction possède deux paramètres :

  • template (string) : le nom du template HTML à retourner (requis)
  • data (array) : un tableau de données à transmettre au template (optionnel)

Le chemin vers le template est stocké dans une variable $templatePath. Le chemin vers le layout du site web est systématiquement inclus et retourné par la fonction.

Méthode 2 : effectuer une redirection

Ajoutons à cette classe une seconde méthode redirectToRoute(), dont le rôle sera d’effectuer des redirections.

AbstractController.php
copié !
namespace Plugo\Controller;

abstract class AbstractController {

	protected function renderView(string $template, array $data = []): string {
		// ...
	}

	protected function redirectToRoute(string $path, array $params = []): void {
		$uri = $_SERVER['SCRIPT_NAME'] . "?path=" . $path;

		if (!empty($params)) {
			$strParams = [];
			foreach ($params as $key => $val) {
				array_push($strParams, urlencode((string) $key) . '=' . urlencode((string) $val));
			}
			$uri .= '&' . implode('&', $strParams);
		}

		header("Location: " . $uri);
		die;
	}

}

La méthode redirectToRoute() est également déclarée comme protected, cela signifie qu’elle ne sera accessible que depuis les classes qui en héritent. Cette fonction possède deux paramètres :

  • path (string) : le chemin de la route vers laquelle rediriger (requis) - Exemples : /, /blog
  • params (array) : des paramètres d’URL GET (optionnels)
  1. $_SERVER['SCRIPT_NAME'] contient le chemin complet ainsi que le nom du fichier courant. $uri va ainsi générer l’URL complète correspondante à la route vers laquelle on souhaite rediriger en définissant le paramètre $_GET['path'].
  2. Si des paramètres d’URL sont transmis via la fonction redirectToRoute(), foreach parcourt le tableau associatif $params de couples clés-valeurs des paramètres GET pour les écrire dans un format d’URL standard. Exemple : $params = ['nb' => 12, 'order' => 'ASC'] deviendra &nb=12&order=ASC
  3. header() : effectue la redirection vers l’URL générée dynamiquement.

Notre contrôleur abstrait contenant 2 méthodes essentielles à l’usage de notre framework, il sera nécessaire de l’importer au sein de chaque contrôleur applicatif.

Contrôleurs applicatifs

Coordonner la logique métier

Le rôle d’un contrôleur applicatif va être de coordonner toutes les actions propres à un projet. Une action est caractérisée par toute opération effectuée sur le serveur pour répondre à la demande du client, comme par exemple :

  • Enregistrer un article en BDD
  • Envoyer un e-mail
  • Afficher une page
  • Supprimer un produit en BDD
  • Déplacer un fichier sur le serveur
  • Effectuer une redirection
  • Etc.

Les contrôleurs applicatifs seront uniquement créés par les utilisateurs du framework.

Ils se situeront donc dans le dossier 📁 src et non 📁 lib.

Nous avions créé au chapitre une route nommée home (répondant donc à l’URL index.php?path=home) chargée de déclencher la méthode home() du contrôleur MainController.

routes.php
copié !
const ROUTES = [
	'home' => [
		'controller' => App\Controller\MainController::class,
		'method' => 'home'
	],
];

Il est temps de créer ce contrôleur.

MainController.php
copié !
namespace App\Controller;

use Plugo\Controller\AbstractController;

class MainController extends AbstractController {

}
  1. Comme toute classe, on la définit dans un namespace. Ici, il s’agit de App\Controller, faisant directement référence au dossier 📁 src/Controller (cf. autoload).
  2. Ensuite, on spécifie faire usage de la classe AbstractController citée dans le namespace Plugo\Controller au sein de ce fichier.
  3. On déclare notre class MainController, héritant de la super-classe AbstractController.

Méthode = action

Ajoutons à notre contrôleur une première méthode chargée d’afficher la page d’accueil du site via la méthode renderView().

MainController.php
copié !
namespace App\Controller;

use Plugo\Controller\AbstractController;

class MainController extends AbstractController {

	public function home() {
		return $this->renderView('main/home.php', ['title' => 'Accueil']);
	}

}

Ici je souhaite retourner la page 📄 templates/main/home.php en lui transmettant dans une variable le titre de la page.

Pour exploiter la méthode redirectToRoute() du contrôleur parent, ajoutons une seconde méthode simulant une redirection.

MainController.php
copié !
namespace App\Controller;

use Plugo\Controller\AbstractController;

class MainController extends AbstractController {

	public function home() { // ... }

	public function contact() {
		// Imaginons ici traiter la soumission d'un formulaire de contact et envoyer un mail...
		return $this->redirectToRoute('home', ['state' => 'success']);
	}

}

Ici, je souhaite qu’après traitement, cette méthode redirige vers la route home, en lui transmettant par exemple le paramètre d’URL GET state=success.

Si nous accédons à nouveau à l’URL plugo.local/index.php?path=/ ou localhost/nom-projet/public/index.php?path=/, nous devrions voir apparaître l’erreur suivante :

Warning: require_once({chemin_vers_repertoire_racine}/nom-projet/templates/layout.php):
Failed to open stream: No such file or directory in {chemin_vers_repertoire_racine}\nom-projet\lib\Controller\AbstractController.php on line 9

Rien de plus normal car nous n’avons pas encore créé les templates de notre framework… il est temps d’y remédier !