On va développer incrémentalement un système de gestion d'anomalie ou demande d'évolution (ticket), en anglais : YAHP (Yet Another Help Desk).

TP1

Techniques mises en oeuvre

  1. formulaire HTML, 
  2. feuille CSS, 
  3. javascript

Expression des besoins

YAHP doit permettre la saisie web d'un ticket : Jacques (un employé de la DSI)  doit pouvoir utiliser son navigateur favori pour rédiger une demande d'intervention...
UC

Travail demandé

Développer la page statique saisieTicket.html suivante permettant la saisie d'un ticket :

formulaire

(Les couleurs, polices, ... sont précisées dans la feuille de style proposée.)
Rajouter du javascript pour s'assurer que les champs obligatoires (Application et Résumé) ont bien été saisis.

Précision : la feuille de style styles.css sera partagée par toutes les étapes du TP et doit donc se trouver dans le réperoire tpdsi/styles

structure

TP PHP 2

TP2

Techniques mises en oeuvre

  1. récupération des paramètres HTTP, 
  2. utilisation des fonctions de manipulation de chaines de caractères
  3. récupération des caractéristiques du navigateur client

Expression des besoins

YAHP doit permettre à plusieurs utilisateurs la saisie web d'un ticket et montrer que le ticket a bien été reçu par le serveur.
UC

Travail demandé

Développer la page saisieTicket.html et le script editTicketAction.php.
Ce script doit d'abord afficher l'adresse IP de l'internaute puis récupèrer les infos saisies dans le formulaire editTicketForm.html pour les afficher dans un tableau d'id buglist (<table id="buglist" border="1" cellspacing="1">).
La couleur de fond de ces infos dépendra de la priorité de la demande. Pour cela on  utilisera un style particulier pour les lignes du tableau (balise <tr class="tab_bg_???">) à choisir parmi les styles suivants à définir dans la feuille de style :
Priorité Couleur
de fond
Style à
utiliser
Très urgente red tab_bg_red
Urgente orange tab_bg_orange
Moyenne yellow tab_bg_yellow
Faible blue tab_bg_blue
Très faible green tab_bg_green
La description détaillée sera tronquée à 40 caractères à l'affichage.

