web-dev-qa-db-fra.com

Existe-t-il un cas d'utilisation pour les singletons avec accès à la base de données en PHP?

J'accède à ma base de données MySQL via PDO. Je configure l'accès à la base de données, et ma première tentative a été d'utiliser les éléments suivants:

La première chose à laquelle j'ai pensé est global:

$db = new PDO('mysql:Host=127.0.0.1;dbname=toto', 'root', 'pwd');

function some_function() {
    global $db;
    $db->query('...');
}

Ceci est considéré comme une mauvaise pratique. Après une petite recherche, je me suis retrouvé avec le motif Singleton , qui

"s'applique aux situations dans lesquelles il doit y avoir une seule instance d'une classe."

Selon l'exemple du manuel, nous devrions procéder comme suit:

class Database {
    private static $instance, $db;

    private function __construct(){}

    static function singleton() {
        if(!isset(self::$instance))
            self::$instance = new __CLASS__;

        return self:$instance;
    }

    function get() {
        if(!isset(self::$db))
            self::$db = new PDO('mysql:Host=127.0.0.1;dbname=toto', 'user', 'pwd')

        return self::$db;
    }
}

function some_function() {
    $db = Database::singleton();
    $db->get()->query('...');
}

some_function();

Pourquoi ai-je besoin de cette classe relativement grande quand je peux le faire?

class Database {
    private static $db;

    private function __construct(){}

    static function get() {
        if(!isset(self::$db))
            self::$db = new PDO('mysql:Host=127.0.0.1;dbname=toto', 'user', 'pwd');

        return self::$db;
    }
}

function some_function() {
    Database::get()->query('...');
}

some_function();

Ce dernier fonctionne parfaitement et je n'ai pas à m'inquiéter de $db plus.

Comment créer une classe singleton plus petite ou existe-t-il un cas d'utilisation pour les singletons qui manque à PHP?

136
seriousdev

D'accord, je me suis posé la question pendant un moment lorsque j'ai commencé ma carrière. Il a mis en œuvre différentes manières et a proposé deux raisons de choisir de ne pas utiliser de classes statiques, mais elles sont plutôt volumineuses.

La première est que vous trouverez très souvent quelque chose dont vous êtes absolument sûr de ne jamais avoir plus d'une instance, vous en avez éventuellement une seconde. Vous pouvez vous retrouver avec un deuxième moniteur, une deuxième base de données, un deuxième serveur - peu importe.

Lorsque cela se produit, si vous avez utilisé une classe statique, votre refactor sera bien pire que si vous aviez utilisé un singleton. Un singleton est un modèle douteux en soi, mais il se convertit assez facilement en un modèle d'usine intelligent - il peut même être converti pour utiliser l'injection de dépendance sans trop de problèmes. Par exemple, si votre singleton est obtenu via getInstance (), vous pouvez très facilement changer cela en getInstance (databaseName) et autoriser plusieurs bases de données - aucun autre changement de code.

Le deuxième problème concerne les tests (et honnêtement, il s’agit du même problème que le premier). Parfois, vous souhaitez remplacer votre base de données par une base de données fictive. En réalité, il s'agit d'une deuxième instance de l'objet de base de données. Cela est beaucoup plus difficile à faire avec des classes statiques qu'avec un singleton, il suffit de simuler la méthode getInstance (), pas toutes les méthodes d'une classe statique (ce qui peut être très difficile dans certaines langues).

Cela revient vraiment à des habitudes - et quand les gens disent que les "Globals" sont mauvais, ils ont de très bonnes raisons de le dire, mais cela n’est peut-être pas toujours évident tant que vous n’avez pas réglé le problème vous-même.

La meilleure chose à faire est de demander (comme vous l’avez fait), puis de faire un choix et d’observer les conséquences de votre décision. Avoir les connaissances nécessaires pour interpréter l'évolution de votre code au fil du temps est beaucoup plus important que de le faire correctement dès le départ.

79
Bill K

Les singletons ont très peu, sinon dire non, d’utilisation en PHP.

