Précédent   Forum des professionnels en informatique > PHP > Langage > Contribuez
Contribuez Proposez vos articles, cours, tutoriels, FAQ, sources, etc. pour PHP
Partagez cette discussion sur d'autres réseaux sociaux : Viadeo Twitter Google Facebook Digg Delicious MySpace Yahoo
Réponse Proposer ce sujet en actualité
 
Outils de la discussion
Publicité
'
Vieux 24/02/2011, 12h34   #1
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 014
Points : 5 014
Par défaut [PHP 5.3] Singleton Factory

Bonjour à tous.

Ca fait un moment que je joues avec les singletons en PHP 5.3. Des singletons simples qui portent leur propre instance aux singletons factories en passant par des singletons génériques à hériter (merci le late static binding), j'ai à peu près fait le tour.

Il y a 1 semaine, je suis tombé sur un singleton factory remarquablement ingénieux fait par Stealth35. Ce singleton permet l'usage de cette syntaxe:
Code :
1
2
3
4
 
Singleton::PDO($dsn, $user, $pwd);
// ...
Singleton::PDO()->query($query);
Pratique non ?

Voici l'implémentation originale du Singleton:
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
 
final class Singleton {
 
	private static $_instances;
 
	public static function __callStatic ($name, $args) {
		if (!isset(static::$_instances[$name]) || !empty(args)) {
			$class = new ReflectionClass($name);
			static::$_instances[$name] = $class->newInstanceArgs($args);
		}
		return static::$_instnaces[$name];
	}
}
Cette classe très simple fonctionne très bien mais à deux défauts bien gênants:
- en PHP inférieur à 5.3.4, si l'objet qu'on essaie d'instancier n'a pas de constructeur ou que son constructeur ne reçoit pas de paramètres (SplObjectStorage par exemple) ReflectionClass lèves une ReflexionException lors de l'appel de ReflectionClass::newInstanceArgs()
- il n'est pas possible (ou difficilement / salement) d'instancier des classes d'un autre namespace que global