Exemple d'affichage :
(Demande envoyée par l'adresse IP 127.0.0.1)
Application Priorité Type Date Résumé Description
YAHP Très urgente anomalie ? Mes demandes d'intervention ne sont pas sauvegardées J'ai saisi plusieurs demandes d\'interv


Note 1 : Vous devez utiliser impérativement (en l'adaptant très légèrement) le fichier saisieTicket.html  fourni dans la correction du TP1.
Note 2 : On ne traitera pas la pièce-jointe éventuelle dans cette étape.

TP PHP 3

TP3

Techniques mises en oeuvre

  1. lecture d'un fichier,
  2. upload de fichier,
  3. écriture dans un fichier,
  4. manipulation de dates, 
  5. session

Expression des besoins

YAHP doit permettre la saisie web d'un ticket ... et sa sauvegarde sur le serveur!
Un administrateur doit pouvoir préciser la liste des applications gérées par le système sans modifier le code html ou php; cette liste est donc définie sur le serveur dans un fichier app.csv (exemple) et lue lors de la construction du formulaire.
Pour éviter des attaques malicieuses, seuls les utilisateurs authentifiés ont désormais la possibilité de se connecter.
UC
En l'absence de base de données, les informations d'un ticket sont sauvegardées sur le serveur dans un fichier tickets.csv. Chaque ligne de ce fichier a la structure suivante :
	ID_ticket;nom_application;login;ID_priorité;type;date_de_création;résumé;description;nom_de_la_pièce_jointe
    (où ID_ticket et ID_priorité sont des entiers, type peut prendre les valeurs 'anomalie' ou 'évolution', la priorité est un entier, la date est une chaîne de caractères au format jj/mm/yyyy)

Travail demandé

Génération dynamique de la liste des applications

Certains des éléments du formulaire de saisie de ticket devenant aussi dynamiques, on va remplacer le fichier statique saisieTicket.html par une page dynamique editTicketForm.php.
Le lien public permettant à un utilisateur de saisir un ticket devient editTicket.php. Ce fichier doit :

Précision : la fonction getAllApplisAsOptions() doit retourner une chaine de caractères de la forme :

        <option value="-1">--Autre--</option>
        <option>Application1</option>
        <option>Application2</option> ...

Sauvegarde des informations saisies dans le formulaire

Modifier le script de traitement du formulaire editTicketAction.php. Il doit définir et utiliser les fonctions suivantes:
Précisions
Les fonctions saveTicket() et printTicketAsTableRow() doivent avoir les signatures suivantes:
. saveTicket($applicationName, $login, $priorityId, $type, $date, $oneLiner, $description, $attachmentName)
.printTicketAsTableRow($applicationName, $priorityText, $type, $date, $oneLiner, $description, $tableRecordClass)

La date de création du ticket doit aussi être enregistrée au format jj/mm/yyyy.
Par souci de simplification, on pourra considèrer dans cette étape que le champ description ne comporte qu'une seule ligne.

(Note : il peut s'avérer nécessaire de modifier les droits d'écriture du fichier tickets.csv.)

Gestion de session

On souhaite protéger l'accès à la page editTicket.php et aux autres pages: si l'utilisateur ne s'est pas identifié, (i.e.si un attribut userId (ou login)  n'est pas présent dans la session), il est renvoyé sur un formulaire d'identification.
Suivre les étapes du tutoriel de contrôle d'accès aux pages pour mettre en place ce mécanisme de protection.

TP PHP 4

TP4

Techniques mises en oeuvre

  1. programmation objet, 
  2. pattern DAO
  3. remaniement (refactoring)

Expression des besoins

YAHP doit permettre la visualisation web des tickets existants. Après le premier cas d'utilisation "Créer un ticket" vu dans le TP précédent,  un nouveau cas d'utilisation "Voir tous les tickets" est donc recensé et doit être implémenté.
UC
On souhaite aussi refactoriser le code php précédemment écrit en utilisant des objets.

Travail demandé

CU "S'authentifier"

Définir la classe LaxisteLoginController  (sous classe de AbstractLoginController)  qui redéfinit les méthodes :
  1. isValidLogin( $login);  // retourne true si $login a au moins 2 caractères
  2. isValidPassword($login,  $password); //  retourne true si $login et $password sont identiques!!
Configurer le système de contrôle d'accès par login en utilisant une instance de LaxisteLoginController pour vérifier login et mot de passe.

CU "Créer un ticket"

Classe Ticket

Définir (dans un fichier Ticket.class.php) la classe Ticket qui a pour attributs les différentes infos associées à un ticket : ticketID, applicationName, login, priority (sous forme d'un entier), type, creationDate, oneLiner, detailedDescription, attachmentName.
Définir un constructeur ayant autant de paramètres pour initialiser ces attributs ainsi que les différents accesseurs permettant d'obtenir la valeur de ces attributs, plus précisément définir:

Classe TicketDAO

La classe TicketDAO déclare un attribut privé $filePath permettant de préciser le chemin du fichier utilisé pour la persistance des tickets (par défaut tickets.csv).
Compléter le fichier TicketDAO.class.php dont un squelette est fourni  pour définir les méthodes suivantes :

Classe AppService

Dans le souci de séparer le code de l'application en différentes couches, définir la classe AppService qui va avoir pour responsabilité d'offrir un certain nombre de services utilisés par la couche de présentation. Dans la classe AppService, définir donc, en les extrayant du fichier editTicketAction.php,  les méthodes suivantes qui prennent une instance de Ticket en paramètre :

Inclure les fichiers requis comme précédemment.

Controleur editTicketAction

Dans editTicketAction.php:

Le diagramme de séquence suivant représente les échanges de messages nécessaires :

Diagramme de séquence "Créer un ticket"

CU "Voir tous les tickets"

Ecrire une page de consultation showAllTickets.php affichant tous les tickets créés dans une table. Exemple d'affichage :
Application Priorité Type Date Résumé Description
YAHD Très urgente anomalie 06/05/2007 Mes demandes d'intervention ne sont pas sauvegardées J'ai saisi plusieurs demandes d'intervention avec cet outil mais elles ont disparu
YAHD Urgente evolution 06/02/2008 Je voudrais n'afficher que certains tickets
YAHD Moyenne anomalie 06/02/2008 Le rouge affiché est trop criard
YAHD Moyenne anomalie 06/02/2008 La police utilisée est trop petite

Chaque ticket est affiché avec une couleur reflétant la priorité de la demande (AppService::printTicketAsTableRow($ticket)).

Dans la classe AppService définir la méthode getAllTickets()  qui retourne un tableau contenant toutes les instances de tickets (par un appel à la méthode findAll() de TicketDAO)

Ecrire le fichier showAllTickets.php (en récupérant une grande partie du code HTML de editTicketAction.php); protéger l'accès à cette nouvelle page de façon identique à la page editTicket.php (de telle sorte que seuls les utilisateurs authentifiés puissent consulter la liste de tous les tickets ... Attention, ils ne doivent pas être redirigés vers une autre page dans le cas contraire).

Le diagramme de séquence suivant représente ces échanges de messages :
Diagramme de séquence "Voir tous les tickets"
Le diagramme de classes ci-dessous permet de préciser les relations (de dépendance en l'occurrence) entre ces classes:
Diagramme de séquence "Voir tous les tickets"

Page d'accueil

Créer un fichier index.php reprenant/adaptant le contenu de cet exemple de superbe page d'accueil.

TP PHP 5

TP5

Techniques mises en oeuvre

  1. Bases de données
  2. pattern DAO
  3. PDO

Expression des besoins

YAHP doit permettre la visualisation web des tickets existants.
Le système de persistance des tickets dans un fichier plat s'avère peu pratique :
L'utilisation d'une base de données relationnelle s'impose donc.

Cette nouvelle évolution est purement technique et non fonctionnelle. Il n'y a pas de nouveaux cas d'utilisation.

Analyse

On se doute qu'on sera amené à manipuler de nouvelles entités (dite "métiers") représentées dans le diagramme de classe ci dessous:
CD_entities

Travail demandé

Initialisation de la base de données

A l'aide de phpMyAdmin (et éventuellement de ce tutoriel), créer une nouvelle base de données MySQL appelée yahd, y insérer le schéma schema.sql.
(Note : pour simplifier on déclare la date sous forme d'une chaine de caractères (VARCHAR) plutôt que sous forme d'une DATE et on range le nom de l'application directement dans le champ applicationName).

Classes DAO

Renommer le fichier TicketDAO.class.php en  FileTicketDAO.class.php (y renommer la classe définie).

Créer le fichier TicketDAO.class.php pour y insérer la définition fournie de la classe désormais abstraite TicketDAO (dont les méthodes ne contiennent pas de code).

Classe PDOTicketDAO

On va utiliser l'extension PDO (et plus précisément les méthodes  PDOStatement::execute() et PDOStatement::fetch()) pour accéder à la base de donnés MySQL.
Créer le fichier pdo.conf.php  fourni qui définit les paramètres de connexion à la base de données.
Compléter le fichier PDOTicketDAO.class.php dont un squelette est fourni.

On pourra mettre au point (tester) cette classe en utilisant en ligne de commande le script fourni ticketMenu.app.php de la façon suivante :

$ php ticketMenu.app.php
Usage : php ticketMenu.app.php command
  where command is insert, findAll, findById id, deleteAll, ...
$ php ticketMenu.app.php deleteAll

$ php ticketMenu.app.php insert

$ php ticketMenu.app.php findAll
Array
(
    [0] => Ticket Object
        (
            [ticketID:private] => 5
            [applicationName:private] => Paye
            [login:private] => unittest
            [priority:private] => 3

Classe AppService

Modifier la classe AppService dans AppService.class.php en conséquence (noter que les modifications à apporter sont minimales grâce à l'utilisation du pattern DAO).

Tests unitaires

Valider les développements en exécutant les tests unitaires de ce TP jusqu'à l'obtention de la barre verte. TP PHP 6

TP6

Techniques mises en oeuvre

  1. Tests unitaires PHPUnit
  2. TDD (Test Driven Development)
  3. Refactoring (réusinage de code)
  4. envoi de mail

Expression des besoins

On veut pouvoir tester le site facilement, notamment pour s'assurer que les modifications apportées régulièrement au code source n'introduisent pas des régressions. On doit donc développer des batteries de tests unitaires UnitTestCase. On aimerait bien pouvoir tester aussi automatiquement tous les enchainements d'écran du site web en écrivant des test de recettes web WebTestCase.

Le responsable d'une application doit recevoir un courrier électronique lorsque qu'un ticket relatif à l'application dont il a la responsabilité est créé (le mail du responsable est aussi spécifié dans le fichier app.csv qui contient désormais par exemple :
1;Paye        ;paye@cnam.fr
2;Inscriptions;inscriptions@cnam.fr
3;mon appli   ;moi@cnam.fr
).

Travail demandé

Test Unitaire de la classe Product

Définir (dans un fichier appelé ProductTest.class.php, dans le même répertoire que Product.class.php) la classe de tests ProductTest. Cette classe doit définir les méthodes de test suivantes : testGetMail, testSetMail, testCheckMailWithValidLength, testCheckTooShortMail, testCheckMailWithoutArrobas, testCheckMailDomain, testCheckMailName, ....

Définir (dans un fichier Product.class.php) la classe Product qui a pour attributs les différentes infos associées à une application : id, name et mail.
Définir également dans cette classe Product un constructeur (Product($name, $mail, $id = -1)), les accesseurs getMail() et setMail($mail).

Important : le but est de développer la classe Product avec une approche TDD.

Implémenter la méthode checkMail itérativement, - en faisant (échouer puis) passer successivement les tests testCheckMailWithValidLength, testCheckTooLongMail,  testCheckMailWithoutArrobas, ...-,  ... et en respectant la documentation suivante:

 /**
  * check a mail address
  *
  * check the correctness of length, domain and contents
  * of a mail address
  *
  * @param string $mail mail address to check
  *   if this $mail parameter is '', check the mail
  *   atribute of the current object
  * @return boolean true if mail OK, false else
  */
 function checkMail($mail = '') {
  if ( $mail == '' )
   $mail = $this->mail;
  // 1. tests sur longueur
  // TODO

Plus précisément, une adresse mail (nom@domaine) sera considérée comme valable si elle vérifie chacune des conditions suivantes :
  1. sa longueur est supérieure à 10 et inférieure à 35,
  2. contient un arrobase '@',
  3. la partie domaine se termine par .fr,
  4. la partie nom ne contient que des lettres de a à z et/ou des points ou tirets (indication : utiliser le masque '/^[a-zA-Z.-]+$/'  avec preg_match).

Test Unitaire de la classe FileProductDAO

A l'image de la classe FileTicketDAO qui est responsable de lire/écrire dans un fichier csv les instances de Ticket, la classe FileProductDAO va permettre d'accéder à des instances de Product stokées dans le fichier texte app.csv .
Ecrire cette classe FileProductDAO qui satisfait le test unitaire FileProductDAOTest qui est fourni dans le fichier FileProductDAOTest.class.php .

Notez que FileProductDAO définit un constructeur ayant un paramètre optionnel : FileProductDAO($productFile='app.csv'), constructeur appelé par la classe de test :

class FileProductDAOTest extends UnitTestCase {
    private $dao; // fixture
    function setUp() {
        $this->dao = new FileProductDAO('app_test.csv');
    }

Le fichier app_test.csv utilisé par le test est le suivant :

1;Paye        ;paye@cnam.fr
2;Inscriptions;inscriptions@cnam.fr
3;mon appli   ;moi@cnam.fr

Refactoring de editTicket.php pour utiliser AppService

La page editTicket.php contient du code qu'on va déplacer vers la couche Service dans la classe AppService.

Dans la classe AppService définir l'attribut _productDAO (à initialiser dans le constructeur) et les nouvelles méthodes :
  1. getAppList() (à supprimer de editTicket.php et à adapter pour utiliser la classe FileProductDAO)
  2. getAllApplisAsOptions()  (à supprimer de editTicket.php et à adapter),
  3. getProductMailFromName($productName),
  4. sendMailForTicket($ticket).
Dans la page editTicket.php utiliser une méthode de la classe AppService pour initialiser $allApplisAsOptions.

Dans la page editTicketAction.php utiliser la méthode sendMailForTicket($ticket) de la classe AppService pour prévenir le responsable de l'application de la création d'un nouveau ticket.
TP PHP 7

TP7

Techniques mises en oeuvre

  1. Eléments de présentation d'IHM communs : fichiers d'en-tête,
  2. Architecture en couches : Vue -> Service -> Accès aux données,
  3. Factorisation du code SQL dans la super classe PDOAbstractDAO ("template method" design pattern)
  4. Centralisation de l'affichage.

Expression des besoins

On souhaite affirmer "l'identité visuelle" et la navigabilité de l'application YAHP.
Les applications (Products) doivent être elles aussi gérées dans la base de données désormais.
Paralèllement on veut rendre l'application plus maintenable et évolutive.

Travail demandé

Etape 1 - fichiers d'en-tête communs

Définir les fichiers header.inc.php et footer.inc.php qui contiennent les éléments de haut de page et de bas de page communs à toutes les pages accédées (par exemple unne image et/ou une barre de menus menubar.inc.php permettant d'accéder à l'ensemble des fonctionnalités du site). Les inclure dans les pages editTicket.php et showAllTickets.php.

Etape 2 - sources déplacés dans des sous-répertoires

On commence à avoir beaucoup de fichiers. On décide donc, pour s'y retrouver plus facilement, de les déplacer dans des sous-répertoires :

Nom du répertoire Contenu
domain classes métiers soit Ticket et Product pour l'instant
dao classes d'accès aux données (plus fichiers de configuration)
service classes de type service (AppService)
view fichiers affichant (ou traitant) des pages web

On définit ainsi implicitement les 3 couches logicielles Vue, Service et Accès aux données (correspondant respectivement aux répertoires view, service et dao) . Ces couches peuvent manipuler les entités du domaine métier. Par contre, la communication entre ces 3 couches est unidirectionnelle  dans le sens  view -> service -> dao : ainsi les classes de la couche service peuvent utiliser celles de la couche dao mais pas l'inverse : celles de la couche dao ne peuvent pas référencer les classes de la couche service.

Déplacer l'essentiel des fichiers sous la racine dans les sous répertoires adéquats (fichiers sql et csv ainsi que mysql.conf.php dans dao); seules exceptions : on ne déplacera pas le répertoire login et le fichier index.php qui doivent rester sous la racine.
Modifier dans les sources PHP les directives include d'inclusion de fichier en utilisant par exemple :
    include_once dirname(__FILE__) . '/../domain/Ticket.class.php';
à la place de :
    include_once 'Ticket.class.php';

Etape 3 - DAO simplifiés

Attention, le schéma de la base de données a changé: une nouvelle table product est introduite et la table ticket utilise désormais la clé étrangère product_fk à la place du champ application (comparer dao/structure.sql fourni et ../tp6/schema.sql).
Afin de faciliter et d'accélérer le développement des classes DAO, on a factorisé un maximum de code SQL (findById, insert, update, ...) dans la classe PDOAbstractDAO fournie. Cette classe bien qu'abstraite implémente par exemple insert(DomainObject $domainObject) en faisant appel à la méthode abstraite getInsertSqlStatement qui devrait être par les classes DAO concrètes (c'est une utilisation du  design pattern "Template Method").

Définir la classe Product (dans le fichier domain/Product.class.php) après avoir comparé le fichier domain/Ticket.class.php fournie avec celui du tp6.
Définir la classe PDOProductDAO (dans le fichier dao/PDOProductDAO.class.php) qui hérite de PDOAbstractDAO (à l'image de PDOTicketDAO).

Etape 4 - Centralisation de l'affichage

La solution proposée à l'étape 1 n'est pas totalement satisfaisante car elle requiert quand même l'inclusion des fichiers d'en-tête dans chaque page web accessible.
Elle peut être améliorée en prenant le problème à l'envers; plutôt que d'inclure les fichiers communs dans chaque page du site, on  peut se contenter de les inclure dans un seul script (index.php), à condition de paramétrer ce script par le contenu spécifique à afficher (au travers d'un paramètre page ou content par exemple) :

// index.php minimal
<?php session_start();
include 'login/loginAccess.inc.php';
include dirname(__FILE__) . '/view/header.inc.php';
$content = $_REQUEST['content'];
// Attention Trou de Sécurité potentiel ici
// Il faudrait faire des vérifications minimales sur $content
// pour interdire l'injection de code PHP externe
// (si $content commence par http:// par exemple)
include $content;
include dirname(__FILE__) . '/view/footer.inc.php';
?>

(Noter que le contrôle de login est aussi centralisé dans ce fichier index.php.)
Le script index.php ainsi défini centralise l'affichage et une partie du traitement des pages du site. Avantage supplémentaire : modifier totalement la présentation du site pourra se faire en modifiant seulement le fichier index.php (adaptation simplifiée du pattern Front Controller).

Barre de menus

Dans le fichier menubar.inc.php contenant la barre de menus remplacer tous les liens de la forme
<a href="view/x.php">
par :
<a href="index.php?content=view/x.inc.php">

Cas d'utilisation "Voir tous les tickets"

Pour afficher la page affichant tous les tickets, il suffit désormais d'utiliser l'URL

 http://localhost/tpdsi/tp7/index.php?content=view/showAllTickets.inc.php

Noter la présence de .inc dans showAllTickets.inc.php. Ce fichier est une version édulcorée du fichier showAllTickets.php (il ne contient pas de balises <html>, <head>, <body>, ... et n'inclut pas le fichier d'en-tête header.inc.php et le fichier loginAccess.inc.php).

//  showAllTickets.inc.php
<h2>Liste de tous les tickets ouverts</h2>
...


Par souci de compatibilité ascendante, on pourra redéfinir showAllTickets.php de la façon suivante:

// showAllTickets.php incluant showAllTickets.inc.php
<?php
session_start();
?>
<?php include_once dirname(__FILE__) . '/../login/loginAccess.inc.php'; ?>
<?php
$pageId = 'Voir tous les tickets';
include 'header.inc.php';
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
  <title>YAHD - Voir tous les tickets</title>
  <link rel="stylesheet" href="../../styles/styles.css" type="text/css" media="screen">
</head>
<body>

<?php
include_once 'showAllTickets.inc.php';
?>

<?php include 'footer.inc.php'; ?>

</body>
</html>

Cas d'utilisation "Créer un ticket"

Pour créer un ticket, il vaut mieux désormais utiliser l'URL

 http://localhost/tpdsi/tp7/index.php?content=view/editTicket.inc.php

Réécrire editTicket.inc.php et editTicket.php en utilisant la même technique que dans la section précédente.
Remplacer la valeur du champ action du formulaire du fichier editTicketForm.php par index.php?content=view/editTicketAction.php (au lieu de editTicketAction.php).

Arborescence finale

Voici une vue des répertoires et fichiers attendus (respectez la casse de leurs noms) :
Répertoires et fichiers du tp7

Tests

Valider vos développements en exécutant les tests unitaires et les tests de recette fournis.