Dans les langues où les objets résident dans la mémoire partagée, les singletons peuvent être utilisés pour limiter l'utilisation de la mémoire. Au lieu de créer deux objets, vous référencez une instance existante à partir de la mémoire d'application partagée globalement. Dans PHP, il n'y a pas de mémoire d'application de ce type. Un singleton créé dans une requête vit exactement pour cette requête. Un singleton créé dans une autre requête exécutée simultanément est toujours une instance complètement différente. Ainsi, l'un des deux objectifs principaux d'un Singleton n'est pas applicable ici.

En outre, de nombreux objets ne pouvant conceptuellement exister qu'une seule fois dans votre application ne nécessitent pas nécessairement un mécanisme de langage pour le faire respecter. Si vous avez besoin de seulement une instance, alors n'instanciez pas une autre . Ce n'est que lorsque vous pouvez ne pas avoir d'autre instance, par exemple. lorsque les chatons meurent lors de la création d'une deuxième instance, vous pouvez avoir un cas d'utilisation valide pour un singleton.

L'autre objectif serait d'avoir un point d'accès global à une instance au sein de la même demande. Bien que cela puisse sembler souhaitable, ce n’est vraiment pas le cas, car cela crée un couplage avec la portée globale (comme pour tous les globals et statiques). Cela rend les tests unitaires plus difficiles et votre application en général moins maintenable. Il existe des moyens pour atténuer cela, mais en général, si vous avez besoin de la même instance dans plusieurs classes, utilisez Dependency Injection .

Voir mes diapositives pour Singletons dans PHP - Pourquoi ils sont mauvais et comment vous pouvez les éliminer de vos applications pour plus d'informations.

Même Erich Gamma , l'un des inventeurs du modèle de Singleton, doute aujourd'hui de ce modèle:

"Je suis favorable à la suppression de Singleton. Son utilisation est presque toujours une odeur de design"

Lectures complémentaires

Si, après ce qui précède, vous avez encore besoin d’aide pour décider:

Singleton Decision Diagram

317
Gordon

Qui a besoin de singletons en PHP?

Notez que presque toutes les objections aux singletons viennent de points de vue techniques - mais elles sont aussi TRÈS limitées dans leur portée. Surtout pour PHP. Dans un premier temps, je vais énumérer certaines des raisons d’utiliser des singletons, puis d’analyser les objections à l’utilisation des singletons. Premièrement, les personnes qui en ont besoin:

- Les personnes qui codent un grand framework/code, qui sera utilisé dans de nombreux environnements différents, devront travailler avec des frameworks/codes différents, préexistants et différents, avec la nécessité de mettre en œuvre plusieurs, des demandes changeantes, voire fantaisistes, de la part de clients/chefs/responsables/directions/unités font de même

Vous voyez, le motif singleton est auto-inclusif. Une fois terminé, une classe singleton est rigide vis-à-vis du code dans lequel vous l'incluez et elle fonctionne exactement comme vous avez créé ses méthodes et ses variables. Et c'est toujours le même objet dans une requête donnée. Puisqu'il ne peut pas être créé deux fois pour constituer deux objets différents, vous savez ce qu'est un objet singleton à un moment donné d'un code, même si le singleton est inséré dans deux, trois bases de code différentes, anciennes et même spaghetti. Par conséquent, cela facilite la tâche en termes de développement - même si de nombreuses personnes travaillent dans ce projet, quand vous voyez un singleton être initialisé à un moment donné dans une base de code donnée, vous savez de quoi il s'agit, comment il fonctionne. Si c'est la classe traditionnelle, vous devrez garder une trace de l'endroit où cet objet a été créé pour la première fois, des méthodes qui y ont été invoquées jusqu'à ce point du code et de son état particulier. Mais déposez-y un singleton, et si vous abandonnez les méthodes de débogage et d’information appropriées et que vous effectuez un suivi dans le singleton lors du codage, vous savez exactement de quoi il s'agit. Par conséquent, cela facilite la tâche des personnes qui doivent travailler avec des bases de code différentes, avec la nécessité d’intégrer du code, ce qui a été fait auparavant avec différentes philosophies ou par des personnes avec lesquelles vous n’avez aucun contact. (c’est-à-dire, fournisseur-projet-entreprise-quoi qu’il n’y ait ni aucun soutien, rien du tout).

- Personnes devant travailler avec des tiers API , services et sites Web.

