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
-
{
-
-
-
{
-
-
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 $_dbh;
-
private $_tbl;
-
-
/**
-
* Retourne une l’instance de la classe */
-
public
static function getInstance
()
-
{
-
-
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) {
-
-
}
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.