Présentation
Le constructeur, ou builder, permet de faciliter la création d'objets en s'abstrayant des données sources et des paramètres à passer pour construire les objets: Il va permettre d'externaliser le processus de création d'un objet.
Nous allons vous proposer deux exemples:
- le premier dans le cadre d'une mailing list
- le second dans le cadre d'un accès à une zone membre
Dans les deux cas, la création des objets métiers sera identique mais le processus de création de ces objets ne sera pas positionné au même endroit.
Haut de pageImplémentation du design pattern
L'implémentation d'un constructeur n'a rien de particulier en soit: elle doit juste vous faciliter la création d'objets
Par contre, les builders sont généralement uniques dans une application, et de telle sorte on utilise souvent le design pattern singleton pour les implémenter
Haut de pageSans ce design pattern
Nous nous basons sur classes User_Base et User_Db qui correspondent aux objets metiers traités
La classe User_Base définit les attributs de n'importe quel utilisateur:
<?php
/**
* $Id$
* @package Poo_Example
* @subpackage Builder
*/
if (!class_exists('User_Base')) {
/**
* Classe metier definissant un utilisateur
* @package Poo_Example
* @subpackage Builder
*/
class User_Base
{
/**
* @var string $_login Le login
* @access private
*/
protected $_login = '';
/**
* @var string $_password Le mot de passe
* @access protected
*/
protected $_password = '';
/**
* @var string $_mail Le mail
* @access protected
*/
protected $_mail = '';
/**
* Constructeur
* @access public
* @return void
*/
public function __construct()
{
}
/**
* Definit le login
* @param string $login Le login
* @return void
* @access public
*/
public function setLogin($login)
{
$this->_login = $login;
}
/**
* Retourne le login
* @return string
* @access public
*/
public function getLogin()
{
return $this->_login;
}
/**
* Definit le mot de passe
* @param string $password Le mot de passe
* @return void
* @access public
*/
public function setPassword($password)
{
$this->_password = $password;
}
/**
* Retourne le mot de passe
* @return string
* @access public
*/
public function getPassword()
{
return $this->_password;
}
/**
* Definit le mail
* @param string $mail Le mail
* @return void
* @access public
*/
public function setMail($mail)
{
$this->_mail = $mail;
}
/**
* Retourne le mail
* @return string
* @access public
*/
public function getMail()
{
return $this->_mail;
}
/**
* @access public
* @return string
*/
public function __toString()
{
$serial = "---------------------------\n";
$serial .= "login : ".$this->_login."\n";
$serial .= "password : ".$this->_password."\n";
$serial .= "mail: ".$this->_mail."\n";
$serial .= "---------------------------\n";
return $serial;
}
}
}
Et la classe User_Db etend la classe User_Base pour assurer les traitements relatifs à votre base de données:
<?php
/**
* $Id$
* @package Poo_Example
* @subpackage Builder
*/
if (!class_exists('User_Db')) {
/**
*
*/
if (!defined('__EXAMPLE_PATH__')) {
define('__EXAMPLE_PATH__', realpath(dirname(__FILE__).'/../'));
}
require_once __EXAMPLE_PATH__ . '/Autoload.php';
/**
* Classe utilisateur liee a la base de donnees
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @package Poo_Example
* @subpackage Builder
*/
class User_Db extends User_Base
{
const __ACCOUNT_LOCK_DURATION__ = 3600;
/**
* @var int $id
* @access protected
*/
protected $_id = null;
/**
* @var int $_wrongAttempt {var_wrong_login_attempt}
* @access private
*/
private $_wrongAttempt = 0;
/**
* @var mixed $db Connexion a la base de donnees de type PDO
* @access private
*/
private $_db = null;
/**
* Constructeur
* @param mixed $db : Connexion a la base de donnees de type PDO
* @access public
* @return void
*/
public function __construct(PDO $db)
{
$this->_db = $db;
parent :: __construct();
}
/**
* definit l'id
* @param int $id l'id a definir
* @return void
* @access public
*/
public function setId($id)
{
$this->id = $id;
}
/**
* recupere l'id
* @return int
* @access public
*/
public function getId()
{
return $this->id;
}
/**
* Verifie si l'utilisateur est valide
* @access public
* @return boolean
*/
public function isValid()
{
$isValid = false;
$sql = 'SELECT id, email, login_attempt FROM users WHERE password = :password AND';
$sql .= ' login = :login AND ((last_wrong_check + :lock_duration) < NOW())';
$sql .= ' LIMIT 0,1';
$stmt = $this->_db->prepare($sql);
try {
$stmt->bindParam(':password', $this->_password, PDO::PARAM_STR);
$stmt->bindParam(':login', $this->_login, PDO::PARAM_STR);
$stmt->bindParam(':lock_duration', self :: __ACCOUNT_LOCK_DURATION__,
PDO::PARAM_INT);
$stmt->execute();
}
if ($values = $stmt->fetch(PDO::FETCH_ASSOC)) {
$this->setId(intval($values['id']));
$this->setEmail(strval($values['email']));
$this->_wrongAttempt = intval($values['login_attempt']);
$isValid = true;
}
unset ($sql);
unset ($stmt);
return $isValid;
}
/**
* incremente compteur de tentatives de login
* @access public
* @return boolean
*/
public function addLoginAttempt()
{
$queryOk = false;
$sql = 'UPDATE users SET login_attempt = (login_attempt + 1),';
$sql .= 'last_wrong_check = NOW() WHERE login = :login LIMIT 1';
$stmt = $this->_db->prepare($sql);
try {
$stmt->bindParam(':login', $this->_login, PDO::PARAM_STR);
$stmt->execute();
}
if ($stmt->fetch(PDO::FETCH_ASSOC)) {
$queryOk = true;
$this->_wrongAttempt++;
}
unset ($sql);
unset ($stmt);
return $queryOk;
}
/**
* reinitialise le compteur de mauvaises connexions
* @access public
* @return boolean
*/
public function resetLoginAttempt()
{
$ok = true;
if ($this->wrong_attempt > 0) {
$ok = false;
$sql = 'UPDATE users SET Login_attempt = 0 WHERE login = :login LIMIT 1';
$stmt = $this->_db->prepare($sql);
try {
$stmt->bindParam(':login', $this->_login, PDO::PARAM_STR);
$stmt->execute();
}
unset ($stmt);
unset ($sql);
$ok = true;
}
return $ok;
}
public function lockAccount()
{
$ok = true;
$sql = "UPDATE users SET locked = 'YES' WHERE login :login LIMIT 1";
$stmt = $this->_db->prepare($sql);
try {
$stmt->bindParam(':login', $this->_login, PDO::PARAM_STR);
$stmt->execute();
}
unset ($stmt);
unset ($sql);
$ok = true;
}
/**
* @access public
* @return string
*/
public function __toString()
{
$serial = "---------------------------\n";
$serial .= "id : ".$this->_id."\n";
$serial .= "login : ".$this->_login."\n";
$serial .= "password : ".$this->_password."\n";
$serial .= "mail: ".$this->_mail."\n";
$serial .= "---------------------------\n";
return $serial;
}
}
}
Mailing List
Dans ce cadre, nous allons construire des objets metiers correspondants aux utilisateurs à partir des informations contenues en base de données. Nous obtiendrons une collection d'objets :
<?php
/**
* $Id$
* @package Poo_Example
* @subpackage Builder
*/
/**
*
*/
define('__EXAMPLE_PATH__', realpath(dirname(__FILE__).'/../../../'));
require_once __EXAMPLE_PATH__ . '/Autoload.php';
/**
* Script de mailing list n'utilisant pas le Constructeur
* @package Poo_Example
* @subpackage Builder
*/
$login = 'test_user';
$password = '5b2bSTCzbNTJjezj';
$database = 'test_builder';
$host = 'localhost';
$type = 'mysql';
/**
* Creation de l'objet base de donnees PDO
*/
$cc = new PDO("$type:dbname=$database;host=$host", $login, $password);
/**
* Recupere la liste des utilisateurs de la base de donnees
*/
$userList = array();
$sql = 'SELECT login, password, email FROM users';
$result = $cc->query($sql);
if ($cc->numRows($result) > 0) {
while ($user_record = $db->fetch_assoc($result)) {
$user = new User_Db($cc);
$user->setId($user_record['id']);
$user->setPassword($user_record['password']);
$user->setLogin($user_record['login']);
$user->setMail($user_record['mail']);
$userList[] = $user;
echo $user;
unset ($user);
}
}
// Votre traitement ici
?>
Soumission d'un formulaire d'authentification
Dans ce contexte, nous construisons un objet User_Db pour s'assurer que le couple login / mot de passe obtenu apres soumission d'un formulaire d'authentification est valide. Cela nécessite de contrôler et valider les données reçues pour les fournir à l'objet metier.
<?php
/**
* $Id$
* @package Poo_Example
* @subpackage Builder
*/
/**
*
*/
define('__EXAMPLE_PATH__', realpath(dirname(__FILE__).'/../../../'));
require_once __EXAMPLE_PATH__ . '/Autoload.php';
$login = 'test_user';
$password = '5b2bSTCzbNTJjezj';
$database = 'test_builder';
$host = 'localhost';
$type = 'mysql';
/**
* Script d'authentification n'utilisant pas le Constructeur
* @package Poo_Example
* @subpackage Builder
*/
if (count($_POST)>0) {
/**
* Creation de l'objet base de donnees PDO
*/
$cc = new PDO("$type:dbname=$database;host=$host", $login, $password);
/**
* Creation de l'objet metier User_Db
*/
$user = new Builder_User_Db($cc);
if (isset($_POST['login'])) {
$user->setLogin($_POST['login']);
}
if (isset($_POST['password'])) {
$user->setPassword($_POST['password']);
}
// Le couple login password est valide?
if ($user->isValid()) {
echo "Vous etes authentifie";
} else {
echo "Login et / ou mot de passe incorrect";
}
}
// Votre traitement ici
?>
Avec ce design pattern
Dans le cadre de l'utilisation du Constructeur, nous ne voulons pas connaitre les propriétés nécéssaires pour construire nos objets, de telle sorte que:
- Nous obtenions un objet User pour une ligne retournée suite à une requête SQL. Ceci nous permettra d'obtenir une collection d'utilisateur pour la mailing list
- Obtenir un objet User_Db après une soumission de formulaire, pour pouvoir etre inclus dans notre exemple de formulaire d'authentification.
- Comme nous n'avons besoin que d'une seul contructeur d'utilisateurs dans notre application, nous l'implémentons en singleton
<?php
/**
* $Id$
* @package Poo_Example
* @subpackage Builder
*/
if (!class_exists('User_Builder')) {
/**
*
*/
if (!defined('__EXAMPLE_PATH__')) {
define('__EXAMPLE_PATH__', realpath(dirname(__FILE__).'/../'));
}
require_once __EXAMPLE_PATH__ . '/Autoload.php';
/**
* Constructeur d'utilisateur
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @package Poo_Example
* @subpackage Builder
*/
class User_Builder implements Singleton_Interface
{
/**
* @var mixed $instance L'instance du constructeur
* @access private
* @static
*/
private static $_instance = null;
/**
* Retourne l'instance du constructeur
* @access public
* @static
* @return mixed
*/
public static function getInstance()
{
if (is_null( self :: $_instance)) {
self :: $_instance = new User_Builder();
}
return self :: $_instance;
}
/**
* Constructeur
* @access private
* @return void
*/
private function __construct()
{
}
/**
* surcharge La methode magique __clone
* @access public
* @return void
* @throws Exception
*/
public function __clone()
{
throw new Exception("Ne peut cloner un objet singleton");
}
/**
* Retourne un utilisateur par rapport a un enregistrement bdd
* @param array $record L'enregistrement
* @param PDO $db Connexion a la base de donnees de type PDO
* @access public
* @return mixed
* @see User_Db
*/
public function getUser($record, PDO $db)
{
$user = new User_Db($db);
if (isset($record['id'])) {
$user->setId(intval($record['id']));
}
if (isset($record['login'])) {
$user->setLogin(strval($record['login']));
}
if (isset($record['password'])) {
$user->setPassword(strval($record['password']));
}
if (isset($record['login_attempt'])) {
$user->setLoginAttempt(intval($record['login_attempt']));
}
return $user;
}
/**
* Retourne un utilisateur par rapport a un enregistrement bdd
* @param array $superGlobal L'enregistrement
* @param PDO $db Connexion a la base de donnees de type PDO
* @access public
* @return mixed
* @see User_Db
*/
public function getUserForAuthentification($superGlobal, PDO $db)
{
if (isset($superGlobal['id'])) {
unset($superGlobal['id']);
}
if (isset($superGlobal['login_attempt'])) {
unset ($superGlobal['login_attempt']);
}
return $this->getUser($superGlobal, $db);
}
}
}
Mailing List
Sans ce design pattern, vous avez pu voir que nous instancions les objets de la même façon avec des paramètres différents.
Dans le cas de son utilisation, les traitements réalisés sur les retours de requête sont moins nombreux et rendent plus clair votre code: un seul traitement est réalisé, celui d'appeler la méthode correspondante du Constructeur :
<?php
/**
* $Id$
* @package Poo_Example
* @subpackage Builder
*/
/**
*
*/
define('__EXAMPLE_PATH__', realpath(dirname(__FILE__).'/../../../'));
require_once __EXAMPLE_PATH__ . '/Autoload.php';
/**
* Version modifiee du script de mailing list utilisant Builder_User_Builder
* @package Poo_Example
* @subpackage Builder
*/
$login = 'test_user';
$password = '5b2bSTCzbNTJjezj';
$database = 'test_builder';
$host = 'localhost';
$type = 'mysql';
/**
* Creation de l'objet base de donnees PDO
*/
$cc = new PDO("$type:dbname=$database;host=$host", $login, $password);
/**
* Recupere la liste des utilisateurs de la base de donnees
*/
$userList = array();
$sql = 'SELECT login, password, email FROM users';
$result = $db->query($sql);
if ($cc->numRows($result) > 0) {
$userBuilder = User_Builder :: getInstance();
while ($user_record = $db->fetch_assoc($result)) {
$user = $userBuilder->getUserFromDbRecord($user_record, $cc);
$userList[] = $user;
echo $user;
unset ($user);
}
}
//Votre traitement ici
?>
Soumission de formulaire d'authentification
Là encore, vous ne vous souciez plus des contrôles à faire pour valider les données reçues du client : Tout est centralisé dans le code de votre Constructeur. Le code de votre script d'authentification s'en trouve allégé.
<?php
/**
* $Id$
* @package Poo_Example
* @subpackage Builder
*/
/**
*
*/
define('__EXAMPLE_PATH__', realpath(dirname(__FILE__).'/../../../'));
require_once __EXAMPLE_PATH__ . '/Autoload.php';
$login = 'test_user';
$password = '5b2bSTCzbNTJjezj';
$database = 'test_builder';
$host = 'localhost';
$type = 'mysql';
/**
* Version modifiee du script d'authentification utilisant Builder_User_Builder
* @package Poo_Example
* @subpackage Builder
*/
if (count($_POST)>0) {
/**
* Creation de l'objet base de donnees PDO
*/
$cc = new PDO("$type:dbname=$database;host=$host", $login, $password);
/**
* Creation de l'objet metier User_Db
*/
$user_builder = User_Builder :: getInstance();
$user = $user_builder->getUserFromFormSubmission($_POST, $cc);
// Le couple login password est valide?
if ($user->isValid()) {
echo "Vous etes authentifie";
//Votre traitement ici
} else {
echo "Login et / ou mot de passe incorrect";
}
} else {
// Votre traitement ici
}
?>
Conclusion
Les exemples présentés ne sont que de simples exemples d'utilisation de Constructeur. Vous pouvez notamment:
- Améliorer les contrôles réalisés sur la validation des données, ils sont ici très sommaires :)
- Dans le cas d'une connexion à une base de données, j'utilise un objet basé sur le modèle de PDO. N'importe quel autre implémentation aurait fait l'affaire.
Dans tous les cas, l'externalisation des contrôles et validation des données dans un Constructeur va vous assurer une uniformité des contrôles réalisés et une centralisation de votre code.
Haut de pageTélécharger les exemples
: 