Si vous regardez de plus près, cela n’est pas trop différent du cas précédent: les API, les services et les sites Web tiers sont comme des bases de code isolées externes sur lesquelles vous n’avez AUCUN contrôle. Tout peut arriver. Ainsi, avec une classe/session utilisateur unique, vous pouvez gérer TOUT type d'implémentation de session/autorisation auprès de fournisseurs tiers tels que OpenID , Facebook , Twitter et bien d’autres - et vous pouvez effectuer TOUT simultanément à partir de l’objet SAME singleton - qui est facilement accessible, dans un état connu à tout moment du code dans lequel vous le branchez. Vous pouvez même créer plusieurs sessions sur plusieurs API/services tiers différents pour l'utilisateur SAME dans votre propre site Web/application et faire ce que vous voulez en faire.

Bien sûr, tout cela peut également être harmonisé avec les méthodes traditionnelles en utilisant des classes et des objets normaux - le problème ici est que singleton est plus ordonné, plus ordonné et donc plus facile à gérer/à tester que l’utilisation traditionnelle des classes/objets dans de telles situations.

- Personnes ayant besoin d'un développement rapide

Le comportement global des singletons facilite la construction de tout type de code avec un framework contenant une collection de singletons, car une fois que vous aurez bien construit vos classes de singleton, les méthodes établies, matures et définies seront facilement disponibles. utilisable n'importe où, n'importe quand, de manière cohérente. Il faut un peu de temps pour faire mûrir vos cours, mais ils sont ensuite solides, cohérents et utiles. Vous pouvez avoir autant de méthodes dans un singleton qui font ce que vous voulez, et bien que cela puisse augmenter l'empreinte mémoire de l'objet, cela vous apportera beaucoup plus de temps nécessaire au développement rapide - une méthode que vous n'utilisez pas dans un cas donné de une application peut être utilisée dans une autre application intégrée, et vous pouvez simplement insérer une nouvelle fonctionnalité demandée par le client/responsable/chef de projet en apportant quelques modifications.

Vous avez l'idée. Passons maintenant aux objections aux singletons et à la croisade profane contre quelque chose d’utile:

- La principale objection est que cela complique les tests.

Et vraiment, dans une certaine mesure, même si cela peut être facilement atténué en prenant les précautions appropriées et en codant les routines de débogage dans vos singletons, sachant que vous allez déboguer un singleton. Mais vous voyez, ce n’est pas très différent de TOUT autre philosophie/méthode/modèle de codage existant - c’est simplement que les singletons sont relativement nouveaux et peu répandus, de sorte que les méthodes de test actuelles finissent par être incompatibles avec elles. Mais cela n’est pas différent dans tous les aspects des langages de programmation - des styles différents nécessitent des approches différentes.

L'un des arguments de cette objection est qu'il ne tient pas compte du fait que les applications développées ne sont pas conçues pour des tests, et que les tests ne sont pas la seule phase/processus qui entrent dans le développement d'applications. Les applications sont développées pour une utilisation en production. Et comme je l'ai expliqué dans la section "qui a besoin de singletons", les singletons peuvent réduire considérablement les difficultés liées à la nécessité de faire fonctionner un code avec et à l'intérieur de nombreux services différents codebase/applications/tiers. Le temps que l'on peut perdre en test, c'est du temps gagné en développement et en déploiement. Ceci est particulièrement utile à l’ère de l’authentification/application/intégration tierces - Facebook, Twitter, OpenID, et bien d’autres encore et qui sait quelle est la prochaine étape.

Bien que cela soit compréhensible, les programmeurs travaillent dans des circonstances très différentes selon leur carrière. Et pour les personnes qui travaillent dans des entreprises relativement grandes avec des départements définis qui gèrent des logiciels/applications définis de manière confortable et sans le désastre imminent de coupes budgétaires/mises à pied et la nécessité qui en découle de faire BEAUCOUP de choses avec beaucoup de choses différentes dans mode bon marché/rapide/fiable, les singletons peuvent sembler moins nécessaires. Et cela peut même être une nuisance/un empêchement à ce qu'ils ont déjà.

Mais pour ceux qui doivent travailler dans les tranchées sales du développement "agile", devant mettre en œuvre de nombreuses demandes différentes (parfois déraisonnables) de leur client/gestionnaire/projet, les singletons sont un avantage salvateur pour les raisons expliquées plus haut.

