Comment organisez-vous et gérez-vous vos objets d'aide comme le moteur de base de données, la notification utilisateur, la gestion des erreurs, etc. dans un projet orienté objet basé sur PHP?
Disons que j'ai un gros CMS PHP. Le CMS est organisé en différentes classes. Quelques exemples:
- l'objet de base de données
- gestion des utilisateurs
- une API pour créer / modifier / supprimer des éléments
- un objet de messagerie pour afficher des messages à l'utilisateur final
- un gestionnaire de contexte qui vous amène à la bonne page
- une classe de barre de navigation qui affiche des boutons
- un objet de journalisation
- éventuellement, gestion des erreurs personnalisée
etc.
Je traite de la question éternelle, comment rendre au mieux ces objets accessibles à chaque partie du système qui en a besoin.
ma première approche, il y a de nombreuses années, était d'avoir une $ application globale qui contenait des instances initialisées de ces classes.
global $application;
$application->messageHandler->addMessage("Item successfully inserted");
Je suis ensuite passé au modèle Singleton et à une fonction d'usine:
$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");
mais je ne suis pas content non plus. Les tests unitaires et l'encapsulation deviennent de plus en plus importants pour moi, et dans ma compréhension de la logique derrière les globals / singletons détruit l'idée de base de la POO.
Ensuite, il y a bien sûr la possibilité de donner à chaque objet un certain nombre de pointeurs vers les objets d'assistance dont il a besoin, probablement la manière la plus propre, la plus économe en ressources et la plus conviviale pour les tests, mais j'ai des doutes sur la maintenabilité de cela à long terme.
La plupart des frameworks PHP que j'ai examinés utilisent soit le modèle singleton, soit des fonctions qui accèdent aux objets initialisés. Les deux bonnes approches, mais comme je l'ai dit, je ne suis satisfait d'aucune des deux.
Je voudrais élargir mon horizon sur les modèles communs qui existent ici. Je recherche des exemples, des idées supplémentaires et des pointeurs vers des ressources qui discutent de cela dans une perspective à long terme et dans le monde réel .
De plus, je suis intéressé d'entendre parler d' approches spécialisées, de niche ou simplement étranges du problème.
la source
$mh=&factory("messageHandler");
est inutile et ne donne aucun avantage en termes de performances. De plus, ceci est obsolète dans la version 5.3.Réponses:
J'éviterais l'approche Singleton suggérée par Flavius. Il existe de nombreuses raisons d'éviter cette approche. Cela viole les bons principes de la POO. Le blog de test google contient de bons articles sur le Singleton et comment l'éviter:
http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html
Alternatives
un prestataire de services
http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html
injection de dépendance
http://en.wikipedia.org/wiki/Dependency_injection
et une explication php:
http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection
Ceci est un bon article sur ces alternatives:
http://martinfowler.com/articles/injection.html
Implémentation de l'injection de dépendances (DI):
Je pense que vous devriez demander ce qui est nécessaire dans le constructeur pour que l'objet fonctionne :
new YourObject($dependencyA, $dependencyB);
Vous pouvez fournir les objets nécessaires (dépendances) manuellement (
$application = new Application(new MessageHandler()
). Mais vous pouvez également utiliser un framework DI (la page wikipedia fournit des liens vers des frameworks PHP DI ).Il est important de ne transmettre que ce que vous utilisez réellement (appelez une action), PAS ce que vous passez simplement à d'autres objets parce qu'ils en ont besoin. Voici un article récent de 'oncle Bob' (Robert Martin) sur la DI manuelle et l'utilisation du framework .
Quelques réflexions supplémentaires sur la solution de Flavius. Je ne veux pas que ce post soit un anti-post mais je pense qu'il est important de voir pourquoi l'injection de dépendances est, au moins pour moi, meilleure que les globaux.
Même si ce n'est pas une «vraie» implémentation de Singleton , je pense toujours que Flavius s'est trompé. L'état global est mauvais . Notez que ces solutions utilisent également des méthodes statiques difficiles à tester .
Je sais que beaucoup de gens le font, l'approuvent et l'utilisent. Mais lire les articles du blog de Misko Heverys ( un expert en testabilité de Google ), les relire et digérer lentement ce qu'il dit modifie beaucoup la façon dont je vois le design.
Si vous souhaitez pouvoir tester votre application, vous devrez adopter une approche différente pour concevoir votre application. Lorsque vous effectuez une programmation test-first, vous aurez des difficultés avec des choses comme celle-ci: «Ensuite, je veux implémenter la journalisation dans ce morceau de code; écrivons d'abord un test qui enregistre un message de base ', puis proposons un test qui vous oblige à écrire et à utiliser un enregistreur global qui ne peut pas être remplacé.
J'ai encore du mal avec toutes les informations que j'ai obtenues de ce blog, et ce n'est pas toujours facile à mettre en œuvre, et j'ai beaucoup de questions. Mais il n'y a aucun moyen que je puisse revenir à ce que j'ai fait avant (oui, l'état global et les singletons (gros S)) après avoir compris ce que Misko Hevery disait :-)
la source
C'est comme ça que je le ferais. Il crée l'objet à la demande:
C'est la façon dont je le fais, cela respecte les principes de la POO, c'est moins de code que la façon dont vous le faites actuellement, et l'objet n'est créé que lorsque le code en a besoin pour la première fois.
Remarque : ce que j'ai présenté n'est même pas un vrai motif singleton. Un singleton n'autoriserait qu'une seule instance de lui-même en définissant le constructeur (Foo :: __ constructor ()) comme privé. Il s'agit uniquement d'une variable "globale" disponible pour toutes les instances "Application". C'est pourquoi je pense que son utilisation est valable car elle ne néglige PAS les bons principes de la POO. Bien sûr, comme n'importe quoi au monde, ce "modèle" ne doit pas non plus être surutilisé!
J'ai vu cela être utilisé dans de nombreux frameworks PHP, Zend Framework et Yii parmi eux. Et vous devriez utiliser un cadre. Je ne vais pas vous dire lequel.
Addendum Pour ceux d'entre vous qui s'inquiètent de TDD , vous pouvez toujours faire du câblage pour l'injecter de dépendances. Cela pourrait ressembler à ceci:
Il y a suffisamment de place à l'amélioration. C'est juste un PoC, utilisez votre imagination.
Pourquoi est-ce que c'est comme ça? Eh bien, la plupart du temps, l'application ne sera pas testée à l'unité, elle sera en fait exécutée, espérons-le dans un environnement de production . La force de PHP est sa rapidité. PHP n'est PAS et ne sera jamais un "langage POO propre", comme Java.
Dans une application, il n'y a qu'une seule classe Application et une seule instance de chacun de ses assistants, au plus (comme pour le chargement différé comme ci-dessus). Bien sûr, les singletons sont mauvais, mais encore une fois, seulement s'ils n'adhèrent pas au monde réel. Dans mon exemple, ils le font.
Les «règles» stéréotypées comme «les singletons sont mauvais» sont la source du mal, elles sont destinées aux paresseux qui ne veulent pas penser par eux-mêmes.
Ouais, je sais, le manifeste PHP est MAUVAIS, techniquement parlant. Pourtant, c'est un langage à succès, à sa manière hackish.
Addenda
Un style de fonction:
la source
J'aime le concept d'injection de dépendance:
Fabien Potencier a écrit une très belle série d'articles sur l'injection de dépendances et la nécessité de les utiliser. Il propose également un joli et petit conteneur d'injection de dépendances nommé Pimple que j'aime beaucoup utiliser (plus d'informations sur github ).
Comme indiqué ci-dessus, je n'aime pas l'utilisation des Singletons. Un bon résumé des raisons pour lesquelles les singletons ne sont pas d'un bon design peut être trouvé ici sur le blog de Steve Yegge .
la source
decupling from GOD object
: stackoverflow.com/questions/1580210/… avec un très bel exempleLa meilleure approche est d'avoir une sorte de conteneur pour ces ressources. Certaines des façons les plus courantes d'implémenter ce conteneur :
Singleton
Non recommandé car il est difficile à tester et implique un état global. (Singletonite)
Enregistrement
Élimine la singletonite, bogue que je ne recommanderais pas non plus au registre, car c'est aussi une sorte de singleton. (Difficile à tester unitaire)
Héritage
Dommage, il n'y a pas d'héritage multiple en PHP, donc cela limite tout à la chaîne.
Injection de dépendance
C'est une meilleure approche, mais un sujet plus vaste.
Traditionnel
La façon la plus simple de faire cela consiste à utiliser l'injection de constructeur ou de setter (passer un objet de dépendance à l'aide de setter ou dans le constructeur de classe).
Cadres
Vous pouvez lancer votre propre injecteur de dépendances, ou utiliser certains des frameworks d'injection de dépendances, par exemple. Yadif
Ressource d'application
Vous pouvez initialiser chacune de vos ressources dans le bootstrap de l'application (qui agit comme un conteneur) et y accéder n'importe où dans l'application accédant à l'objet bootstrap.
C'est l'approche implémentée dans Zend Framework 1.x
Chargeur de ressources
Une sorte d'objet statique qui charge (crée) la ressource nécessaire uniquement lorsque cela est nécessaire. C'est une approche très intelligente. Vous pouvez le voir en action, par exemple en implémentant le composant d'injection de dépendances de Symfony
Injection dans une couche spécifique
Les ressources ne sont pas toujours nécessaires n'importe où dans l'application. Parfois, vous en avez juste besoin, par exemple dans les contrôleurs (MV C ). Ensuite, vous pouvez y injecter les ressources uniquement.
L'approche courante consiste à utiliser des commentaires de docblock pour ajouter des métadonnées d'injection.
Voir mon approche à ce sujet ici:
Comment utiliser l'injection de dépendances dans Zend Framework? - Débordement de pile
En fin de compte, j'aimerais ajouter une note sur une chose très importante ici - la mise en cache.
En général, malgré la technique que vous choisissez, vous devez réfléchir à la manière dont les ressources seront mises en cache. Le cache sera la ressource elle-même.
Les applications peuvent être très volumineuses et le chargement de toutes les ressources à chaque demande est très coûteux. Il existe de nombreuses approches, y compris ce serveur d'applications en php - Hébergement de projet sur Google Code .
la source
Si vous souhaitez rendre les objets disponibles globalement, le modèle de registre peut être intéressant pour vous. Pour vous inspirer, jetez un œil à Zend Registry .
Donc aussi la question Registry vs. Singleton .
la source
Les objets en PHP prennent une bonne quantité de mémoire, comme vous l'avez probablement vu dans vos tests unitaires. Par conséquent, il est idéal de détruire les objets inutiles dès que possible pour économiser de la mémoire pour d'autres processus. Dans cet esprit, je trouve que chaque objet correspond à l'un des deux moules.
1) L'objet peut avoir de nombreuses méthodes utiles ou doit être appelé plus d'une fois, auquel cas j'implémente un singleton / registre:
2) L'objet n'existe que pour la durée de vie de la méthode / fonction qui l'appelle, auquel cas une création simple est bénéfique pour éviter que les références d'objet persistantes ne maintiennent les objets en vie trop longtemps.
Le stockage d'objets temporaires N'IMPORTE O peut entraîner des fuites de mémoire, car les références à ceux-ci peuvent être oubliées pour conserver l'objet en mémoire pour le reste du script.
la source
J'irais pour la fonction renvoyant des objets initialisés:
Dans l'environnement de test, vous pouvez le définir pour renvoyer des maquettes. Vous pouvez même détecter à l'intérieur qui appelle la fonction en utilisant debug_backtrace () et renvoyer différents objets. Vous pouvez vous inscrire à l'intérieur qui veut obtenir quels objets pour obtenir des informations sur ce qui se passe réellement dans votre programme.
la source
Pourquoi ne pas lire le beau manuel?
http://php.net/manual/en/language.oop5.autoload.php
la source