Présentation de l’activité

Objectifs

L’objectif de cette activité est de travailler sur la couche modèle / données d’une application MVC, et d’expérimenter l’utilisation d’un ORM qui suit le principe des conventions.

Travail à faire

  • Prendre le temps d’étudier les points communs et différence entre modèle objet et relationnel.
  • Déployer le code de départ.
  • Initialiser l’ORM.
  • Créer les classes métiers.
  • Modifier les contrôleurs.

Étude

  • Étudier le cours sur l’ORM.
  • Analyser le prototype des méthodes publiques (pas le code), c’est à dire l’API de la classe ORM du cadriciel TeachFrame :
    • indiquer pour chaque opération CRUD quelle méthode utiliser et quels sont ses paramètres ;
    • indiquer ce qui permet de savoir que le cadriciel suit le principe des conventions plutôt que celui des annotations.
    • indiquer (et justifier) si le chargement des objets est anticipé ou paresseux.

Comparaisons entre modèle relationnel et objet

Bien que la persistance soit assurée par une base de données relationnelle, la programmation dans le contrôleur est orientée objets.

Diagramme UML de domaine (modèle objet)

Diagramme de domaine

Remarque : IslandCollection correspond à Array<Island>.

Shéma relationnel

Island (id, name, population, area, idArchipelago)
    clé primaire : id
    clé étrangère : idArchipelago référence Archipelago(id)

Archipelago (id, name)
    clé primaire : id

I - Déployer le code de départ

  • Créer un nouveau dossier orm et y extraire le code de départ, de sorte à ce que l’URL de l’application soit de la forme https://web.sio.local/p.nom4/orm.
  • Corriger la directive RewriteBase du fichier .htaccess.
  • Adapter la configuration du fichier config.yaml.
  • Ajouter les classes ORM et Collection du cadriciel TeachFrame.
  • Vérifier le bon fonctionnement de l’application avant de continuer.

II - Initialiser l’ORM

L’ORM contient des fonctions permettant de simplifier le point d’entrée (fichier index.php) de l’application ; remplacer le code de ce fichier par :

<?php
require 'lib/autoloader.php';
use teachframe\Router;
use teachframe\ORM;

ORM::setConfigPDO('config.yaml');

$router = new Router();
$router->loadRoutes('routes.yaml');
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);

La classe ORM du cadriciel “teachframe” est importée, et toutes les instructions de chargement du fichier de configuration et d’initialisation de PDO sont prises en charge par la méthode setConfigPDO de l’ORM.

III - Créer les classes métiers

Créer, dans le dossier (namespace) model, les classes métiers Island et Archipelago ; elles doivent avoir les mêmes propriétés que les champs des tables sous-jacentes de la base de données, à ces exceptions près :

  • les objets de la classe Island on une propriété $archipelago de type Archipelago, contrairement à la table qui a un champ idArchipelago de type int ;
  • les objets de la classe Archipelago ont une propriété (supplémentaire) islands qui doit (l’ORM l’impose) être de type IslandCollection.

Ajouter les accesseurs pour les propriétés (getId, getName…).

Code de la classe model\IslandCollection :

<?php
namespace model;
use teachframe\Collection;

class IslandCollection extends Collection {}

IV - Modifier les contrôleurs

Modifier les contrôleurs pour qu’ils utilisent l’ORM plutôt que des requêtes SQL pour extraire les objets de la base de données.

Ajouter un minimum de présentation (en attendant le développement de la vue) avec des liens pour la navigation entre îles et archipels.

Islandcontroller

<?php
namespace controller;
use teachframe\ORM;
use model\Island;

class IslandController {
  function getAll() {
    $islands = ORM::getAll(Island::class);

    echo '<p>Liste des îles :</p>';
    echo '<ul>'; //présentation minimaliste en attendant la vue
    foreach ($islands as $island) { //pour chaque île
      echo '<li><a href="' . Router::getURL('/island/' .$island->getId()) . '">' . $island->getName() . '</a></li>';
    }
    echo '</ul>';
  }

  function getOne (string $id) {
    $island = ORM::getOne(Island::class, $id);
    if (null == $island) {
      http_response_code(404);
      die('error: \'/island/' . $id . '\' not found');
    }
    $archipelago = $island->getArchipelago();

    echo '<dl>'; //présentation minimaliste en attendant la vue
    echo '<dt>Île :</dt><dd>' . $island->getName() . '</dd>';
    echo '<dt>Archipel :</dt><dd><a href="' . Router::getURL('/archipelago/' . $archipelago->getId()) . '">' . $archipelago->getName() . '</a></dd>';
    echo '</dl>';
    echo '<p><a href="' . Router::getURL('/island/') . '">Liste des îles.</a></p>';
  }
}

Dans les méthodes getOne et getAll des contrôleurs, les requêtes (query, prepare, exec…) sont remplacés par l’utilisation des méthodes éponymes (de même nom) de l’ORM qui renvoient des objets ou tableaux d’objets ; les instructions de parcours de jeu d’enregistrements (fetch et fetchAll) ne sont plus nécessaires.

Une collection (tableau) peut être parcourue par l’itérative foreach (par exemple), et les objets sont manipulés via leurs méthodes (accesseurs notamment).

L’approche objet permet de naviguer d’une classe à l’autre (cf $island->getArchipelago()) sans avoir à gérer les concepts relationnels : clés étrangères, jointures… Pour information, l’ORM utilisé effectue un chargement anticipé des objets liés lorsque la méthode getOne est utilisée ; le chargement paresseux (à la demande) n’est pas pris en charge ; par exemple, dans getAll, $island->getArchipelago() renverrait null.

La méthode getURL de la classe Router permet de corriger les URLs pour prendre en compte de façon transparente le dossier dans lequel l’application est installée (p.nom4/orm par exemple) ; exemple : Router::getURL('/island/') renvoie /p.nom4/orm/island.

ArchipelagoController

Pour la route archipelago_getone, le contrôleur doit afficher la liste des îles de l’archipel.