X

Parlons technique !

Au (re)lancement de ce site, j’avais annoncé que je ne parlerais pas de technique sur cet espace… Malheureusement, étant passionné par le développement WEB et étant actuellement en période de forte productivité sur des projets personnels motivants, l’envie m’a repris de détailler un peu mes projets et le travail associé ! Alors, voici un premier post technique !

Le projet

Si vous me connaissez, ou si vous avez regardé la page C.V., vous avez sûrement entendu parler de l’activité de mon père (qui est apiculteur) et du site associé : Didapi.fr. J’ai développé ce site en tant que cadeau d’anniversaire de mon père, pour ses 50 ans. Le temps ayant passé depuis, j’ai décidé lors de mon retour du Canada de me remettre à l’ouvrage, afin de faire de son site un espace d’application de mes connaissance en développement Front-end et de test de technologies.

L’objectif

Le résultat souhaité est une combinaison d’un ensemble de pages statiques, constitutives de la partie vitrine du site, et d’un système d’administration personnalisé, taillé en fonction des besoins de mon père, et lui permettant de gérer l’évolution de son cheptel apicole et de suivre les récoltes, les traitements et les transhumances.

Pour la partie vitrine, rien de plus simple: quelques pages HTML, améliorées par Twitter Bootstrap, saupoudrées de PHP et de JavaScript pour quelques fonctionnalités funky.

Pour la partie administration, j’ai longuement réfléchis. Mes conclusions ont été qu’il me fallait une combinaison de pages et de webservices écrit en PHP, manipulant des données stockées dans une base MySQL. Voulant rendre ces pages dynamiques, j’utiliserais les capacités de JQuery en AJAX pour ce qui est des appels aux webservices, implémentant eux les fonctions de sauvegarde et de récupération de données.

Les étapes

La première étape pour moi a été d’explorer la bibliothèque Twitter Bootstrap, afin de proposer à mon père un design de base pour les différentes pages statiques du site.

La seconde étape, plus longue et ardue, a été de développer mes compétences en JavaScript, JQuery et écriture de webservices en PHP pour réaliser une première implémentation des fonctionnalités souhaitées de gestion de cheptels. Je me suis à ce moment légèrement facilité le travail en utilisant un ORM très léger et facile d’utilisation, RedBean PHP.

Je présenterais dans un prochain billet l’architecture de l’information de ce projet via un diagramme de classes

Suite au développement d’un premier prototype, que je dois actuellement tester et faire valider par mon père, j’ai hérité d’un énorme tas de “code spaghetti” dont je ne suis pas très fier. Cette étape m’a pourtant permis de définir précisément mes besoins et les futures orientations techniques du projet.

Les futures orientations

Le code produit, ainsi que je l’ai déjà dit, pouvait être alors qualifié de “code spaghetti”. Il violait ainsi quelques bonnes pratiques fondamentales du développement informatique, que je présente dans les paragraphes suivants. Pour ma défense, j’ai travaillé sur cette première version avec un principe énoncé par un ancien Team Lead avec lequel j’ai travaillé à Montréal, qui se résumait ainsi: “Make it work, make it clean, make it fast”. Je suis donc actuellement dans la deuxième étape de ce processus, le “make it clean” !

Separation Of Concerns

Ce principe à prendre en compte lors de la réalisation d’une architecture logicielle suggère de garder à l’esprit la séparation des fonctionnalités au sein d’une application en modules ne se chevauchant pas et ne créant ainsi pas de redondances.

Single Responsibility Principle

Le principe de responsabilité unique demande, en programmation orienté objet, à valider que chacune des classes d’une application n’implémente qu’une responsabilité et n’exécute des opérations ne tombant que dans le champ de cette responsabilité, qui doit être proprement et entièrement implémenté au sein de la-dite classe.

Le respect de ce principe dans nos développements facilite l’application du principe de Separation of concerns précédemment présenté.

DRY: Don’t Repeat Yourself

Comme son nom l’indique, ce principe va à l’encontre d’une habitude de facilité de développeur débutant, qui est de copier-coller partout un code implémentant le comportement souhaité de manière à gagner en vitesse. En POO, la réalité est qu’un coper-coller signifie qu’une factorisation est possible, et il vaut toujours mieux prendre le temps d’appliquer cette factorisation que de répéter dans n fichiers un bout de code. en OOP, plusieurs concepts nous permettent de respecter le DRY, tels que l’implémentation de Helpers (classes comme méthodes), l’héritage de classes, ou encore le polymorhpisme.

Illustrations de ces orientations

Pour illustrer mes propos, je vais vous présenter un code que j’ai remis en ordre récemment, ainsi que la manière dont je l’ai fais évolué.

Le code de base

Le code suivant est celui d’un webservice implémenté en PHP (appelé par AJAX au sein d’autres pages de l’application)