De plus, ReflectionException semble incappable d'utiliser l'autoloading (et vu que cette classe n'a pratiquement pas de documentation...)

C'est pourquoi je me suis permi de l'améliorer légèrement:
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
 
final class Singleton extends Object
{
    /**
     * Singleton's instances
     * @var object
     */
    private static $_instances;
 
    /**
     * Let the singleton use any namespaced
     * class.
     * @var string
     */
    private static $_current_namespace = '';
 
    /**
     * Use auotloader flag
     * @var boolean
     */
    private static $_autoload = false;
 
    /**
     * __callStatic overloading.
     *
     * Allow the use of the factory.
     * E.G.
     * > Singleton::PDO($dsn, $user, $pwd);
     *
     * @param string $name
     * @param array $arguments
     * @return object
     */
    public static function __callStatic($name, array $args) {
        $name = static::$_current_namespace . '\\' . $name;
        if(empty(static::$_instances[$name]) || !empty($args)) {
            if (class_exists($name, static::$_autoload)) {
                $class = new ReflectionClass($name);
                static::$_instances[$name] = !empty($args) ? $class->newInstanceArgs($args) : $class->newInstance();
            }
            else
                throw new RuntimeException("Class $name not found");
        }
        return static::$_instances[$name];
    }
 
    /**
     * Sets the namespace to be used
     * @param string $ns = ''
     * @return void
     */
    public static function setNamespace ($ns = '') {
        if (strpos($ns, '\\') === 0) $ns = substr($ns, 1);
        static::$_current_namespace = $ns;
    }
 
    /**
     * Enable the Singleton to use autoloader
     * @return void
     */
    public static function enableAutoload () {
        static::$_autoload = true;
    }
 
    /**
     * Do not let the Singleton to use autoloader
     * @return void
     */
    public static function disableAutoload () {
        static::$_autoload = false;
    }
}
Ainsi, on peut désormais changer le namespace à utiliser pour créer une nouvelle instance de singleton et laisser l'autoloader charger pour nous les classes dynamiquement.

E.G.
Code :
1
2
3
4
5
 
Singleton::setNamespace('App\core');
Singleton::enableAutoload();
Singleton::MockObject('test'); // charge \App\Core\MockObject
Singleton::MockObject()->test = "hello";
J'attends vos retours, et plus particulièrement les tiens Stealth.
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 13h22   #2
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
je vais regardé ca plus en detail, sinon je l'ai mise sur github : https://github.com/stealth35/SingletonFactory

__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 13h50   #3
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 014
Points : 5 014
Si tu mets la classe modifié je veux être dans le @author après toi

Tiens au passage, si tu utilise PHPUnit, voilà ma classe de tests:
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 
include_once 'libraries/cobalt/core/Object.class.php';
include_once 'libraries/cobalt/core/Autoloader.class.php';
include_once 'libraries/cobalt/util/Singleton.class.php';
 
use cobalt\util\Singleton;
use cobalt\core\Autoloader;
 
class TestSingleton extends PHPUnit_Framework_TestCase {
 
    public function testNewInstance () {
        $this->assertNotEmpty(Singleton::stdClass());
        $instance = Singleton::stdClass();
        $this->assertNotEmpty($instance);
        $this->assertEquals($instance, Singleton::stdClass());
    }
 
    /**
     * @depends testNewInstance
     * @expectedException RuntimeException
     */
    public function testNewInstanceException () {
        $instance = Singleton::foobar();
    }
 
    /**
     * @depends testNewInstance
     * @expectedException ReflectionException
     */
    public function testNewInstanceArgsException () {
        $instance = Singleton::stdClass('a','b','c');
    }
 
    /**
     * @depends testNewInstance
     */
    public function testNamespacedInstance () {
        Autoloader::add("D:\workspace_helios\cobalt\libraries");
        Autoloader::register();
        Singleton::setNamespace("cobalt\core");
        Singleton::enableAutoload();
 
        $instance = Singleton::MockObject();
        $this->assertNotEmpty($instance);
        $this->assertEquals('cobalt\core\MockObject', $instance->getClass());
    }
}
Tous ces tests sont fonctionnels mais je n'ai pas fini d'éplucher tous les cas d'erreur.

Une autre amélioration possible serait de retourner la référence et pas l'objet pour gagner en performances mais ce n'est pas possible avec __callStatic visiblement (j'ai écrit une note là dessus après avoir fait des tests: http://php.net/manual/en/language.references.return.php). Une idée ?
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 13h58   #4
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
Citation:
Envoyé par Benjamin Delespierre Voir le message
Si tu mets la classe modifié je veux être dans le @author après toi
c'est fait
je mettrai tout les docbooks plus tard
__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 14h02   #5
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 014
Points : 5 014
Pour le pb de la référence tu as une idée ?
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 15h08   #6
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
Citation:
Envoyé par Benjamin Delespierre Voir le message
Pour le pb de la référence tu as une idée ?
en php 5 tout objet est passer en référence

EDIT : t'es sur du : class_exists($name, static::$_autoload)
ton static::$_autoload est true ou false non ?
__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 15h12   #7
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 014
Points : 5 014
En effet:
Code :
1
2
3
4
5
6
7
8
9
10
11
 
class Test {
   public $value = 42;
}
 
$test = Singleton::Test();
var_dump($test);
$test->value = 55;
var_dump(Singleton::Test());
Singleton::Test()->value = 42;
var_dump($test);
Donc ça c'est bon...

C'est unit testé et ça fonctionne. Le paramètre booléen sert à utiliser ou non l'autoloader dans class_exists.
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 15h21   #8
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
Citation:
Envoyé par Benjamin Delespierre Voir le message
C'est unit testé et ça fonctionne. Le paramètre booléen sert à utiliser ou non l'autoloader dans class_exists.
ah oui, mais ca sert pas a grand chose, puisque ReflectionClass ca déjà retourner l'exception

sinon avec spl_autoload_register ca marche bien aussi

Code :
1
2
spl_autoload_register();
Singleton::__callStatic('\DOM\Document', array());
les alias non par contre

Code :
1
2
3
spl_autoload_register();
use \DOM\Document as Document;
Singleton::Document();
__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 15h33   #9
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
visiblement y'a un bug... :

Code :
1
2
3
4
5
6
7
8
9
use \stdClass as object;
var_dump(new object);
var_dump(class_exists('object'));
 
/*
object(stdClass)#1 (0) {
}
bool(false)
*/
EDIT : j'ai rajouté dans le dépôt le support des namespaces via Singleton::$namespace
__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 18h32   #10
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 014
Points : 5 014
C'est un bug connu il me semble.
Personnellement je dis non aux alias de classes et de namespaces: Eclipse n'arrive pas à faire d'auto complétion dessus donc l'avantage de typographie est nul.

Citation:
Singleton::$namespace
Mmh j'aime tellement mieux quand les attributs sont protégés...

Citation:
sinon avec spl_autoload_register ca marche bien aussi
Oui visiblement l'autoloading à l'air de se faire malgré tout, c'est une erreur de ma part que j'ai corrigé.

On est pas trop mal avec ça

L'amélioration future devra supporter un mécanisme de persistence avec les patterns Strategy et Adapter et là ce serait gé-nial.

Maintenant, ta classe m'a donnée une idée pour faire une identity map sur le même principe... C'est globalement le même fonctionnement mais avec plusieurs instances disponibles d'une même classe, différenciés par leurs RTTI.
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 18h47   #11
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
Citation:
Envoyé par Benjamin Delespierre Voir le message
C'est un bug connu il me semble.
visiblement non, d'ailleurs j'ai eu le droit a un jolie "bogus" et basta...

Citation:
Envoyé par Benjamin Delespierre Voir le message
Maintenant, ta classe m'a donnée une idée pour faire une identity map sur le même principe... C'est globalement le même fonctionnement mais avec plusieurs instances disponibles d'une même classe, différenciés par leurs RTTI.
au début j'étais parti pour étendre SplObjectStorage et faire des clés spl_object_hash, mais je voulais faire le plus simple possible

EDIT : n'hésite pas à faire un fork sur github
__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 18h53   #12
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 014
Points : 5 014
Citation:
au début j'étais parti pour étendre SplObjectStorage, mais je voulais faire le plus simple possible
Tout à fait d'accord. Je me suis moi aussi attaqué au problème; le souci est que SplObjectStorage à beau être traversable, il est difficile de trouver un élément dedans sans itérer dessus. J'en suis arrivé à la conclusion que la seule façon de faire un dictionnaire de données en PHP est le bon vieux tableau associatif.

Ce que je ne comprends pas, c'est que SplObjectStorage autorise la création d'infos en regard de chaque objet inséré mais ne permet pas de retrouver ces objets à partir de ces informations. En d'autres termes, les objets sont les clés et les infos sont les valeurs, c'est assez confus pour moi...

En revanche c'est un bon candidat quand il s'agit de trouver une structure pour jeter des objets sans se soucier de pouvoir les récupérer par index comme avec le pattern Observer par exemple ou le dispatch se fait sur tous les objets de la collection d'observés.

Tu as déjà expérimenté le fait de lui coller un iterateur par dessus pour lui donner une "forme" ?
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 19h00   #13
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
non mais tu peux y accéder comme un array avec les []
__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 19h03   #14
Modérateur
 
Avatar de Benjamin Delespierre
 
Benjamin Delespierre
Développeur Web
Inscription : février 2010
Messages : 2 984
Détails du profil
Informations personnelles :
Nom : Benjamin Delespierre
Âge : 24
Localisation : France

Informations professionnelles :
Activité : Développeur Web
Secteur : High Tech - Opérateur de télécommunications

Informations forums :
Inscription : février 2010
Messages : 2 984
Points : 5 014
Points : 5 014
Citation:
non mais tu peux y accéder comme un array avec les []
C'est l'objet que tu dois connaitre pour ça.

Code :
1
2
3
4
5
6
7
8
9
10
 
$storage = new SplObjectStorage;
 
$o1 = (object)"hello";
$o2 = (object)"world";
 
$storage->attach($o1, 'hello');
$storage->attach($o2, 'world');
 
echo $storage[$o1] . ' ' . $storage[$o2];
C'est globalement l'inverse de ce qu'on veut d'habitude
__________________
A la recherche d'un framework MVC facile a prendre en main ? Essayez Axiom
Nouveau: la référence d'Axiom est disponible sur GitHub (je la peaufine en ce moment même).

Un problème correctement identifié est à moitié résolu, évitez de poster l'intégralité de votre code avec pour seule explication "ça ne marche pas...".
Pour identifier correctement vos problèmes PHP, utilisez la gestion des erreurs et xdebug.

Les boutons et existent, servez-vous en
Benjamin Delespierre est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 24/02/2011, 19h09   #15
Modérateur
 
Inscription : septembre 2010
Messages : 7 101
Détails du profil
Informations forums :
Inscription : septembre 2010
Messages : 7 101
Points : 8 465
Points : 8 465
ouai c'est dommage
__________________
http://blog.stealth35.com/
stealth35 est déconnecté   Envoyer un message privé Réponse avec citation 00
Réponse Proposer ce sujet en actualité
Outils de la discussion



Fuseau horaire GMT +2. Il est actuellement 16h34.


 
 
 
 
Partenaires

Hébergement Web