- Une autre objection est que son empreinte mémoire est plus importante

Parce qu'un nouveau singleton existera pour chaque requête de chaque client, ceci PEUT être une objection pour PHP. Avec des singletons mal construits et utilisés, l'empreinte mémoire d'une application peut être plus importante si de nombreux utilisateurs sont servis par l'application à un moment donné.

Bien que cela soit valable pour TOUT type d’approche que vous pouvez adopter lors du codage. Les questions à se poser sont les suivantes: les méthodes, les données détenues et traitées par ces singletons sont-elles inutiles? En effet, si elles sont nécessaires pour de nombreuses demandes reçues par l'application, alors même si vous n'utilisez pas de singletons, ces méthodes et données seront présentes dans votre application sous une forme ou une autre par le biais du code. Donc, tout devient une question de quantité de mémoire que vous allez économiser, lorsque vous initialisez un objet de classe traditionnel 1/3 dans le traitement de code et le détruisez aux 3/4.

Vous voyez, lorsque vous posez la question, la question devient tout à fait hors de propos - il ne devrait pas y avoir de méthodes inutiles, de données conservées dans des objets dans votre code TOUJOURS - que vous utilisiez des singletons ou non. Donc, cette objection aux singletons devient vraiment hilarante en ce sens qu'elle suppose qu'il y aura des méthodes inutiles, des données dans les objets créés à partir des classes que vous utilisez.

- Certaines objections non valides telles que 'rend le maintien de connexions multiples à la base de données impossible/difficile'

Je ne peux même pas commencer à comprendre cette objection, quand tout le monde a besoin de maintenir plusieurs connexions à une base de données, plusieurs sélections de base de données, plusieurs requêtes de base de données, plusieurs ensembles de résultats dans un singleton donné les conservent simplement dans des variables/tableaux dans le singleton tant que ils sont nécessaires. Cela peut être aussi simple que de les garder dans des tableaux, bien que vous puissiez inventer la méthode que vous souhaitez utiliser pour effectuer cela. Mais examinons le cas le plus simple, l’utilisation de variables et de tableaux dans un singleton donné:

Imaginez que le ci-dessous se trouve dans un singleton de base de données donné:

$ this -> connexions = tableau (); (syntaxe incorrecte, je viens de le taper comme ceci pour vous donner l'image - la déclaration correcte de la variable est public $ connections = array (); et son utilisation est $ this-> connections ['connectionkey'] naturellement )

Vous pouvez configurer et conserver plusieurs connexions à tout moment dans un tableau de cette manière. Il en va de même pour les requêtes, les ensembles de résultats, etc.

$ this -> requête (QUERYSTRING, 'nom de la requête', $ this-> connexions ['particulrconnection']);

Qui peut simplement faire une requête à une base de données sélectionnée avec une connexion sélectionnée, et simplement stocker dans votre

$ this -> résultats

tableau avec la clé 'nom de requête'. Bien sûr, vous aurez besoin de coder votre méthode de requête pour cela - ce qui est facile à faire.

Cela vous permet de gérer un nombre pratiquement infini de connexions aux bases de données et d'ensembles de résultats (autant que les limites de ressources le permettent bien sûr) autant que vous en avez besoin. Et ils sont disponibles pour TOUT morceau de code en n'importe quel point de n'importe quelle base de code dans laquelle cette classe singleton a été instanciée.

Bien sûr, vous auriez naturellement besoin de libérer les ensembles de résultats et les connexions lorsqu'ils ne sont pas nécessaires - mais cela va sans dire, et cela n'est pas spécifique aux singletons ou à toute autre méthode/style/concept de codage.

À ce stade, vous pouvez voir comment gérer plusieurs connexions/états avec des applications ou services tiers dans le même singleton. Pas si différent.

En résumé, les motifs uniques sont simplement une méthode/un style/une philosophie avec lesquels programmer, et ils sont aussi utiles que TOUTE autre, quand ils sont utilisés au bon endroit, de la bonne manière. Ce qui n'est différent de rien.

Vous remarquerez que dans la plupart des articles dans lesquels des singletons sont frappés, vous verrez également des références à des "globals" étant "diaboliques".

Regardons les choses en face: tout ce qui n’est pas utilisé correctement, abusé, abusé, IS mal). Cela ne se limite pas à une langue, à un concept de codage, à une méthode. Chaque fois que vous voyez quelqu'un émettre des déclarations générales comme "X is evil ', fuyez cet article. Il y a de fortes chances que ce soit le produit d'un point de vue limité - même si ce point de vue est le résultat d'années d'expérience dans quelque chose de particulier - qui finit généralement par résulter d'un travail aussi beaucoup dans un style/méthode donné - conservatisme intellectuel typique.

