web-dev-qa-db-fra.com

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

Comment organisez-vous et gérez-vous vos objets auxiliaires comme le moteur de base de données, la notification aux utilisateurs, la gestion des erreurs, etc. dans un projet orienté objet PHP basé sur PHP?

Disons que j'ai un grand PHP CMS. 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 personnalisée des erreurs

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 plusieurs années, était d'avoir un $ application global 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 selon moi, la logique derrière les globaux/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 de la manière la plus propre, la plus économe en ressources et la plus conviviale pour les tests, mais j'ai des doutes quant à sa maintenabilité à long terme.

La plupart des frameworks PHP que j'ai examinés utilisent soit le motif singleton, soit les fonctions qui accèdent aux objets initialisés. Les deux approches sont fines, mais comme je l'ai dit, je ne suis satisfait ni de l'un ni de l'autre.

Je voudrais élargir mon horizon sur les modèles communs qui existent ici. Je cherche des exemples, des idées supplémentaires et des pointeurs vers des ressources qui en discutent dans une perspective à long terme, monde réel.

En outre, je suis intéressé d'entendre parler des approches spécialisées, de niche ou tout simplement bizarres du problème.

114
Pekka

J'éviterais l'approche Singleton suggérée par Flavius. Il existe de nombreuses raisons d'éviter cette approche. Il viole les bons principes OOP. 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.htmlhttp: //googletesting.blogspot. com/2008/05/tott-using-dependancy-injection-to.htmlhttp://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

Alternatives

  1. un fournisseur 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épendance (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épendance est, au moins pour moi, meilleure que les autres.

Même si ce n'est pas une implémentation "vraie" Singleton , je pense toujours que Flavius ​​s'est trompé. L'état global est mauvais . Notez que ces solutions utilisent également 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 ( n expert en testabilité de Google ), le relire et digérer lentement ce qu'il dit a changé ma façon de voir le design.

Si vous voulez pouvoir tester votre application, vous devrez adopter une approche différente pour concevoir votre application. Lorsque vous effectuez une programmation testée en premier, vous aurez des difficultés avec des choses comme ceci: "ensuite, je veux implémenter la journalisation dans ce morceau de code; écrivons d'abord un test qui enregistre un message de base ', puis élaborons un test qui vous oblige à écrire et à utiliser un enregistreur global qui ne peut pas être remplacé.

Je suis toujours en difficulté 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, état global et Singletons (big S)) après avoir compris ce que Misko Hevery disait :-)

68
koen
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 ferais. Il crée l'objet à la demande:

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

C'est la façon dont je le fais, il respecte les principes OOP, 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 ne permettrait 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 valide car elle ne néglige PAS les bons principes OOP. Bien sûr, comme n'importe quoi dans le monde, ce "modèle" ne doit pas non plus être abusé!

J'ai vu cela être utilisé dans de nombreux frameworks PHP, Zend Framework et Yii parmi eux. Et vous devriez utiliser un framework. Je ne vais pas vous dire lequel.

Addendum Pour ceux parmi vous qui s'inquiètent TDD , vous pouvez toujours vous rattraper du câblage pour l'injecter en dépendance. 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 assez de place pour l'amélioration. C'est juste un PoC, utilisez votre imagination.

Pourquoi ça comme ça? Eh bien, la plupart du temps, l'application ne sera pas testée à l'unité, elle sera en fait exécutée, si tout va bien dans un environnement de production. La force de PHP est sa vitesse. PHP n'est PAS et ne sera jamais un "langage propre OOP langage"), 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 (selon le chargement différé comme ci-dessus). Bien sûr, les singletons sont mauvais, mais là encore, 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.

Oui, je sais, le manifeste PHP est MAUVAIS, techniquement parlant. Pourtant, c'est un langage réussi, à 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();
16
Flavius

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. (De Site Web Pico Container )"

Fabien Potencier a écrit un très sympa série d'articles sur l'injection de dépendance et la nécessité de les utiliser. Il propose également un joli et petit conteneur d'injection de dépendance nommé Pimple que j'aime beaucoup utiliser (plus d'informations sur github ).

Comme indiqué ci-dessus, je n'aime pas l'utilisation de singletons. Un bon résumé des raisons pour lesquelles les singletons ne sont pas un bon design peut être trouvé ici dans le blog de Steve Yegge .

15
Thomas

La meilleure approche consiste à avoir une sorte de conteneur pour ces ressources. Certaines des façons les plus courantes de mettre en œuvre ce conteneur:

Singleton

Non recommandé car il est difficile à tester et implique un état global. (Singletonite)

Enregistrement

Élimine la singletonite, bug que je ne recommanderais pas trop de registre, car c'est aussi une sorte de singleton. (Test difficile à réaliser)

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 le faire est d'utiliser l'injection de constructeur ou de setter (passer l'objet de dépendance à l'aide de setter ou dans le constructeur de classe).

Cadres

Vous pouvez lancer votre propre injecteur de dépendance, ou utiliser certains des cadres d'injection de dépendance, par exemple. Yadif

Ressource d'application

Vous pouvez initialiser chacune de vos ressources dans l'application bootstrap (qui agit comme un conteneur), et y accéder n'importe où dans l'application en 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) les ressources nécessaires uniquement lorsque cela est nécessaire. Il s'agit d'une approche très intelligente. Vous pouvez le voir en action, par exemple implémentation composant d'injection de dépendances de Symfony

Injection sur 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 injecter les ressources uniquement là-bas.

L'approche courante consiste à utiliser les commentaires docblock pour ajouter des métadonnées d'injection.

Voir mon approche ici:

Comment utiliser l'injection de dépendances dans Zend Framework? - Stack Overflow

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 penser à la façon 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, dont celle-ci appserver-in-php - Hébergement de projet sur Google Code .

9
takeshin

Si vous voulez rendre les objets disponibles globalement, le modèle de registre pourrait être intéressant pour vous. Pour l'inspiration, jetez un œil à Zend Registry .

Il en va de même de la question Registry vs. Singleton .

6
Felix Kling

Les objets dans PHP occupent une bonne quantité de mémoire, comme vous l'avez probablement vu à partir de vos tests unitaires. Par conséquent, il est idéal de détruire les objets inutiles comme 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'objets persistantes ne maintiennent les objets en vie trop longtemps.

$object = new Class();

Stocker des objets temporaires N'IMPORTE OÙ peut entraîner des fuites de mémoire car les références à ceux-ci peuvent être oubliées quant à la conservation de l'objet en mémoire pour le reste du script.

4
Xeoncross

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 à l'aide de debug_backtrace () et renvoyer différents objets. Vous pouvez vous inscrire à l'intérieur qui veut obtenir quels objets pour avoir un aperçu de ce qui se passe réellement à l'intérieur de votre programme.

3
Kamil Szot