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 :

[hardCore PHP OO] injection de dépendance/inversion de contrôle


Sujet :

Langage PHP

  1. #1
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut [hardCore PHP OO] injection de dépendance/inversion de contrôle
    Bonjour,

    Je souhaites revoir mes habitudes de dev, et je m’intéresse actuellement à PHPUnit.
    J'avais l'habitude d'utiliser des singletons et/ou des méthodes statiques, mais il semble que ce soit le diable, au moins pour les tests unitaires.
    Sans relancer le débat, vous confirmez?

    Donc j'ai maintenant l'occasion de me lancer dans l'injection de dépendance et l'inversion de contrôle. Je souhaiterai un outil, qui:
    -Gère l'inversion de contrôle
    -Gere les require/include
    -soit léger
    -soit peu verbeux, idéalement un truc comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $monObjet=$contexte->factory('objet')->get(25);
    Connaissez-vous un outil qui corresponde à ça, ou vaut-il mieux que je le développe moi-même?

    D'autres remarques?

    Merci,
    @+
    Piero

    J'avais ouvert cette discussion dans un autre forum, je me suis planté donc j'ai mis un lien vers celle-ci...

  2. #2
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 38
    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
    Par défaut
    J'avais l'habitude d'utiliser des singletons et/ou des méthodes statiques, mais il semble que ce soit le diable, au moins pour les tests unitaires.
    Douce musique à mes oreilles, c'est pas tout les jours que j'entends quelqu'un devenir raisonnable.

    Blague à part c'est tout à fait vrai, les composants qui utilisent massivement les singletons et les membres statiques sont par nature difficiles voire impossible à tester car ils agissent comme un ensemble et non comme des briques remplaçables. On ne peut donc pas les mocker, ce qui rends le test pratiquement impossible.

    J'ai publié sur mon site un article sur l'injection de dépendances et la programmation orientée composants en général, je t'invite à y jeter un oeil.

    Par contre la gestion de l'inclusion automatique et l'injection de composants sont deux aspects séparés.

    Pour l'injection de dépendances, tu peux utiliser Pimple, c'est simple, léger mais puissant malgré tout (surtout son système d'abstract-factory à base de closures !)

    Pour l'autoload, utilise celui de composer en créant tes composants de sorte qu'ils soient compatibles composer. Une fois installé sur ton projet, composer t'offre nativement un autoloader puissant.

  3. #3
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Citation Envoyé par Benjamin Delespierre Voir le message
    Douce musique à mes oreilles, c'est pas tout les jours que j'entends quelqu'un devenir raisonnable.

    Blague à part c'est tout à fait vrai, les composants qui utilisent massivement les singletons et les membres statiques sont par nature difficiles voire impossible à tester car ils agissent comme un ensemble et non comme des briques remplaçables.
    J'avoue qu'il m'a fallu un bon temps de réflexion avant d'en convenir! Je suis du genre têtu...

    Citation Envoyé par Benjamin Delespierre Voir le message
    On ne peut donc pas les mocker, ce qui rends le test pratiquement impossible.
    Holà, je me mocke de personne, ni d'aucun objet... Bref, je suis pas encore à l'aise avec ces termes, je débute... Mocker c'est un peu comme si on les by-pass avec un faux objet, c'est bien ça?

    Citation Envoyé par Benjamin Delespierre Voir le message
    J'ai publié sur mon site un article sur l'injection de dépendances et la programmation orientée composants en général, je t'invite à y jeter un oeil.
    J'y vais de ce pas...

    Citation Envoyé par Benjamin Delespierre Voir le message
    Par contre la gestion de l'inclusion automatique et l'injection de composants sont deux aspects séparés.
    Oui je sais, mais justement, je me demandais si il ne serait pas intéressant, de faire d'une pierre deux coups, un système "tout-en-un". Mais peut-être que je prends mes rêves pour des réalités, et qu'on ne peux pas faire "correspondre" les deux. Je dois encore y réfléchir en tout cas.

    Citation Envoyé par Benjamin Delespierre Voir le message
    Pour l'injection de dépendances, tu peux utiliser Pimple, c'est simple, léger mais puissant malgré tout (surtout son système d'abstract-factory à base de closures !)
    , j'en ai des frissons dans le dos, que des trucs que j'aime, il a l'air parfait ce truc, on pourrai pas y greffer un système d'inclusions automatiques?

    Citation Envoyé par Benjamin Delespierre Voir le message
    Pour l'autoload, utilise celui de composer en créant tes composants de sorte qu'ils soient compatibles composer. Une fois installé sur ton projet, composer t'offre nativement un autoloader puissant.
    Ok, j'y jetterai un œil, c'est light? C'est pas trop contraignant de créer des composants compatibles?

    Merci Benjamin pour ces réponses, j'aurai certainement d'autres questions ensuite...
    @+
    Piero

  4. #4
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 38
    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
    Par défaut
    Oui je sais, mais justement, je me demandais si il ne serait pas intéressant, de faire d'une pierre deux coups, un système "tout-en-un". Mais peut-être que je prends mes rêves pour des réalités, et qu'on ne peux pas faire "correspondre" les deux. Je dois encore y réfléchir en tout cas.
    Le système tout-en-un est à l'opposé de l'approche orientée composant.

    BTW, l'autoload c'est vraiment vraiment simple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    <?php
     
    register_autoload_function(function ($classname) {
      return include "{$classname}.php";
    });
    et c'est tout. Si tes composants / librairies sont dans l'include path, leur chargement sera automatique à condition que chaque classe soit dans un fichier séparé NomDeLaClasse.php

    Faire un composant compatible composer c'est vraiment simple, lis la doc, rien à voir avec PEAR

  5. #5
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Salut Benjamin,

    J'ai lu la doc que tu m'as passé, et je me suis lancé. J'ai installé composer puis via ce dernier Pimple, aucun problème à ce niveau... Ensuite, j'ai tenté un truc, et là je me fais des nœuds au cerveau.
    Citation Envoyé par Benjamin Delespierre Voir le message
    Le système tout-en-un est à l'opposé de l'approche orientée composant.
    Ma foi, le but c'est pas le tout en un, mais disons que je voulais faire un système de require avec pimple pour certains fichiers. D'ailleurs un truc dans le style de l'injecteur de dépendances que tu présentes sur ton site:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $app->addModel('articles', function () use ($app) {
        include_once $app['models_path'] . '/articles.php';
        return new Articles($app['article']);
    });
    Citation Envoyé par Benjamin Delespierre Voir le message
    BTW, l'autoload c'est vraiment vraiment simple
    Je connais l'autoload, mais je préfère ne l'utiliser que pour les fichiers dont j'ai le moins besoin.
    Citation Envoyé par Benjamin Delespierre Voir le message
    Faire un composant compatible composer c'est vraiment simple, lis la doc, rien à voir avec PEAR
    Oui, j'ai jeté un œil, vite fait, et c'est vrai que ça a l'air pas trop compliqué. Pour l'instant j'en ai plus l'utilité pour importer des trucs, mais je ferai mes composants dans quelques temps...

    J'ai quelques problèmes avec pimple, je posterai mon code tout à l'heure.
    @+

  6. #6
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 38
    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
    Par défaut
    Je connais l'autoload, mais je préfère ne l'utiliser que pour les fichiers dont j'ai le moins besoin.
    Il n'y a pas de réelle différences de perfs entre l'usage d'un autoloader et l'inclusion classique: http://blog.ircmaxell.com/2012/07/is...-solution.html

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $app->addModel('articles', function () use ($app) {
        include_once $app['models_path'] . '/articles.php';
        return new Articles($app['article']);
    });
    Ce bout de code était là pour illustrer qu'on pouvait déterminer au runtime le composand modèle à charger sans pour autant rentrer dans le détail de ce qu'il faut vraiment faire en réalité: utiliser les namespaces et laisser l'autoloader se démerder. Comme le but de l'article c'était pas d'expliquer les namespaces et que ça aurait pris trop de temps d'aller jusqu'au bout (j'essaie de ménager mes lecteurs) j'ai utilisé une just-in-time inclusion, ce qui n'est pas franchement beau. Je vais peut être changer ça...

    Donc Pimple n'a pas besoin de charger les composants, il n'est d'ailleurs qu'un conteneur qui sert à faire de l'injection de dépendances, en réalité c'est pas lui qui fait l'injection

    Dans une architecture propre, c'est l'autoloader qui s'occupe de tout.

  7. #7
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut chargeur de foos
    Donc voilà, je veux faire un système de chargement automatique de foos.
    Le foo à charger dépend (par défaut) d'une variable super globale.
    index.php (le départ):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    //todo delete
    error_reporting(E_ALL);
    ini_set('display_errors','1');
     
    require 'core/tools/FooCtx.php';
    $ctx=new \Core\FooCtx();
    $foo=$ctx['GET_FOO']();
    FooCtx (le chargeur de foo à proprement parler, mais il aura également d'autres attribution par la suite)
    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
    <?php
    namespace Core;
    require 'Context.php';
     
    class FooCtx extends Context{
     
        public function __construct() {
            parent::__construct();
            $this['FOO_PATH']='foo/';
            $ctx=$this;
            $this['GET_FOO']=function($fooName=NULL) use ($ctx){
                var_dump($fooName);
                if(empty($fooName))
                        $fooName=  end($ctx['URI']);
                $ctx['REQUIRE']($ctx['FOO_PATH'].$fooName.'.php');
                return new $fooName($ctx);
            };
        }
    }
     
    ?>
    Puis Context, qui est un chargeur plus générique, et qui aura également d'autres attributions ensuite:
    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
    <?php
    namespace Core;
    require 'vendor/pimple/pimple/lib/Pimple.php';
     
    class Context extends \Pimple{
     
        public function __construct() {
            $this['REQUIRED_CLASS_FILES']=array();
            $ctx=$this;
            $this['REQUIRE']=function($file) use ($ctx){
                //var_dump($file);
                if(!array_key_exists($file, $ctx['REQUIRED_CLASS_FILES'])){
                    require $file;
                    $ctx['REQUIRED_CLASS_FILES'][$file]=TRUE;
                }
            };
            //Storing URI parameters
            $uri=array();
            $i=1;
            while (!empty($_SERVER['REDIRECT_arg'.$i]))
                    $uri[]=$_SERVER['REDIRECT_arg'.$i++];
            $this['URI']=$uri;
        }
    }
     
    ?>
    Mon foo de test se contente pour l'instant de faire un echo hello world.
    Mon problème se situe au niveau de la classe FooCtx, pour GET_FOO:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $this['GET_FOO']=function($fooName=NULL) use ($ctx){
                var_dump($fooName);
    J’appelle la closure dans index.php de cette manière:
    Je m'attendais donc à ce que $fooName soit NULL. Or, il n'en est rien $fooName est une instance de FooCtx, ce que je ne m'explique pas. Serait-ce du à Pimple? Je cherche pour l'instant dans le code de pimple pour essayer de comprendre. Mais j'ai peut-être fait quelque chose d'une mauvaise manière. Aurait-tu une idée?

    Problème n°2: De quel manière (juste les grandes lignes), tu mettrais en place des tests unitaires pour tester ce code? J'avoue que je sais pas trop par où commencer.
    Merci,
    @+
    Piero

  8. #8
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Citation Envoyé par Benjamin Delespierre Voir le message
    Il n'y a pas de réelle différences de perfs entre l'usage d'un autoloader et l'inclusion classique: http://blog.ircmaxell.com/2012/07/is...-solution.html
    Alors ça , j'aurai juré que l'autoloader était bien pire que ça. Je vais faire mes propres bench pour en avoir le cœur net.
    Citation Envoyé par Benjamin Delespierre Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $app->addModel('articles', function () use ($app) {
        include_once $app['models_path'] . '/articles.php';
        return new Articles($app['article']);
    });
    Ce bout de code était là pour illustrer qu'on pouvait déterminer au runtime le composand modèle à charger sans pour autant rentrer dans le détail de ce qu'il faut vraiment faire en réalité: utiliser les namespaces et laisser l'autoloader se démerder. Comme le but de l'article c'était pas d'expliquer les namespaces et que ça aurait pris trop de temps d'aller jusqu'au bout (j'essaie de ménager mes lecteurs) j'ai utilisé une just-in-time inclusion, ce qui n'est pas franchement beau. Je vais peut être changer ça...

    Donc Pimple n'a pas besoin de charger les composants, il n'est d'ailleurs qu'un conteneur qui sert à faire de l'injection de dépendances, en réalité c'est pas lui qui fait l'injection

    Dans une architecture propre, c'est l'autoloader qui s'occupe de tout.
    Ok je vais revoir mon code (et peut être mes "mauvaises" habitudes)
    @+

  9. #9
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Citation Envoyé par BPiero Voir le message
    Alors ça , j'aurai juré que l'autoloader était bien pire que ça. Je vais faire mes propres bench pour en avoir le cœur net.
    J'ai fait mes tests et j'arrive pas du tout aux mêmes conclusions que lui (j'avoue que je m'en doutait un peu, c'est pour ça que j'ai fait mes tests). Mes require sont 10X plus rapides que les siens et mes autoload 4X plus lents pour 1000 classes. En dessous, j'ai quelque chose d'assez linéaire pour les 2 méthodes, contrairement à lui, certainement à cause de ce qu'il a voulu tester (il a pas viré les requires inutiles, mais c'était pour illustrer le chargement en dur de dur, du genre dans un fichier mesRequires.php où il charge toutes les classes de son projet, enfin j'imagine...). Moralité, il en déduit que l'autoload est plus rapide en dessous de 500 classes. Mes chiffres donnent, avec sa méthodologie, que 20 autoloads équivalent à 1000 requires, donc que les autoload sont plus rapides en dessous de 20 classes (Si on charge systématiquement toutes les 1000 classes avec les requires).
    Conclusion: Je pensais que ça serait quand même encore pire pour les autoloads, donc je serais prêt à sacrifier 0.05s de temps moyen de chargement de page et quelques Mo de mémoire serveur pour des moyens/gros projets si ça doit me faciliter la vie. Les requires restent une bonne piste pour l'optimisation sur un site un peu lourd.
    @+
    Piero

  10. #10
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 38
    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
    Par défaut
    Même Symfony2 ne charge pas 500 classes par requête au Runtime, ton test est inutile car il ne reflète pas la réalité. Si tu as 1000 classes à charger, c'est pas l'autoload ton problème, c'est ton projet tout entier qui est à revoir.

    En réalité, on se fiche pas mal des 3% de perfs que font perdre l'usage d'un autoloader, c'est très largement compensé par la flexibilité qu'il apporte. Si tu travailles sous forte charge, tu peux mettre en place des solutions de cache d'opcode pour booster les performances et des serveurs web plus performants qu'Apache comme Ngnix, c'est ce qui est fait en règle générale. Rappelles-toi qu'une optim qui fait gagner 3% n'est pas une bonne optim, on optimise pour gagner 100% de perfs ou plus.

  11. #11
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Citation Envoyé par Benjamin Delespierre Voir le message
    Rappelles-toi qu'une optim qui fait gagner 3% n'est pas une bonne optim, on optimise pour gagner 100% de perfs ou plus.
    Effectivement, donc on peut dire que les requires sont rarement une bonne optim, enfin bref... C'est du pinaillage, comme je le disais dans la conclusion de mes tests, même si il s'agit pas que de 3%, et comme tu le souligne si justement, on est loin des 100%.

  12. #12
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Pour mon code j'ai trouvé ou était l'erreur, je pensais que les services de pimple étaient de bêtes closures, mais pas du tout. Ils ne peuvent pas prendre d'argument. J'ai donc contourné en prenant exemple dans ton site, je déclare tout simplement une fonction classique dans mon conteneur. En ce qui concerne les requires, j'utilise la méthode de composer, j'ai un peu galéré pour comprendre le fonctionnement des chemins/namespace, mais tout est ok maintenant. Par contre en ce qui concerne mes tests, et plus particulièrement le TDD, je suis toujours un peu paumé (mais j'ai quelques idées). Je vais poster mon nouveau code, et donner les pistes auxquelles j'ai pensé pour le tdd.

  13. #13
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    <?php
    require 'vendor/autoload.php';
    $ctx=new \Core\Tools\FooCtx();
    $ctx['run']();
    ?>
    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
    <?php
    namespace Core\Tools;
     
    class FooCtx extends Context{
     
        public function __construct() {
            parent::__construct();
            $this['fooNs']='\Foo\\';
            $this['run']=function($c){
                $foo=$c->getFoo();
                $foo->run();
            };
        }
     
        public function getFoo($fooName=NULL){
            if(empty($fooName)){
                $uri=$this['uri'];
                $fooName=  end($uri);
            }
            $class=$this['fooNs'].$fooName;
            return new $class($this);
        }
    }
     
    ?>
    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
    <?php
    namespace Core\Tools;
     
    class Context extends \Pimple{
     
        public function __construct() {
            //Storing URI parameters
            $uri=array();
            $i=1;
            while (!empty($_SERVER['REDIRECT_arg'.$i]))
                    $uri[]=$_SERVER['REDIRECT_arg'.$i++];
            $this['uri']=$uri;
        }
    }
     
    ?>
    déjà c'est plus succin (c'est plutôt bon signe, mais c'est aussi grace à composer et l'autoloader). Enfin bref, mon problème maintenant c'est par rapport aux tests:
    J'aurai peut-être du écrire mes tests d'abord, mais bon je savais pas trop comment faire et il faut bien commencer.
    Maintenant je veux faire des tests sur ce code.
    En ce qui concerne le premier fichier (index.php) je pense qu'il n'a pas à être testé (c'est aussi pour ça qu'il est si court), je mettrais l'équivalent dans le setUp des tests.
    Pour ce qui est des Foo, il ne sont pas encore implémentés, donc je peux faire des espions qui se chargeront de vérifier que les foos sont bien chargés. Le problème c'est le reste. Je suis en train de réfléchir à ce que je dois en faire.
    Comment on fait des tests avec l'héritage? on teste le plus générique et on fait passer les mêmes tests aux enfants, puis etc? peut on mocker un parent? Je réfléchit à tout ça mais je suis sûr de rien.

  14. #14
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 38
    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
    Par défaut
    Y'a deux trois choses qui vont pas:
    • ne mets pas ?> à la fin de scripts qui ne contiennent que du PHP, c'est dangereux car ça risque de déclencher l'envoi de headers
    • tes classes Core ne sont manifestement pas des classes Core, elles doivent être complètement génériques donc on ne doit pas y retrouver de contraintes liées à l’environnement. Par exemple tester des clés de $_SERVER est une contrainte liée à l’environnement.
    • généralement, la classe Context au niveau Core est une interface car il y aura logiquement plusieurs contextes complêtement différents dans ton application (HTTPContext, CacheAwareContext, CLIContext, StdinAwareContext etc.)
    • les noms de tes classes ne sont pas du tout parlant (surtout FooCtx.. vu comme ça on ne sait même pas ce que c'est...) Un développeur qui utilise ton code devrait être capable d'imaginer son fonctionnement sans avoir à lire le code des classes. Par exemple la classe Application, tout le monde sait globalement à quoi elle sert, pareil pour Session ou HTTPRequest & HTTPResponse.


    On ne teste pas l'héritage, seules les classes concrètes (celles qui seront finalement utilisées sont testables). On ne modifie pas non plus la hiérarchie d’héritage pour les tests, ce serait un non-sens. Il faut au contraire modifier la hiérarchie de dépendances entre les composants si nécessaire (par exemple pour mocker - émuler un comportement non déterministe).

    En ce qui concerne le premier fichier (index.php) je pense qu'il n'a pas à être testé (c'est aussi pour ça qu'il est si court), je mettrais l'équivalent dans le setUp des tests.
    Le fichier qui attache les composants les uns aux autre, les configure et les déclenche (aussi appellé glue-code) n'a pas à être testé autrement que comportementalement (c'est à dire au niveau de l'expérience utilisateur).

    Pour ce qui est des Foo, il ne sont pas encore implémentés
    Mais c'est quoi ça un Foo ??

  15. #15
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Depuis mon dernier post j'ai bien avancé, j'ai écrit des test, refactoré, réecrit des test rerefactoré etc... Je posterai mon nouveau code après prise en compte des remarques de Benjamin.
    Citation Envoyé par Benjamin Delespierre Voir le message
    ne mets pas ?> à la fin de scripts qui ne contiennent que du PHP, c'est dangereux car ça risque de déclencher l'envoi de headers
    Je connais ce danger (donc je fais toujours attention à ce qu'il n'y ai rien après -ni espace ni saut de ligne- mais ce que je ne savais pas, c'est qu'ils étaient optionnels... Merci, c'est super utile, je les supprime.
    Citation Envoyé par Benjamin Delespierre Voir le message
    tes classes Core ne sont manifestement pas des classes Core, elles doivent être complètement génériques donc on ne doit pas y retrouver de contraintes liées à l’environnement. Par exemple tester des clés de $_SERVER est une contrainte liée à l’environnement.
    Oui, je m'en suis rendu compte (d'autant plus en faisant mes tests). Je souhaites un système d'analyse de $_SERVER réutilisable dans toute l'appli. Au niveau tests, j'imagine que je pourrai mocker. je pense que je vais dédier une classe pour ça, cependant je l'aurai bien placée dans le core, car c'est une fonctionnalité de base réutilisée à plusieurs endroits de mon appli. Penses-tu que ce soit une mauvaise chose de la mettre dans le core?
    Si oui, tu la mettrai où par ex?
    Citation Envoyé par Benjamin Delespierre Voir le message
    généralement, la classe Context au niveau Core est une interface car il y aura logiquement plusieurs contextes complêtement différents dans ton application (HTTPContext, CacheAwareContext, CLIContext, StdinAwareContext etc.)
    En fait je sais pas trop pourquoi je l'ai nommée comme ça (j'ai du voir ça sur le net pour du java), mais il s'agit tout simplement d'un conteneur d'injection de dépendance, d'un fournisseur de services et de variables globales, dont la spécialisation dépend du contexte, mais pas dans ce sens, plutôt disons un contexte défini au niveau http (dans le .htaccess par ex). Mais ce n'est peut être pas très parlant, je pense le renommer en container, penses-tu que ce soit plus approprié?
    Est-ce parlant si j'abrège en $c pour l'instance?
    Citation Envoyé par Benjamin Delespierre Voir le message
    les noms de tes classes ne sont pas du tout parlant (surtout FooCtx.. vu comme ça on ne sait même pas ce que c'est...) Un développeur qui utilise ton code devrait être capable d'imaginer son fonctionnement sans avoir à lire le code des classes. Par exemple la classe Application, tout le monde sait globalement à quoi elle sert, pareil pour Session ou HTTPRequest & HTTPResponse.
    Citation Envoyé par Benjamin Delespierre Voir le message
    Mais c'est quoi ça un Foo ??
    Un Foo, c'est un truc qui fait des bidules J'aurai pu mettre un toto, bar, baz, lorem ipsus... Non, plus sérieusement, c'est une abstraction, un nom métasyntaxique. Je l'utilise:
    • pour me concentrer plus sur le fond que sur la forme
    • parce qu'il s'agit d'une spécialisation que je n'ai pas encore implémenté (mais il y aura concrètement plusieurs de ces spécialisations, et il faut bien tester quelque chose. Pour être plus précis, il s'agit d'une sorte de double dispatch.
    • Parce que je ne peux pas dévoiler certains détails de l'implémentation, ni la finalité réelle de l'application sur internet.

    Alors j'imagine que tu dois te dire que ça te fait une belle jambe, qu'on est pas rendu si on travaille sur une appli dont on ne connais pas le but... J'espère que tu ne le prendra pas mal, mais généralement, quand on utilise ce genre de nommage, c'est pour qu'il soit tacitement reconnu comme tel par les autres devs, bref, tu ne devrais pas me demander ce qu'est un Foo.
    Cependant, comme tu m'es fort sympathique, et que je pense que ce serai la moindre des choses que de satisfaire ta curiosité, je te le dirai le moment venu par mail.
    Citation Envoyé par Benjamin Delespierre Voir le message
    On ne teste pas l'héritage, seules les classes concrètes (celles qui seront finalement utilisées sont testables).
    C'est ce que j'ai fait pour l'instant... Question subsidiaire, à quoi sert donc:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    PHPUnit_Framework_TestCase::getMockForAbstractClass();
    Citation Envoyé par Benjamin Delespierre Voir le message
    On ne modifie pas non plus la hiérarchie d’héritage pour les tests, ce serait un non-sens. Il faut au contraire modifier la hiérarchie de dépendances entre les composants si nécessaire (par exemple pour mocker - émuler un comportement non déterministe).
    C'est ce que je fais pour l'instant, mais est-il possible:
    1. D'écrire un test ContainerTest (abstraite?) héritant de PHPUnit_Framework_TestCase pour ma classe Container (tu sais, l'ancienne classe Context, )
    2. D'écrire un test FooCtnrTest héritant de ContainerTest, qui reprendrai donc les test du parent, pour ma classe FooCtnr (tu sais, l'ancienne classe FooCtx...), tout en y ajoutant les tests spécifiques aux Foo (pour l'instant, il n'y a rien de spécifique aux Foos bien sûr puisque c'est une abstraction, hormis l'instanciation du Foo et l'enregistrement de cette instance dans le conteneur)

    Citation Envoyé par Benjamin Delespierre Voir le message
    Le fichier qui attache les composants les uns aux autre, les configure et les déclenche (aussi appellé glue-code) n'a pas à être testé autrement que comportementalement (c'est à dire au niveau de l'expérience utilisateur).
    , c'est bien le cas de mes containers, pourtant vu qu'il s'agit d'un composant central, j'aurai préféré le tester plutôt deux fois qu'une et dans tous les sens, et tout au long du développement de mon appli. J'aimerai que tu m'en dise plus à ce sujet:
    • Pourquoi?
    • Comment faire autrement?
    • Faut-il les mocker?
    • Si je ne les teste pas, puis-je y mettre des super globales telles que $_SERVER?

    Merci Benjamin, pour toutes ces remarques qui me font avancer à pas de géant...
    Piero
    Edit: Je posterai mon code en début d'aprem

  16. #16
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut Mon code
    arborescence:
    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
    /Core
      /Foo
        IFoo.php
      /Tools
        Container.php
        Runnable.php
        UriHandler
    /Foo
      Bar.php
      Foo.php
      FooCtnr.php
    /test
      FooCtnrTest.php
      UriHandlerTest.php
      mockIndex.php
    /vendor
      /composer
        ..
      /pimple
        ..
      autoload.php
    .htaccess
    composer.json
    composer.lock
    foo.php
    Pour les fichiers intéressants:
    • IFoo
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      <?php
      namespace Core\Foo;
       
      interface IFoo extends \Core\Tools\Runnable{
       
          public function __construct(\Foo\FooCtnr $c);
       
      }
    • Container
      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
      <?php
      namespace Core\Tools;
       
      abstract class Container extends \Pimple implements Runnable{
       
          protected $runnable;
       
          protected $uriHandler;
       
          public function __construct(UriHandler $uriHdlr=NULL) {
              $this->uriHandler=(empty($uriHdlr))?new UriHandler():$uriHdlr;
          }
       
          protected function setRunnable(Runnable $runnable){
              $this->runnable= $runnable;
          }
       
          /*
           * Double dispatch using inheritance and namespace
           */
          protected function setRunnableByName($runnableName=NULL){
              if(empty($runnableName))
                      $runnableName=$this->uriHandler->getRunnableName ();
              $containerClassName=get_class($this);
              $ns=substr($containerClassName,0,strrpos($containerClassName,'\\'));
              $className='\\'.$ns.'\\'.ucfirst($runnableName);
              $this->setRunnable(new $className($this));
          }
       
          public function run(){
              if(empty($this->runnable))
                      $this->setRunnableByName();
              $this->runnable->run();
          }
      }
    • Runnable
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      <?php
      namespace Core\Tools;
       
      interface Runnable {
       
          public function run();
      }
    • UriHandler
      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
      <?php
      namespace Core\Tools;
       
      class UriHandler {
       
          private $uri;
       
          public function __construct(Array $server=NULL,$prefix=NULL) {
              if(empty($server))
                      $server=$_SERVER;
              if(empty($prefix)&&$prefix!=='')
                      $prefix='REDIRECT_arg';
              $this->uri=array();
              $i=1;
              while (!empty($server[$prefix.$i]))
                      $this->uri[]=$server[$prefix.$i++];
          }
       
          public function getRunnableName(){
              return end($this->uri);
          }
       
      }
    • Bar
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <?php
      namespace Foo;
       
      class Bar extends Foo{
       
          public function run(){
       
          }
      }
    • Foo
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <?php
      namespace Foo;
       
      abstract class Foo implements \Core\Foo\IFoo{
       
          protected $c;
       
          public function __construct(FooCtnr $c) {
              $this->c=$c;
          }
      }
    • FooCtnr
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <?php
      namespace Foo;
       
      class FooCtnr extends \Core\Tools\Container{
       
          public function __construct(\Core\Tools\UriHandler $uriHdlr=NULL) {
              parent::__construct($uriHdlr);
          }
       
          public function setFoo($foo=NULL){
              ($foo instanceof Foo)?$this->setRunnable($foo):$this->setRunnableByName($foo);
          }
      }
    • FooCtnrTest
      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
      <?php
       
      class FooCtnrTest extends PHPUnit_Framework_TestCase
      {
       
          public $index;
       
          public $foo;
       
          public function setUp()
          {
              require_once 'mockIndex.php';
              $this->index  = new mockIndex();
              $this->foo = $this->getMock('\Foo\Bar',array(),array($this->index->c));
          }
       
          public function testRun_Foo_run(){
              $this->index->c->setFoo($this->foo);
              $this->foo->expects($this->once())->method('run');
              $this->index->c->run();
          }
      }
    • UriHandlerTest
      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
      <?php
       
      class UriHandlerTest extends PHPUnit_Framework_TestCase
      {
       
          public function setUp()
          {
              require_once __DIR__ .'/../vendor/autoload.php';
          }
       
          public function testGetRunnableName_withOneWord(){
              $word='test';
              $uriHdlr= new \Core\Tools\UriHandler(array('1'=>$word),'');
              $this->assertEquals($uriHdlr->getRunnableName(),$word);
          }
      }
    • mockIndex
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <?php
       
      class mockIndex {
       
          public $c;
       
          public function __construct($fooName='bar') {
              include __DIR__ .'/../vendor/autoload.php';
              $server=array('1'=>$fooName);
              $uriHdlr=new \Core\Tools\UriHandler($server);
              $this->c=new \Foo\FooCtnr($uriHdlr);
          }
      }
    • foo (mon index, il faut comprendre par là que c'est le fichier d'entrée en cas de foo -cas défini dans le .htaccess- mais qu'il ne sera pas destiné à être l'unique fichier d'entrée, par ex baz.php avec $ctx=new \Baz\BazCtnr();)
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      <?php
      //TODO delete
      error_reporting(E_ALL);
      ini_set('display_errors','1');
       
      require 'vendor/autoload.php';
      $ctx=new \Foo\FooCtnr();
      $ctx->run();

  17. #17
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 38
    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
    Par défaut
    ça commence à prendre forme

    Y'a plus qu'une seule chose qui me dérange, c'est les inclusions qui sont faites dans les méthodes des composants, c'est pas top. Inclure un fichier explicitement revient à introduire du couplage. Comme mentionné précédemment, on aurait eu meilleur compte d'utiliser le nom du composant ou de la classe et de laisser l'autoloader faire son travail.

  18. #18
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut
    Citation Envoyé par Benjamin Delespierre Voir le message
    ça commence à prendre forme
    Merci, je trouve aussi, même si j'hésite encore à revoir certaines choses... Au niveau des containers, je me demande si les méthodes et attributs ne seraient pas mieux en services et paramètres Pimple classiques, ça permettrai p-ê de gagner encore en découplage...
    vs (dans le constructeur)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    $this['uriHandler']=(empty($uriHdlr))?
      $this->share(
         function($c){
            return new UriHandler();
         }
      ):
      $uriHdlr;
    Il me semble que mon container gagnerai en souplesse... Enfin je dois encore y réfléchir, et ça tombe bien j'ai une semaine de vacances pour ça
    Citation Envoyé par Benjamin Delespierre Voir le message
    Y'a plus qu'une seule chose qui me dérange, c'est les inclusions qui sont faites dans les méthodes des composants, c'est pas top. Inclure un fichier explicitement revient à introduire du couplage. Comme mentionné précédemment, on aurait eu meilleur compte d'utiliser le nom du composant ou de la classe et de laisser l'autoloader faire son travail.
    Là j'avoue que je te suis plus... Quelles inclusions? On peut éviter le
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    require 'vendor/autoload.php';
    ?
    Ou alors tu voulais dire pour les tests? Que je mettes l'autoloader de composer et qu'il me trouve les mocks? avec un namespace Test? Peux-tu préciser?
    Merci pour tout, je reviens la semaine prochaine, mais d'ici là, je risque ne pas avoir le temps d'aller sur internet.
    @+ Piero

  19. #19
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 38
    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
    Par défaut
    Au niveau des containers, je me demande si les méthodes et attributs ne seraient pas mieux en services et paramètres Pimple classiques, ça permettrai p-ê de gagner encore en découplage...
    Il faut que tu voies si c'est justifié. Il est inutile de banaliser (apporter de la généricité) à outrance au risque de ne plus arriver à exprimer simplement un besoin métier. En somme, la généricité c'est bien mais il faut savoir s'arrêter à niveau acceptable sinon ton code c'est juste de la pâte à modeler et ça ne sert plus à rien de concret.

    L'inclusion de l'autoloader devrait se faire au niveau du front controller (index.php). En somme, il devrait ressembler à ça ton index:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    include "vendor/autoload.php";
    include "boostrap.php";
     
    $app = new Application($config);
    $app->run();
     
    // c'est tout !
    Pour tester, on peut soit changer la définition des services (qui devrait prendre place dans boostrap.php ou entre le new et le run de Application) soit carrément utiliser un autre front-controller (index.test.php)

  20. #20
    Membre très actif
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mars 2011
    Messages
    154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ariège (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mars 2011
    Messages : 154
    Par défaut Demande de dernières précisions
    Salut,
    Je suis revenu de vacances...
    • J'ai un peu de mal à définir la limite du découplage
      Si j'ai bien compris des classes qui sont destinées à travailler ensemble doivent être couplées, si il s'agit mettons de modules différents, de haut/bas niveau elles doivent être découplées?
      Tu peux confirmer? (où infirmer et entrer un peu plus dans le détail)
      Par exemple pour mon uriHandler que j'estime finalement assez spécifique et associé à mon container, je peux coupler?
    • Donc pour bootstrap, c'est simplement un fichier php qui contient un tableau associatif $config avec tous les paramètres et services utilisateur, qui viennent disons surcharger ceux que j'ai défini par défaut dans le constructeur d'Application?
      J'ai juste?
    • En ce qui concerne les tests, je comprends pas très bien ce que tu préconises, mais peut-être que c'est moi qui m'y prends mal... Ce que tu propose c'est simplement des methodes pour faciliter les tests fonctionnels, c'est ça?

    Je suis désolé si mes questions te paraissent triviales, mais pour moi c'est tout nouveau, et d'une part les tutos que j'ai lu ne répondent pas à toutes mes questions, d'autre part je n'ai pas suffisamment de temps/motivation pour approfondir les cours un peu obscurs que je trouve sur internet.
    Dans tous les cas grâce à toi je m'en sort quand même, merci (encore une fois) pour tes réponses, qui, si elles me font pas mal gamberger, ont le mérite de me faire progresser rapidement et (je l'espère) dans la bonne direction.
    @+
    Piero

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [EJB3] [JBoss] Injection de dépendance circulaire ?
    Par Claythest dans le forum Java EE
    Réponses: 6
    Dernier message: 04/08/2009, 08h11
  2. [EJB3] Injection de dépendance et Stateful
    Par newbeewan dans le forum Java EE
    Réponses: 1
    Dernier message: 15/05/2007, 07h33
  3. [Integration] [EasyMock] Injection de dépendance à l'éxécution
    Par frangin2003 dans le forum Spring
    Réponses: 2
    Dernier message: 06/03/2007, 11h06
  4. Spring + TagSupport et injection de dépendance
    Par worldchampion57 dans le forum Spring Web
    Réponses: 2
    Dernier message: 26/02/2007, 09h01

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