On peut en donner d’innombrables exemples, allant de "les globals sont diaboliques" à "les iframes sont diaboliques". Il y a environ 10 ans, l'hérésie consistait même à proposer l'utilisation d'un iframe dans une application donnée. Vient ensuite Facebook, les iframes partout, et regarde ce qui est arrivé - les iframes ne sont plus si pervers.

Il y a encore des gens qui insistent obstinément pour dire qu'ils sont "pervers" - et parfois pour de bonnes raisons aussi - mais, comme vous pouvez le constater, il existe un besoin, les iframes répondent à ce besoin et fonctionnent bien, et le monde entier ne fait que passer à autre chose.

Le principal atout d'un programmeur/codeur/ingénieur logiciel est un esprit libre, ouvert et flexible.

21
unity100

Les singletons sont considérés par beaucoup comme anti-patterns car ils ne sont que des variables globales glorifiées. En pratique, il y a relativement peu de scénarios où il est nécessaire pour une classe de n'avoir qu'une seule instance; En général, une seule instance suffit , auquel cas son implémentation en tant que singleton est totalement inutile.

Pour répondre à la question, vous avez raison de dire que les singletons sont excessifs ici. Une simple variable ou fonction fera l'affaire. Cependant, une meilleure approche (plus robuste) consisterait à utiliser injection de dépendance pour supprimer le besoin de variables globales.

15
Will Vousden

Dans votre exemple, vous avez affaire à un seul élément d’information apparemment immuable. Pour cet exemple, un Singleton serait excessif et le simple fait d'utiliser une fonction statique dans une classe suffirait.

Autres réflexions: vous pouvez être amené à mettre en place des modèles pour des motifs et votre instinct vous dit "non, vous n’êtes pas obligé de le faire" pour les raisons que vous avez expliquées.

MAIS: Nous n'avons aucune idée de la taille et de la portée de votre projet. S'il s'agit d'un code simple, peut-être jeté, qui n'aura probablement pas besoin d'être modifié, alors oui, utilisez des membres statiques. Toutefois, si vous pensez que votre projet doit éventuellement être redimensionné ou préparé pour le codage de maintenance, vous pouvez utiliser le modèle Singleton.

8
Paul Sasik

Premièrement, je veux juste dire que je ne trouve pas beaucoup d’utilisation dans le modèle Singleton. Pourquoi voudrait-on garder un seul objet dans toute l'application? Surtout pour les bases de données, si je veux me connecter à un autre serveur de base de données? Je dois me déconnecter et me reconnecter à chaque fois ...? En tous cas...

L'utilisation de globals dans une application présente plusieurs inconvénients (comme le fait l'utilisation traditionnelle du motif Singleton):

  • Difficile au test unitaire
  • Problèmes d'injection de dépendance
  • Peut créer des problèmes de verrouillage (application multithread)

Utiliser des classes statiques au lieu d'une instance singleton offre certains des mêmes inconvénients, car le plus gros problème du singleton est la méthode statique getInstance.

Vous pouvez limiter le nombre d'instances qu'une classe peut avoir sans utiliser la méthode traditionnelle getInstance:

class Single {

    static private $_instance = false;

    public function __construct() {
        if (self::$_instance)
           throw new RuntimeException('An instance of '.__CLASS__.' already exists');

        self::$_instance = true;
    }

    private function __clone() {
        throw new RuntimeException('Cannot clone a singleton class');
    }

    public function __destruct() {
        self::$_instance = false;
    }

}

$a = new Single;
$b = new Single; // error
$b = clone($a); // error
unset($a);
$b = new Single; // works

