Dans un projet PHP, quels modèles existent pour stocker, accéder et organiser les objets d'aide? [fermé]

114

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.

Pekka 웃
la source
1
Je viens de poser une question extrêmement similaire qui avait également une prime. Vous apprécierez peut-être quelques réponses ici: stackoverflow.com/questions/1967548/…
philfreo
3
Juste une tête haute, renvoyer un nouvel objet par référence - comme $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.
ryeguy

Réponses:

68

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

  1. un prestataire de services

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. 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):

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 :-)

koen
la source
+1 pour DI. Bien que je ne l'utilise pas autant que je le souhaiterais, cela a été très utile dans les petites quantités sur lesquelles je l'ai utilisé.
Anurag
1
@koen: Voulez-vous donner un exemple PHP d'implémentation DI / SP en PHP? Peut-être que le code @Flavius ​​a été implémenté en utilisant les modèles alternatifs que vous avez suggérés?
Alix Axel
Ajout d'un lien vers l'implémentation et le conteneur DI dans ma réponse.
Thomas
Je lis tout cela maintenant mais je n'ai pas encore tout lu, je voudrais demander, est-ce qu'un framework d'injection de dépendances serait fondamentalement un registre?
JasonDavis
Non, pas vraiment. Mais un conteneur d'injection de dépendances peut également servir de registre. Lisez simplement les liens que j'ai publiés dans ma réponse. Le conpect de DI est expliqué vraiment pratiquement.
Thomas
16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

C'est comme ça que je le ferais. Il crée l'objet à la demande:

Application::foo()->bar();

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:

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

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:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();
Flavius
la source
2
J'ai décliné la réponse parce que je crois que suggérer le modèle singleton pour gérer le problème va à l'encontre des principes solides de la POO.
koen
1
@koen: ce que vous dites est vrai, en général, MAIS pour autant que j'ai compris son problème, il parle des helpers pour l'application, et dans une application il n'y a qu'une ... euh, application.
Flavius
Remarque: ce que j'ai présenté n'est même pas un vrai motif singleton. Un singleton n'autoriserait qu'une seule instance d'une classe en définissant le constructeur comme privé. Il s'agit uniquement d'une variable "globale" disponible pour toutes les instances "Application". C'est pourquoi je pense que sa validité ne fait PAS abstraction des bons principes de la POO. Bien sûr, comme n'importe quoi dans le monde, ce «modèle» ne doit pas non plus être surutilisé.
Flavius
-1 de moi aussi. Ce n'est peut-être que la moitié du Singleton DP, mais c'est le plus laid: "lui donner un accès global".
juste quelqu'un
2
Cela rend en effet son approche actuelle beaucoup plus propre.
Daniel Von Fange
15

J'aime le concept d'injection de dépendance:

"L'injection de dépendances est l'endroit où les composants reçoivent leurs dépendances via leurs constructeurs, leurs méthodes ou directement dans des champs. (Depuis le site Web de Pico Container )"

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 .

Thomas
la source
J'aime l'implémentation via Closures en PHP, lecture très intéressante
Juraj Blahunka
Moi aussi et il a d'autres besoins concernant les fermetures sur son site: fabien.potencier.org/article/17
Thomas
2
espérons que les sites Web grand public migreront bientôt vers PHP 5.3, car il n'est toujours pas courant de voir un serveur php 5.3 complet
Juraj Blahunka
Ils devront, quand de plus en plus de projets nécessitent PHP 5.3 comme Zend Framework 2.0 sera framework.zend.com/wiki/display/ZFDEV2/…
Thomas
1
L'injection de dépendances a également été acceptée réponse à la question sur decupling from GOD object: stackoverflow.com/questions/1580210/… avec un très bel exemple
Juraj Blahunka
9

La 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 .

prend
la source
6

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 .

Felix Kling
la source
Si vous ne souhaitez pas utiliser Zend Framework, voici une belle implémentation du modèle de registre pour PHP5: phpbar.de/w/Registry
Thomas
Je préfère un modèle de registre typé, comme Registry :: GetDatabase ("master"); Registry :: GetSession ($ utilisateur-> SessionKey ()); Registry :: GetConfig ("local"); [...] et définissant une interface pour chaque type. De cette façon, vous vous assurez de ne pas écraser accidentellement une clé utilisée pour quelque chose de différent (c'est-à-dire que vous pourriez avoir une "base de données principale" et une "configuration principale". En utilisant les interfaces, vous vous assurez que seuls des objets valides sont utilisés. Ofc cela pourrait également être implémenté en utilisant plusieurs classes de registre, mais à mon avis, une seule est plus simple et plus facile à utiliser, mais présente toujours les avantages.
Morfildur
Ou bien sûr celui intégré à PHP - $ _GLOBALS
Gnuffo1
4

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:

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

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.

$object = new Class();

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.

Xeoncross
la source
3

J'irais pour la fonction renvoyant des objets initialisés:

A('Users')->getCurrentUser();

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.

Kamil Szot
la source
-1

Pourquoi ne pas lire le beau manuel?

http://php.net/manual/en/language.oop5.autoload.php

gcb
la source
Merci gcb, mais le chargement des classes n'est pas ma préoccupation, ma question est de nature plus architecturale.
Pekka
Bien que cela puisse théoriquement répondre à la question, il serait préférable d'inclure ici les parties essentielles de la réponse et de fournir le lien pour référence.
jjnguy