Gestion des utilisateurs en PHP5 via l’utilisation de patterns
Concepts objets pour gérer ses utilisateurs via Singleton et Factory PHP
Ca faisait un petit moment que je ne rédigeais plus trop d’articles sur PHP, il est vrai que Python me prend pas mal de temps ces jours-ci, alors j’ai décidé de sortir mon Apache et ma DB préférée du sommeil pour voir un peu comment gérer dans une application ses utilisateurs. Bon comme d’habitude, je préviens, tout ça est le mix dans mon cerveau de nombreuses documentations ainsi que d’expériences personnelles, donc il se peut que la méthode que je vais présenter comprenne des erreurs et peut sans doute être améliorée.
Pour moi la grande force de PHP5 est sa partie objet, qui même si elle n’est pas encore parfaite, nous permet de travailler avec de nombreux design patterns (motif de conception) plutÃŽt intéressants. Dans un précédent article j’ai essayé de démontrer l’utilité des registres pour la conservation
et le transfert d’objet dans toutes les couches d’une application, ce soir je vais présenter le bien connu Singleton ainsi que l’excellente Factory.
Gestion des utilisateurs
Dans notre application, un utilisateur enregistré pourra soit être administrateur soit être invité. Un invité est un utilisateur de base, il a le droit en lecteur seul. Un administrateur lui, a le droit de lecture, écriture, modification. L’application va donc se découper en deux parties, la premiÚre sera la gestion des utilisateurs via le pattern Factory, la seconde sera la gestion de la base de donnée via le pattern Singleton.
Concept du Singleton
Le Singleton, est un des motifs de conception les plus connus, et un des plus pratiques. Il a sa place dans toutes sortes de classes. Il permet d’instancier une seule fois la classe ainsi dans toutes les couches de notre application on retrouvera la même instance. Pour ça, il utilise un constructeur privé qui sera appelé par une méthode public souvent nommée par convention “getInstance” celle-ci va mettre en attribut l’instance retournée par le constructeur, ainsi au nouvel appel de “getInstance” si notre objet a déjà été créée il le retourne, sinon il le créée. Le principe est assez simple ou plutÃŽt ingénieux ! seulement j’explique pas trÚs bien (; …un bout de code devrait nous éclairer.
-
class unSingleton
-
{
-
private static $_instance;
-
public static getInstance ()
-
{
-
if (is_null (self::$_instance)) {
-
self::$_instance = new unSingleton ();
-
}
-
}
-
private function __construct () {}
Concept de la Factory
Donc la Factory …ce pattern sert d’intermédiaire, son but est de retourner à notre application un objet du type qu’on souhaite. Pour ça, on lui passe en paramÚtre un ou plusieurs arguments, et via la définition de classe “modÚle” elle retournera l’objet adéquat. Cette fois-ci je vais plutÃŽt vous rediriger ver wikipedia, car ce pattern demande un peu plus de code, et on va l’utiliser pour gérer les utilisateurs.
Table utilisateur
Voila la table chargée de stocker les utilisateurs en base de données
-
CREATE TABLE users (
-
id serial,
-
username varchar (40),
-
password varchar (40),
-
usrlevel bool,
-
PRIMARY KEY (id)
-
);
Les utilisateurs
Pour la gestion des utilisateurs, nous allons donc utiliser une Factory, dans un premier temps on définit la classe abstraite qui servira de modÚle aux autres classes, qui dériverons d’elle.
La classe User
-
/**
-
* ModÚle Utilisateur
-
*/
-
abstract class User
-
{
-
private $_username;
-
private $_password;
-
private $_usrlevel;
-
-
public function __construct ($usr, $pwd, $lvl)
-
{
-
$this->_username = $usr;
-
$this->_password = $pwd;
-
$this->_usrlevel = $lvl;
-
}
-
-
public function canRead () { return true; }
-
public function canWrite () { return false; }
-
public function canModify () { return false; }
-
-
public function getUsername () { return $this->_username; }
-
public function getPassword () { return $this->_password; }
-
public function getUsrlevel () { return $this->_usrlevel; }
-
}
Maintenant ses dérivés
-
/**
-
* Invité
-
*/
-
class GuestUser extends User {}
-
-
/**
-
* Administrateur
-
*/
-
class AdminUser extends User
-
{
-
public function canRead () { return true; }
-
public function canWrite () { return true; }
-
public function canModify () { return true; }
-
}
Nous disposons maintenant de tous les éléments nécessaires pour créer notre Factory.
-
/**
-
* Retourne un objet de type User
-
* en fonction de son niveau
-
*/
-
class UserFactory
-
{
-
public static function getUserFactory ($usr, $pwd, $lvl)
-
{
-
if ($lvl == 0)
-
return new AdminUser ($usr, $pwd, $lvl);
-
if ($lvl == 1)
-
return new GuestUser ($usr, $pwd, $lvl);
-
}
-
}
Donc, en fonction d’un identifiant, d’un mot de passe ainsi que d’un niveau d’autorisation, notre Factory nous retournera un objet GuestUser, ou un objet AdminUser.
Nous allons à présent définir notre classe qui fera l’intéraction avec la base de données en utilisant le pattern Singleton. Celle-ci devra être capable d’enregistrer un utilisateur, ainsi que de retourner un utilisateur déjà enregistré. Pour l’enregistrement nous utiliserons la méthode “addUser” qui demandera en paramÚtre un objet de type User, ensuite pour la restauration d’un utilisateur, nous utiliserons la méthode “getUser” qui demandera en paramÚtre un identifiant ainsi qu’un mot de passe, celle-ci retournera grâce à notre Factory un objet de type User, soit Admin soit Invité.
-
/**
-
* Gestion des utilisateurs avec la db
-
*/
-
class UserAuthSingleton
-
{
-
private static $_instance;
-
-
private $_dbh;
-
private $_tbl;
-
-
/**
-
* Retourne une l’instance de la classe */
-
public static function getInstance ()
-
{
-
if (is_null (self::$_instance)) {
-
self::$_instance = new UserAuthSingleton ();
-
}
-
return self::$_instance;
-
}
-
-
/**
-
* Constructeur. */
-
private function __construct ()
-
{
-
$this->_dbh = new PDO (‘pgsql:host=localhost;dbname=users_db’, ’sahid’, ‘test’);
-
$this->_dbh->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
$this->_tbl = "users";
-
}
-
-
/**
-
* Ajoute un utilisateur. */
-
public function addUser (User $u)
-
{
-
if ($this->userExist ($u->getUsername ())) {
-
throw new Exception ("Le nom d’utilisateur exist déjà ");
-
}
-
else {
-
$sql = "
-
insert into {$this->_tbl} (username, password, usrlevel)
-
values (:usr, :pwd, :lvl);";
-
-
$stmt = $this->_dbh->prepare ($sql);
-
$stmt->execute (array (":usr" => $u->getUsername (),
-
":pwd" => $u->getPassword (),
-
":lvl" => $u->getUsrlevel ()));
-
}
-
}
-
-
/**
-
* Retourne un utilisateur. */
-
public function getUser ($usr, $pwd)
-
{
-
$sql = "
-
select username, password, usrlevel
-
from {$this->_tbl}
-
where username = :usr and password = :pwd;";
-
-
$stmt = $this->_dbh->prepare ($sql);
-
$stmt->bindParam (":usr", $usr);
-
$stmt->bindParam (":pwd", sha1 ($pwd));
-
$stmt->execute ();
-
-
// Retourne un objet User en fonction du level
-
if ($stmt->rowCount () == 1) {
-
$row = $stmt->fetchAll ();
-
$user = UserFactory::getUser ($row[0][‘username’],
-
$row[0][‘password’],
-
$row[0][‘usrlevel’]);
-
return $user;
-
}
-
else
-
return false;
-
}
-
-
public function userExist ($usr)
-
{
-
$sql = "
-
select username, password
-
from {$this->_tbl}
-
where username = :usr";
-
-
$stmt = $this->_dbh->prepare ($sql);
-
$stmt->bindParam (":usr", $usr);
-
$stmt->execute ();
-
-
if ($stmt->rowCount () > 0)
-
return true;
-
else
-
return false;
-
}
-
}
Testons tout ça
Création d’un nouvel utilisateur
-
$user = UserFactory::getUserFactory ("sahid", sha1 ("foobar"), 0);
-
-
try {
-
UserAuthSingleton::getInstance ()->addUser ($user);
-
}
-
catch (Exception $e) {
-
echo $e->getMessage ();
-
}
Récupération d’un utilisateur
-
$user = UserAuthSingleton::getInstance ()->getUser ("sahid", "foobar");
-
if ($user instanceof AdminUser)
-
echo $user->getUsername ()."est admin !";
-
if ($user instanceof GuestUser)
-
echo $user->getUsername ()."est invité !";
En conclusion…
Voila comment peuvent être géré des utilisateurs avec un concept objet bien sur il peut y avoir bien plus de niveaux d’autorisation pour nos utilisateurs, et ceux-ci peuvent disposer de bien plus de propriétés, ensuite cette implémentation peut etre sans doute améliorée, le plus important étant la séparation des données.
Tableau de bord
Share and Enjoy
















19 juillet 2007 at 2:24
Parfait que dire de plus, comme d’habitude :)
3 septembre 2007 at 1:27
Bravo pour ce beau concept objet !
J’essaye maintenant d’adapter la class PDO en MySQLi et de faire __autoload($class_name) pour profiter à fond des nouveautés de PHP5 :) … et pour que ça fonctionne…
J’ai pas encore tout compris sans un beau diagramme UML , mais avec l’ingénierie inverse d’une classe de zend je crois, on devrait pouvoir faire une belle image en jpeg.
Dans tous les cas beau tuto.
JY
12 septembre 2007 at 11:33
Intéressant !
Ce qui me dérange un peu c’est le fait que tu aies choisi le même nom de méthode au sein de deux classes différentes==> getUser(). Mais c’est secondaire…
Merci pour ce tuto, il remet les idées en place.
ZjX
12 septembre 2007 at 17:30
ah voui c vrai, ca perd en clarté …
Au niveau de la factory j’aurais peut être du nommer ma méthode getUserFactory () ou autre …
surtout que jean yves n’a pas tort … un petit diagramme UML aurai été un plus… j’y penserai pour les prochains articles (;
13 septembre 2007 at 10:09
Encore une petite remarque ;)
Je démarre les patterns depuis peu et j’ai du mal à comprendre pourquoi une méthode “d’ajout d’utilisateur” (addUser) se trouve dans une classe d’authentification d’utilisateur “UserAuthSingleton” ?
Dans le cas de la séparation des traitements j’organiserai différement les méthodes pour ma part. Mais peut-être n’ai-je pas encore bien compris l’utilisation des patterns !! ^^’
Merci !
ZjX
13 septembre 2007 at 14:11
( ; … tu peux renommer la classe comme bon te semble.
UserAuthSingleton est l’intermédiaire entre ta base de données et un objet utilisateur. développer une classe pour l’ajout des informations utilisateur en db et retrait des informations me parait inutile, néanmoins libre à toi de le faire ( :
13 septembre 2007 at 17:11
Oui oui c’était bien dans ce sens que je considÚrais la chose.
Le renommage de la classe suffit.
a+
13 juillet 2008 at 11:43
Bonjour,
Peut-on utiliser un formulaire pour gerer les Utilisateurs ?
Si oui, comment ?
J’ai essayé, sans aucun succès.
Merci
Jaff
13 juillet 2008 at 11:48
Bonjour,
J’ai oublié de dire ceci :
Cette ligne de code ne fonctionne pas avec ma configuration :
66 # $user = UserFactory::getUser ($row[0][‘username’],
Il me faut ajouter getUserFactory :
$user = UserFactory::getUserFactory ($row[0]['username'],
Merci de me dire pourquoi ?
Jeff
14 décembre 2008 at 23:35
Bon tutoriel!