Cela aidera pour la première fois les points mentionnés ci-dessus: tests unitaires et injection de dépendance; tout en vous assurant qu'une seule instance de la classe existe dans votre application. Vous pouvez, par exemple, simplement transmettre l'objet résultant à vos modèles (modèle MVC) pour qu'ils les utilisent.

5
netcoder

Considérez simplement en quoi votre solution diffère de celle présentée dans la PHP docs. En fait, il n’ya qu’une seule "petite" différence: votre solution offre aux appelants du getter un PDO instance, tandis que celle de la documentation fournit aux appelants de Database::singleton une instance Database (ils utilisent ensuite le getter pour obtenir une instance PDO).

Alors à quelle conclusion aboutissons-nous?

  • Dans le code de la documentation, les appelants reçoivent une instance Database. La classe Database peut exposer (en fait, elle devrait exposer si vous rencontrez tous ces problèmes) une interface de niveau que l'objet PDO qu'il enveloppe.
  • Si vous modifiez votre implémentation pour renvoyer un autre type (plus riche) que PDO, les deux implémentations sont équivalentes. Il n’ya aucun avantage à suivre la mise en œuvre manuelle.

Sur le plan pratique, Singleton est un modèle assez controversé. C'est principalement parce que:

  • C'est trop utilisé. Les programmeurs novices apprennent beaucoup plus facilement Singleton que d’autres modèles. Ils continuent ensuite à appliquer leurs nouvelles connaissances partout, même si le problème à résoudre peut être résolu mieux sans Singleton (lorsque vous tenez un marteau, tout ressemble à un clou).
  • Selon le langage de programmation, implémenter un Singleton de manière étanche et sans fuites peut s'avérer une tâche titanesque (surtout si nous avons des scénarios avancés: un singleton dépendant d'un autre singleton, des singletons pouvant être détruits et recréés, etc. ). Essayez simplement de rechercher "l'implémentation définitive" de Singleton en C++, je vous mets au défi (je possède le design révolutionnaire Modern C++ d'Andrei Alexandrescu, qui documente une bonne partie du gâchis).
  • Cela impose une charge de travail supplémentaire lors du codage du Singleton et de l'écriture de code pour y accéder. Charge que vous pouvez faire en respectant quelques contraintes auto-imposées sur ce que vous essayez de faire avec vos variables de programme.

Donc, comme conclusion finale: votre singleton va très bien. Ne pas utiliser Singleton du tout suffit également la plupart du temps.

5
Jon

Je ne vois aucun intérêt à cela. Si vous avez implémenté la classe de telle sorte que la chaîne de connexion soit considérée comme un paramètre du constructeur et conserve une liste de PDO (un pour chaque objet unique). chaîne de connexion) alors peut-être qu'il y aurait un avantage, mais la mise en œuvre de singleton dans ce cas semble être un exercice inutile.

2
Kell

Votre interprétation est correcte. Les singletons ont leur place mais sont trop utilisés. L'accès aux fonctions de membre statique est souvent suffisant (notamment lorsque vous n'avez pas besoin de contrôler le temps de construction de quelque manière que ce soit). Mieux, vous pouvez simplement mettre des fonctions libres et des variables dans un espace de noms.

Lors de la programmation, il n'y a pas de "juste" et de "faux"; il y a "bonne pratique" et "mauvaise pratique".

Les singletons sont généralement créés en tant que classe à réutiliser ultérieurement. Ils doivent être créés de manière à ce que le programmeur n'instancie pas accidentellement deux instances lors du codage ivre à minuit.

Si vous avez une petite classe simple qui ne devrait pas être instanciée plus d'une fois, vous n'avez pas besoin de pour en faire un singleton. C'est juste un filet de sécurité si vous le faites.

ce n'est pas toujours mauvaise pratique d'avoir des objets globaux. Si vous savez que vous allez l’utiliser dans le monde entier/partout/tout le temps, c’est peut-être une des rares exceptions. Cependant, les globals sont généralement considérés comme une "mauvaise pratique" de la même manière que goto est considérée comme une mauvaise pratique.

2
zzzzBov

Pour autant que je sache, rien ne vous manque. L'exemple est assez imparfait. Cela ferait une différence si la classe singleton avait des variables d'instance non statiques.

1
FooLman