[code language=”php”] <?php require_once(‘Mail.php’); include_once(‘../../globals.php’); require(‘../../library/RedBeanORM/rb.php’); $response = array(); if($_SERVER[‘REQUEST_METHOD’] == ‘POST’) { // if form has been posted process data $data = array( ‘name’ => convertAsSafeString($_POST[‘name’]), ‘firstname’ => convertAsSafeString($_POST[‘firstname’]), ’email’ => convertAsSafeString($_POST[’email’]), ‘phone’ => convertAsSafeString($_POST[‘phone’]), ‘comment’ => convertAsSafeString($_POST[‘comment’]) ); // always return true if you save the contact data ok or false if it fails $response[‘status’] = saveContact($data) ? ‘success’ : ‘error’; $response[‘message’] = $response[‘status’] ? ‘Votre message a bien été sauvegardé!’ : ‘Il y a eu un problème lors de la sauvegarde du message.’; header(‘Content-type: application/json’); echo json_encode($response); exit; } function convertAsSafeString($string) { $result = null; if (isset($string)) { $result = htmlspecialchars($string); } return $result; } function saveContact($data) { $success = false; R::setup(‘mysql:host=’ . Database::HOST . ‘;dbname=’ . Database::NAME, Database::USERNAME, Database::PASSWORD); $contact = R::dispense(‘contact’); $contact->name = utf8_encode($data[‘name’]); $contact->firstname = utf8_encode($data[‘firstname’]); $contact->email = utf8_encode($data[’email’]); $contact->phone = $data[‘phone’]; $contact->comment = utf8_encode($data[‘comment’]); $id = R::store($contact); SendContactMail($contact); if ($id != null) { $success = true; } return $success; } function SendContactMail($contact) { $from = $contact->email; $to = “”; $subject = “Contact”; $body = $contact->comment; $host = “”; $username = “”; $password = “”; $headers = array(‘From’ => $from, ‘To’ => $to, ‘Subject’ => $subject); $smtp = Mail::factory(‘smtp’, array( ‘host’ => $host, ‘port’ => ’25’, ‘auth’ => true, ‘username’ => $username, ‘password’ => $password ) ); $mail = $smtp->send($to, $headers, $body); if (PEAR::isError($mail)) { echo (“<p>” . $mail->getMessage() . “</p>”); } else { echo (“<p>Message successfully sent</p>”); } } ?> [/code]

Analyse du code

Que fait ce code ? Il vérifie que la requête est de type POST (envoi d’un formulaire): [code language=”php”] if($_SERVER[‘REQUEST_METHOD’] == ‘POST’) [/code] Transforme les informations reçues en variables safe: [code language=”php”] $data = array( ‘name’ => convertAsSafeString($_POST[‘name’]), ‘firstname’ => convertAsSafeString($_POST[‘firstname’]), ’email’ => convertAsSafeString($_POST[’email’]), ‘phone’ => convertAsSafeString($_POST[‘phone’]), ‘comment’ => convertAsSafeString($_POST[‘comment’]) ); function convertAsSafeString($string) { $result = null; if (isset($string)) { $result = htmlspecialchars($string); } return $result; } [/code] Se connecte à une base de données: [code language=”php”] R::setup(‘mysql:host=’ . Database::HOST . ‘;dbname=’ . Database::NAME, Database::USERNAME, Database::PASSWORD); [/code] Enregistre les informations dans la base de données via un objet fournis par RedBean PHP: [code language=”php”] $contact = R::dispense(‘contact’); $contact->name = utf8_encode($data[‘name’]); $contact->firstname = utf8_encode($data[‘firstname’]); $contact->email = utf8_encode($data[’email’]); $contact->phone = $data[‘phone’]; $contact->comment = utf8_encode($data[‘comment’]); $id = R::store($contact); [/code] Envoie un mail récapitulatif à l’administrateur: [code language=”php”] SendContactMail($contact); function SendContactMail($contact) { $from = $contact->email; $to = “postmaster”; $subject = “Contact”; $body = $contact->comment; $host = “”; $username = “”; $password = “”; $headers = array(‘From’ => $from, ‘To’ => $to, ‘Subject’ => $subject); $smtp = Mail::factory(‘smtp’, array( ‘host’ => $host, ‘port’ => ’25’, ‘auth’ => true, ‘username’ => $username, ‘password’ => $password ) ); $mail = $smtp->send($to, $headers, $body); if (PEAR::isError($mail)) { echo (“<p>” . $mail->getMessage() . “</p>”); } else { echo (“<p>Message successfully sent</p>”); } } [/code] Et renvoie la réponse en format JSON: [code language=”php”] // always return true if you save the contact data ok or false if it fails $response[‘status’] = saveContact($data) ? ‘success’ : ‘error’; $response[‘message’] = $response[‘status’] ? ‘Votre message a bien été sauvegardé!’ : ‘Il y a eu un problème lors de la sauvegarde du message.’; header(‘Content-type: application/json’); echo json_encode($response); exit; [/code]

Dans ce code PHP, nous voyons bien que le principe de Single Responsibility n’est pas respecté. De plus, ce squelette de code avait été répété à outrance pour chacun des objets que mon système d’administration nécessitait. Donc le DRY n’était pas non plus respecté.

La solution adoptée

Ce simple morceau de code spaghetti m’a forcé à revoir une grande partie de ma stratégie de sauvegarde. En effet, j’ai décidé de mettre en place une architecture réellement orientée objet. Pour cela, j’ai tout d’abord implémenté une classe de base, que j’ai nommé Entity: [code language=”php”] <?php require_once __DIR__ . ‘/../library/RedBeanORM/rb.php’; /** * * Entity: Base class of all objects stored in the database I will use * in this project. * Mapped to database by RedBeanPHP ORM. * **/ class Entity { protected $_id; protected $_entity; // Base Method to be used for saving to Database purpose public function SaveEntity($post, $entityName) { $success = false; R::setup(‘mysql:host=’ . Database::HOST . ‘;dbname=’ . Database::NAME, Database::USERNAME, Database::PASSWORD); $this->_entity = R::dispense($entityName); foreach($post as $key) { if (isset($_POST[$key])) { $this->_entity->setAttr($key, Utility::ConvertAsSafeString($_POST[$key])); } } $this->_id = R::store($this->_entity); if ($this->_id != null) { $success = true; } return $success; } } [/code]

Cette classe implémente une méthode de sauvegarde, qui reçoit par la variable $post un tableau contenant les noms des champs des formulaires à utiliser, et le nom de l’entité à sauvegarder.

J’ai ensuite implémenté une classe Contact.php, héritant de la classe Entity, dont le code est le suivant:

[code language=”php”] <?php require_once ‘Mail.php’; require_once __DIR__ . ‘/entity.php’; /** * * Contact: Class derived from Entity used to manipulate contact information * send through the contact form * **/ class Contact extends Entity { public function SaveEntity($post) { $success = false; $success = parent::SaveEntity($post, ‘contact’); $this->SendContactMail(); return $success; } private function SendContactMail() { $from = $this->_entity->email; $to = “”; $subject = “Contact”; $body = $this->_entity->comment; $host = “”; $username = “postmaster”; $password = “”; $headers = array(‘From’ => $from, ‘To’ => $to, ‘Subject’ => $subject); $smtp = Mail::factory(‘smtp’, array( ‘host’ => $host, ‘port’ => ’25’, ‘auth’ => true, ‘username’ => $username, ‘password’ => $password ) ); $mail = $smtp->send($to, $headers, $body); if (PEAR::isError($mail)) { echo (“<p>” . $mail->getMessage() . “</p>”); } else { echo (“<p>Message successfully sent</p>”); } } } ?> [/code]

Cette classe Contact surcharge la méthode de sauvegarde de l’entité, en appelant la méthode de la classe parente et en ajoutant seulement un appel à une fonction private effectuant l’envoi du mail récapitulatif.

J’ai également implémenté une classe statique nommée Utility possédant la méthode ConvertAsSafeString() remplaçant celle préalablement répétée dans tous mes webservices.

[code language=”php”] <?php /** * * Utility class used to operate methods on string to ensure * the manipulated data are safe * **/ class Utility { // Method to convert as safe string to save in a DB public static function ConvertAsSafeString($string) { $result = null; if (isset($string)) { $result = htmlspecialchars($string); } return $result; } } ?> [/code]

La dernière étape était finalement de revenir sur le fichier save_contact.php et de remplacer le code existant par un appel à ces différentes classes, permettant de respecter les bonnes pratiques évoquées plus haut !

[code language=”php”] <?php require_once(‘Mail.php’); include_once(‘../../globals.php’); $response = array(); if($_SERVER[‘REQUEST_METHOD’] == ‘POST’) { // if form has been posted process data //Instanciates new Contact entity to be able to use the save capability of this object $post = array(‘name’, ‘firstname’, ’email’, ‘phone’, ‘comment’); $contact = new Contact(); // always return true if you save the contact data ok or false if it fails $response[‘status’] = $contact->SaveEntity($post) ? ‘success’ : ‘error’; $response[‘message’] = $response[‘status’] ? ‘Votre message a bien été sauvegardé!’ : ‘Il y a eu un problème lors de la sauvegarde du message.’; header(‘Content-type: application/json’); echo json_encode($response); exit; } ?> [/code]

Comme vous pouvez le voir, un code absolument spaghetti a finalement été remplacé par un code clair, dont l’action principale est d’instancier un objet de type Contact, et de le sauvegarder en base de données à partir des informations envoyées par le formulaire.

 

Et maintenant ?

L’implémentation de ce code, ainsi que d’autres fonctions alambiquées que j’évoquerais dans de futurs billets (dont la mise en place d’un autoloading) m’ont motivées à imaginer l’implémentation de mon propre framework PHP expérimental. Non dans une idée de célébrité ni de contribution exceptionnelle au monde de l’open-source, mais dans l’optique de continuer à “diver in” le fonctionnement de PHP et les possibilités qu’offrent ces facettes orienté objet.

Que pensez-vous de mes avancées, des concepts que je tente d’appliquer et de ma manière de présenter les choses ?

balessan: