IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

Fonctionnement réel du pattern Singleton en php


Sujet :

Langage PHP

  1. #1
    Membre du Club
    Inscrit en
    Juin 2008
    Messages
    125
    Détails du profil
    Informations forums :
    Inscription : Juin 2008
    Messages : 125
    Points : 57
    Points
    57
    Par défaut Fonctionnement réel du pattern Singleton en php
    Bonsoir,

    Selon ce que j'ai du comprendre, le pattern Singleton a pour but, tout simplement, la création d'une instance unique. J'ai donc créé une classe Singleton possédant des méthodes statiques dans le cadre de l'adoption du pattern "Service Locator" (décrit ici).
    Le soucis que je rencontre est que plusieurs instances sont quand même créées pour ce Singleton !? comme si l'unicité ne concerne pas toute l'application php mais plutôt un fichier de script php !?
    Pour éviter de balancer tout le code que j'ai fait, je vais tenter de résumer mon problème comme ceci : j'ai 3 classes A, B et C. A est un singleton. B et C des classes de services.
    A possède un attribut statique $services auquel je rajoute des données via une méthodes A::registerService($pServiceName).
    Quand j'appelle dans B, A::registerService('appel_B'), le tableau $services contient bien 'appel_B'. Mais dès que j'appelle dans C, A::registerService('appel_C'), le tableau ne contient que 'appel_C' (au lieu de 'appel_B' et 'appel_C') !

    Désolé, si je ne suis pas assez clair ...

    Merci.

  2. #2
    Membre actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mai 2012
    Messages
    131
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Maroc

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mai 2012
    Messages : 131
    Points : 242
    Points
    242
    Par défaut
    Bonjour,

    il faut stocker ton tableau $services ds une session

  3. #3
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Contrairement à bon nombre de langages web serveur qui ont un runtime up en permanence, PHP tombe après avoir servi chaque requête, ce qui signifie que ton singleton n'est instancié que pour une requête (d'ou ton phénomène de multiplicité car plusieurs requêtes peuvent tout à fait être servies en parallèle). Si tu ne persiste pas ses informations, tu les aura perdu lors de la requête suivante.

    La nature de la persistance de ton instance va dépendre de son rôle:
    • s'il s'agit de données utilisateurs (comme par exemple un panier d'achat, unique par utilisateur mais chaque utilisateur en au un) alors il faut persister sur session
    • s'il s'agit de données commune à l'ensemble de l'application indépendament des utilisateurs (comme des paramètres de configuration) alors il faut effectuer une persitance en cache (sur fichier par exemple)
    • si on se fout pas mal du fait que le singleton dégage à chaque requête (comme une connexion à la base) alors on ne fait pas de persistance


    J'allais oublier: les singleton, c'est le meilleur moyen connu de l'homme pour introduire du couplage dans des composants.

  4. #4
    Membre du Club
    Inscrit en
    Juin 2008
    Messages
    125
    Détails du profil
    Informations forums :
    Inscription : Juin 2008
    Messages : 125
    Points : 57
    Points
    57
    Par défaut
    Bonjour,

    Tout d'abord merci pour votre réactivité.

    Citation Envoyé par Benjamin Delespierre Voir le message
    La nature de la persistance de ton instance va dépendre de son rôle:
    • s'il s'agit de données utilisateurs (comme par exemple un panier d'achat, unique par utilisateur mais chaque utilisateur en au un) alors il faut persister sur session
    • s'il s'agit de données commune à l'ensemble de l'application indépendament des utilisateurs (comme des paramètres de configuration) alors il faut effectuer une persitance en cache (sur fichier par exemple)
    • si on se fout pas mal du fait que le singleton dégage à chaque requête (comme une connexion à la base) alors on ne fait pas de persistance
    Je sais pas si mon cas s'inscrit dans ta liste. Je le reprécise à nouveau (au cas où) : mon but est de découpler mes modules (et cela donc en passant par le pattern Service Locator). Pour cela, j'ai un tableau qui contient une map statique de identifiant / service. Chaque module vient alors s'inscrire dans cette map via mon service locator. L'objectif du Singleton était alors de n'avoir qu'une seule instance (et donc ne pas perdre le tableau $services).
    Maintenant, je ne vois pas mettre mes services dans la session car ce n'est pas propre à chaque utilisateur. Il reste donc le cache (le 3° point, je l'ai pas trop compris) : je dois voir comment stocker se tableau dans le cache (je suis en plus sous prestashop).

    J'en profite quand même pour poser la question : selon vos expériences, quel est la bonne pratique pour gérer cela ?

    Citation Envoyé par Benjamin Delespierre Voir le message
    J'allais oublier: les singleton, c'est le meilleur moyen connu de l'homme pour introduire du couplage dans des composants.
    Ne t'inquiète pas car mon singleton est là tout simplement pour faire office de Service Locator et donc permettre le découplage de mes modules par inscription de services ...

  5. #5
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Pour cela, j'ai un tableau qui contient une map statique de identifiant / service. Chaque module vient alors s'inscrire dans cette map via mon service locator. L'objectif du Singleton était alors de n'avoir qu'une seule instance (et donc ne pas perdre le tableau $services).
    Cette fabrique de services doit elle disposer d'un état ? Si ce n'est pas le cas pourquoi ne pas utiliser des propriétés statiques plutôt qu'un singleton ?

    Maintenant, je ne vois pas mettre mes services dans la session car ce n'est pas propre à chaque utilisateur. Il reste donc le cache (le 3° point, je l'ai pas trop compris) : je dois voir comment stocker se tableau dans le cache (je suis en plus sous prestashop).
    Je doute fortement de la pertinence d'un cache pour cette classe, le gain de performance risque de ne pas être à la hauteur de l'effort de développement selon moi. Mais c'est à toi de voir, c'est ton application après tout

    J'en profite quand même pour poser la question : selon vos expériences, quel est la bonne pratique pour gérer cela ?
    Personnellement, j'aurais fait quelque chose dans ce goût là:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    <?php
     
    class ServiceLocator {
     
        public static function register ...
     
        public static function factory ...
    }
    C'est à dire une fabrique intégralement statique pour de meilleures performances. Mais c'est à double tranchant: comme le singleton, une classe statique va introduire un fort couplage avec les classes qui vont l’appeler. La meilleure solution n'est ni la fabrique statique ni le singleton mais des instance qu'on utilise grâce à une injection de dépendance. Là le découplage est total mais c'est plus complexe.

    Personnellement, je ne suis pas un grand fan de l'injection de dépendances, ça à toujours tendance à alourdir l'API, c'est vrai qu'avec ton application est flexible comme un chewing-gum mais une telle flexibilité n'est pas toujours utile, il faut savoir rester simple et reconnaître qu'on ne change pas les composants d'une application tous les mardis. J'avais lu un billet assez bien fait là dessus qui critiquait ouvertement la possibilité de changer l'ORM de Symfony pour passer, par exemple, de Doctrine à Propel. Comme l'auteur le faisait remarquer, ces usages sont de facto extrêmement limité vu que dans le monde réel une refonte de l'application est le plus souvent faite pour ce type de besoin.

    En d'autres termes, la généricité, c'est très bien mais il faut toujours garder à l'esprit qu'une solution élégante est une solution facile à écrire et à comprendre.

  6. #6
    Membre du Club
    Inscrit en
    Juin 2008
    Messages
    125
    Détails du profil
    Informations forums :
    Inscription : Juin 2008
    Messages : 125
    Points : 57
    Points
    57
    Par défaut
    Il y a effectivement pas mal de discussion sur quoi utiliser : l'injection de dépendances (DI) ou le service locator (et même l'inversion de contrôle IOC) ... Mais pour le moment, mon objectif est de faire fonctionner le Service Locator mis en place.

    Ceci dit, je pense qu'il est temps de montrer le code (simplifié au max) et d'y situer mon blocage :

    Le Service Locator :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
     
    <?php
    interface IServiceLocator
    {
        public function getService($pServiceName);
        public function setService($pServiceName, $pService);
    }
     
    class ServiceLocatorException extends Exception{}
     
    class ServiceLocator implements IServiceLocator
    {
        private $_services = array();
     
        public function getService ($pServiceName)
        {
           if (isset($this->_services[strtolower($pServiceName)])) {
               return $this->_services[strtolower($pServiceName)];
           }
           throw new ServiceLocatorException("Service $pServiceName is not configured yet");
        }
     
        public function setService ($pServiceName, $pService)
        {
            $this->_services[strtolower($pServiceName)] = $pService;
            return $this;
        }
     
        public function __call ($pName, $pParameters)
        {
            if (strpos(strtolower($pName), 'get') === 0) {
                return $this->getService(substr($pName, 3));
            }else{
                echo $pName;
            }
        }
    }
    ?>
    La classe simplifiant l'usage du Service Locator (pas obligatoire, mais je l'utilise) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
     
    class StaticServiceLocator
    {
        private static $_serviceLocator = false;
     
        public static function setService($pServiceName, $pService)
        {
            return self::_serviceLocatorInstance()->setService($pServiceName, $pService);
        }
     
        public static function getService($pServiceName)
        {
            return self::_serviceLocatorInstance()->getService($pServiceName);
        }
     
        public static function __callStatic($pName, $pParameters)
        {
            return self::_serviceLocatorInstance()->$pName($pParameters);
        }
     
        private static function _serviceLocatorInstance()
        {
            if (self::$_serviceLocator === false) {
                self::$_serviceLocator = new ServiceLocator();
            }
            return self::$_serviceLocator;
        }
    }
    ?>
    Cette classe StaticServiceLocator correspond à la classe A de mon exemple cité dans mes posts précédents.
    Ensuite, j'ai deux modules B et C : le module B offre un ensemble de services dont C a besoin. B va donc s'enregistrer auprès de A. Voici ce qui est fait :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    <?php
    // Ici inclusion du fichier StaticServiceLocator
    class ModuleB {
         ...
         public function __construct()
         {     ...
         }
    }
    StaticServiceLocator::setService("ModuleB", new ModuleB);
    ?>
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    <?php
    // Ici inclusion du fichier StaticServiceLocator
    class ModuleC {
         ...
         public function __construct()
         {     ...
               // Appel du service offert par le ModuleB
               $srvB = StaticServiceLocator::getService("ModuleB");
               $srvB->uneAction();
         }
    }
    ?>
    Dans une page php, le clic sur deux liens (1 lien vers le module B et un autre vers C) fait que le getService, à un moment donné, retourne null. Cela est du au fait que la classe A a été réinstancée (suite au deux appels).

    Je peine à trouver une solution car si la session n'est pas adaptée et qu'on me déconseille également le cache, vers quoi se tourner alors ?

  7. #7
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    La solution en or pour adresser ton problème est d'utiliser un tier supplémentaire (j'ai en tête Java ou NodeJs) mais je t'entends déjà dire non.

    Tes classes sont à priori correctes, aucun problème dans l'implémentation, c'est plutôt l'utilisation qui pêche car tu voudrais que les états des services soient communs à tous les runtimes ce qui en PHP est extrèmement difficile à obtenir car, comme je l'ai mentionné tout à l'heure, chaque requête est servie par un thread apache différent et isolé et, nonobstant le fait qu'ils tombent quand la requête est servie, ne communique pas (ou très mal) entre eux.

    Il faudrait que j'en sache un peu plus sur les responsabilités de tes classes service et ce qu'elles font concrêtement.

  8. #8
    Membre du Club
    Inscrit en
    Juin 2008
    Messages
    125
    Détails du profil
    Informations forums :
    Inscription : Juin 2008
    Messages : 125
    Points : 57
    Points
    57
    Par défaut
    Citation Envoyé par Benjamin Delespierre Voir le message
    La solution en or pour adresser ton problème est d'utiliser un tier supplémentaire (j'ai en tête Java ou NodeJs) mais je t'entends déjà dire non.
    Effectivement c'est un non.

    Citation Envoyé par Benjamin Delespierre Voir le message
    Il faudrait que j'en sache un peu plus sur les responsabilités de tes classes service et ce qu'elles font concrêtement.
    Ca prendrait beaucoup de temps pour synthétiser et fournir le contenu réel de mes classes. Mais je mets à jour la classe ModuleB comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    <?php
    // Ici inclusion du fichier StaticServiceLocator
    class ModuleB {     
         public function __construct()
         {
         }
     
         public static function uneAction()
         {
              echo 'Exécution de l\'action offerte par le module B';
         }
    }
    StaticServiceLocator::setService("ModuleB", new ModuleB);
    ?>
    Voilà. Tout ce dont j'ai besoin, c'est que lorsque je clique sur un lien lié au ModuleC, celui-ci puisse trouver le service ModuleB enregsistré dans A. C'est ce qui ne marche pas ...

  9. #9
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

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

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Tout ce dont j'ai besoin, c'est que lorsque je clique sur un lien lié au ModuleC, celui-ci puisse trouver le service ModuleB enregsistré dans A.
    Dans une page php, le clic sur deux liens (1 lien vers le module B et un autre vers C) fait que le getService, à un moment donné, retourne null. Cela est du au fait que la classe A a été réinstancée (suite au deux appels).
    Tout ça n'a pas le moindre sens pour moi Un coup tu parles de lien et de clics et juste après tu parles de tes services et de tes modules, mais entre les deux j'ai pas la moindre idée de ce qu'il peut bien se passer. Donc j'aurais bien du mal à déterminer quel est réellement ton problème.

    J'ai beau retourner le problème dans tous les sens, je ne comprends pas en quoi "cliquer sur un lien" est d'une quelconque façon lié aux actions des services... ce n'est pas le rôle d'une classe service de recevoir les actions utilisateur, c'est le rôle d'un contrôleur ça.

    Revenons au début.
    Quand j'appelle dans B, A::registerService('appel_B'), le tableau $services contient bien 'appel_B'. Mais dès que j'appelle dans C, A::registerService('appel_C'), le tableau ne contient que 'appel_C' (au lieu de 'appel_B' et 'appel_C') !
    Tu es sûr que:
    1. l'appel à A::registerService est bien fait lors du chargement du module C ?
    2. le module C est bien chargé / initialisé / fabriqué ou que sais-je ?
    3. la procédure de A::registerService est correcte ?

    Pour commencer, comment charges-tu tes modules ?

Discussions similaires

  1. Source Pattern Singleton
    Par Drikcé dans le forum Langage
    Réponses: 5
    Dernier message: 30/10/2007, 15h22
  2. [POO] Singleton en PHP et persistance
    Par ProximIT dans le forum Langage
    Réponses: 10
    Dernier message: 14/12/2006, 16h27
  3. Pattern singleton ou Classe avec méthodes statiques ?
    Par Claythest dans le forum Langage
    Réponses: 3
    Dernier message: 11/12/2006, 11h28
  4. fonctionnement de la classe Singleton
    Par lepoutho dans le forum C++
    Réponses: 11
    Dernier message: 04/08/2005, 09h28
  5. [Débutant] pattern singleton
    Par SirDarken dans le forum Débuter avec Java
    Réponses: 22
    Dernier message: 11/12/2004, 01h55

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo