Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 27-07-2009 14:15:14

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

[Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Bonjour,

Je suis en train d'implémenter une couche de services pour mes projet avec ZF.

Il existe plusieurs solutions pour faire ça: Singleton, super controlleur, helper d'action.

Je m'étais d'abord tourné vers des Singleton, mais finalement j'ai décidé d'utiliser les helper d'actions.
Mais d'une manière un peu particulière. Je souhaite faire de l'inversion de contrôle et donc créer mes objets de services de mes controlleurs depuis un helper.

Voilà à quoi j'ai pensé un m'inspirant un peu du fonctionnement de Bean en Java.

Je pensais créer une ressource "service" que j'initialise à l'aide d'une classe "Tight_Application_Resource_Service" et dans laquelle je charge tous les services disponibles depuis "application.ini" (ou autre, xml par ex.) dans ma ressource.

Nous aurions quelque chose comme :

Code:

resources.service.services.user-service.property = "userService"   ; nom de la propriété dans le controlleur
resources.service.services.user-service.className = "Service_UserService"

resources.service.services.news-service.property = "newsService"   ; nom de la propriété dans le controlleur
resources.service.services.news-service.className = "Service_NewsService"

; etc.

Dans les controlleurs nous aurions:

Code:

<?php
class News_CreateController extends Zend_Controller_Action
{        
        protected $_newsService;
    
        public function setNewsService(Service_NewsService $newsService) {
              $this->_newsService = $newsService;        
        }

        // suite du controlleur

}

Et enfin un helper d'action "Tight_Controller_Action_Helper_Service" dans lequel nous testerions si des propriétés existantes dans la liste de services de notre resource service sont déclarées dans le controlleur. Dans ce cas, nous instancions la classe de service et nous passons l'objet au controlleur via le setter approprié et qui est sensé exister dans le controlleur (sinon exception).

Avec pourquoi pas une option dans les controlleurs du genre $useServices = true/false pour ne pas effectuer systématiquement tout le traitement dans le helper quand ce n'est pas nécessaire.

Les classes de services seront elles stockées dans un dossier "services" (comme pour models) puisque c'est déjà prévu dans l'autoloader de ZF si on regarde le code (Zend_Application_Module_Autoloader, méthode initDefaultResourceTypes()).

Code:

'service' => array(
    'namespace' => 'Service',
    'path'      => 'services',
),

Qu'en pensez-vous ?


A+ benjamin.

Dernière modification par Delprog (27-07-2009 20:28:42)


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#2 27-07-2009 15:16:53

Vincent
Administrateur
Date d'inscription: 19-09-2008
Messages: 510

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Salut Benjamin,

Qu'entends tu par "service" ?


aka miboo

Hors ligne

 

#3 27-07-2009 15:27:40

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Je parle d'une couche de Services pour englober la logique de l'application.

Les services sont chargés de la rencontre entre les objets métiers et la persistance (mapper + gateway).

C'est le pattern Service Layer. Les controlleurs consomment les services et ne font jamais appel à la couche DAO. Ils ne font qu'échanger des objets métiers avec les services aux travers des méthodes qu'ils consomment.

Exemple, j'ai un service NewsService, je veux ajouter une news. Dans mon controlleur je crée l'objet métier de la news, je lui set toutes ses propriétés, ensuite je fais appelle à la méthode addNews() que me met à disposition mon service et qui attend en paramètre mon objet métier.
Le service se charge de faire appel à ce qu'il faut pour insérer mon objet dans la persistance (mapper + db_table par ex.).

La problématique que j'essaie de résoudre ici et de mettre à disposition les services dans les controlleurs sans que ceux-ci aient besoin de les instancier directement. Celà pour éviter les dépendances.

A+ benjamin.


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#4 27-07-2009 20:27:25

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Je vous propose une première implémentation de ce que je propose.

=========================================================

Les services s'ajoutent dans application.ini:

Code:

;====== Resource service (Service Layer)
resources.service.services.test-service.id = "testService"                  
resources.service.services.test-service.property = "testService"            ; nom de la propriété dans le controlleur
resources.service.services.test-service.className = "Service_TestService"

=========================================================

Tight_Service
Classe qui sert de container de services (plus ou moins un catalogue) et qui peut être alimenté.

Code:

<?php
/**
* Service Layer Pattern implementation
* 
* Define a service container to store
* all available services through application 
*
* @category Tight
* @package Tight_Service
* @author    Benjamin Dulau
*/
class Tight_Service 
{
    
    /**
     * Array of registered services
     *
     * @var array
     */
    protected $_services = array();

    
    public function __construct() {

    }    

    /**
     * Add a service to the services container     
     *
     * @param  array  $service config array for provided service     
     * @return void
     */
    public function addService(array $service = null) {
                
        if (is_array($service) && isset($service['id'])) {                    
            if (!array_key_exists($service['id'], $this->_services)) {
                                
                if (!isset($service['property']) || empty($service['property'])) {
                    throw new Tight_Exception_Service($service['property'] . Tight_Exception_Service::SERVICE_PROPERTY_NEED);
                    return;            
                } 
                                
                if (!isset($service['className']) || !class_exists($service['className'])) {
                    throw new Tight_Exception_Service($service['className'] . Tight_Exception_Service::SERVICE_CLASS_UNDEFINED);
                    return;
                }                                
                                
                $this->_services[$service['id']] = array(
                    'property'  => $service['property'],
                    'className' => $service['className']
                );
            }
        }        
    }
    
    /**
     * Return services list array
     * 
     * @return array
     */
    public function getServices() {
        
        return $this->_services;
        
    }
}

=========================================================

Tight_Service_Service
Classe qui définit un composant de service (un service), pourrait être étoffée, pourrait devenir une interface, je n'ai pas encore réfléchi à une composition précise et universelle d'un service.

Code:

<?php
/**
 * Tight_Service_Service
 * Service component
 * 
 * @category   Tight
 * @package    Tight_Service
 * @subpackage Service
 * @author     Benjamin Dulau
 */
class Tight_Service_Service 
{}

=========================================================

Tight_Application_Resource_Service
Ensuite, j'ai créé une classe de resource qui va initialiser mes services:

Code:

<?php
/**
 * Service resource
 *
 * @category   Tight
 * @package    Tight_Application
 * @subpackage Resource
 * @author     Benjamin Dulau
 */
class Tight_Application_Resource_Service extends Zend_Application_Resource_ResourceAbstract
{
    /**
     * @var Tight_Service
     */
    protected $_service;
    
    protected $_options = array();

    /**
     * Initialize Service
     * 
     * @return Tight_Service
     */
    public function init() {
        $service = $this->getService();
        
        // TODO: voir si options possibles pour les services
        $this->_options = $this->getOptions();

        if (is_array($this->_options['services'])) {
            foreach($this->_options['services'] as $svc) {
                   $this->_service->addService($svc);
            }
        }

        // TODO: paramétrage de cette partie pour plus de souplesse et
        // ne pas imposer un helper ? 
        Zend_Controller_Action_HelperBroker::addHelper(new Tight_Controller_Action_Helper_Service());

        return $service;
    }

    /**
     * Retrieve Service instance
     * 
     * @return Tight_Service
     */
    public function getService() {
        if (null === $this->_service) {
            $this->_service = new Tight_Service();
        }
        return $this->_service;
    }
}

=========================================================

Tight_Application_Resource_Service

Code:

<?php
/**
 * Provide IOC for services into Controller_Action
 *
 * @uses       Zend_Controller_Action_Helper_Abstract
 * @category   Tight
 * @package    Tight_Controller
 * @author       Benjamin Dulau
 */
class Tight_Controller_Action_Helper_Service extends Zend_Controller_Action_Helper_Abstract 
{          
    // TODO: Option pour activer/désactiver les services et ne pas
    // effectuer le traitement quand ce n'est pas nécessaire
    
    public function init() {                
        $bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
        $serviceResource = $bootstrap->getResource('Service');
        $services = $serviceResource->getServices();
        
        $service = null;
        foreach($services as $svc) {            
            $var = '_' . $svc['property'];
            $class = $svc['className'];
            $setter = 'set' . ucfirst($svc['property']);
            
            // TODO: return false on protected or private properties
            // Fixed into PHP5.3
            if (property_exists($this->_actionController, $var)) {
                $service = new $class();
                if (!method_exists($this->_actionController, $setter)) {
                    throw new Tight_Exception_Service($setter . Tight_Exception_Service::SERVICE_SETTER_UNDEFINED);
                    return;
                }
                $this->_actionController->$setter($service);
            }
        }              
    }
}

=========================================================

Tight_Exception_Service

Code:

<?php
/**
 * Tight Service Exception
 * 
 * @see  Tight_Exception
 * @author Benjamin Dulau
 */
class Tight_Exception_Service extends Tight_Exception
{
    
    const SERVICE_PROPERTY_NEED     =  'Service provided must define a property to use into dependent classes.';
    const SERVICE_CLASS_UNDEFINED   =  'Provided service class doesn\'t exist.';
    const SERVICE_SETTER_UNDEFINED  =  'Property service setter undefined or cannot be found into dependent class.';    
    
}

=========================================================

UTILISATION :

application.ini :

Code:

;====== Resource service (Service Layer)
resources.service.services.test-service.id = "testService"                  
resources.service.services.test-service.property = "testService"            ; nom de la propriété dans le controlleur
resources.service.services.test-service.className = "Service_TestService"

Service :
Les classes de services doivent se trouver dans le dossiers "services" de chaque module (de même que pour models). Le ZF a déjà prévu dans l'autoloader ce type de ressources.

Code:

<?php 
class Service_TestService extends Tight_Service_Service {    

    public function test() {
        echo 'hello world';
    }    

}

Controlleur :

Code:

<?php
class IndexController extends Zend_Controller_Action
{        
    /**
     * @var Service_TestService
     */
    protected $_testService;
        
    public function setTestService(Service_TestService $testService) {
        $this->_testService = $testService;
    }

    
    public function indexAction() {        
        $this->_testService->test();        
    }
}

=========================================================

REMARQUES :

Voilà donc une première implémentation pour représenter ce à quoi je pense. On a donc un gestionnaire de services, des services indépendants, et un automatisme avec injection de dépendances dans les controlleurs.

Et en plus on conserve l'auto-completion sur les services dans les controlleurs.

Il reste pas de mal de choses à voir ou à repenser, déjà quelques problèmes/idées :

- option pour activer/désactiver le traitement quand les services ne sont pas utilisés dans les controlleurs;
- problème lié à property_exists() qui ne fonctionne pas pour les propriétés sur les types static, protected et private. Ce bug est corrigé dans PHP5.3, mais je cherche une méthode propre pour rendre ce code compatible pour les versions inférieures;
- agrémenter le conteneur (gestionnaire) de services pour lui apporter des méthodes pratiques. Par ex. pour accéder au catalogue des services enregistrés;
- définir un service générique sous forme d'interface ou de classe abstraite pourquoi pas pour avoir un point de départ et une implémentation cohérente de tous les services;
- et certainement plein d'autres choses ! :p


Voilà, balancez les critiques !! smile


A+ benjamin.

Dernière modification par Delprog (29-07-2009 12:12:48)


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#5 29-07-2009 09:45:37

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Et bien ça ne déchaine pas les foules smile


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#6 29-07-2009 12:06:03

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Mise à jour du helper:

Code:

<?php
/**
 * Provide IOC for services into Controller_Action
 *
 * @uses       Zend_Controller_Action_Helper_Abstract
 * @category   Tight
 * @package    Tight_Controller
 * @author       Benjamin Dulau
 */
class Tight_Controller_Action_Helper_Service extends Zend_Controller_Action_Helper_Abstract 
{          
    /**
     * @var bool
     */
    protected $_enabled = false;
    
    /**
     * If enabled by the action controller:
     * Browse registered services from Service application resource
     * and inject service components instances into action controller
     * properties provided for this purpose.
     */
    public function preDispatch()
    {                
        if ($this->_enabled) {
            $bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
            $serviceResource = $bootstrap->getResource('Service');
            $services = $serviceResource->getServices();
            
            $service = null;
            foreach($services as $svc) {            
                $var = '_' . $svc['property'];
                $class = $svc['className'];
                $setter = 'set' . ucfirst($svc['property']);
                
                // TODO: return false on protected or private properties
                // Fixed into PHP5.3
                if (property_exists($this->_actionController, $var)) {
                    $service = new $class();
                    if (!method_exists($this->_actionController, $setter)) {
                        throw new Tight_Exception_Service($setter . Tight_Exception_Service::SERVICE_SETTER_UNDEFINED);
                        return;
                    }
                    $this->_actionController->$setter($service);
                }
            }    
        }          
    }
    
    /**
     * Enable services and IOC
     * To be called by action controllers
     */
    public function enable()
    {        
        $this->_enabled = true;
    }
}

Les controlleurs qui consomment des services doivent maintenant activer le système grâce à:

Code:

public function init()
{
    // enable services injections for this action controller    
    $this->_helper->service->enable();
}

A+ benjamin.


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#7 29-07-2009 12:55:42

Vincent
Administrateur
Date d'inscription: 19-09-2008
Messages: 510

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Delprog a écrit:

Et bien ça ne déchaine pas les foules smile

Perso, j'ai encore du mal à voir l'intérêt. Faut que je relise ton code à tête reposé ce week-end big_smile


aka miboo

Hors ligne

 

#8 31-07-2009 16:50:51

keilnoth
Membre
Date d'inscription: 30-08-2008
Messages: 128
Site web

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Tient marrant, on en parlait l'autre jour au boulot. Zend_Di avait été développé pour faire ça et finalement jugé trop complexe et abandonné :

http://blog.astrumfutura.com/archives/3 … nd_Di.html
http://www.ibuildings.com/blog/archives … llers.html

J'suis pas spécialiste du sujet. Mais visiblement, beaucoup s'accordent à dire que ça devrait être implémenté au niveau de PHP et non au niveau du framework étant donné que le framework offre déjà une couche d'abstraction normalement suffisante. smile

Pour ceux que l'injection de dépendances intéresse :
http://martinfowler.com/articles/injection.html


Quelques tutoriaux Zend Framework !

Hors ligne

 

#9 01-08-2009 00:28:22

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Salut,

keilnoth a écrit:

Tient marrant, on en parlait l'autre jour au boulot. Zend_Di avait été développé pour faire ça et finalement jugé trop complexe et abandonné

Zend_Di est, je trouve aussi, trop complexe, tout comme, en comparaison, les composants Mapper qui sont dans les proposals actuellement.
C'est pour ça que je propose une implémentation relativement simple en utilisant les composants existants et non pas un composant à part entière. Il est évident que les développeurs PHP ne sont pas tout à fait prêt pour ce type d'approche. En java l'IDD est omniprésente et évidente pour tout le monde. En PHP, Zend pratique l'IOC au sein même du framework, mais en dehors de la couche d'abstraction, peu de monde s'en soucis smile

Je ne suis pas tout à fait d'accord pour une implémentation native de ces couches là. C'est justement le rôle d'un framework pour moi. Même en java, ce sont des couches sur des couches qui assument ce rôle.

Zend propose justement suffisamment d'outils puissants pour faciliter l'implémentation de telles couches, pour moi il est logique de s'en servir. Après, tout ceci demande beaucoup de réflexion et de toute manière tout le monde ne sera jamais d'accord smile

Pour l'instant j'ai commencé à mettre en pratique ce que j'ai proposé ici, et je suis relativement satisfait et compte bien enrichir cette solution. Après ça n'a pas l'air de motiver plus de monde que ça.
Après tout, ce n'est qu'une proposition simplifiée d'IOC pour une couche particulière (les services), c'est donc limité.


A+ benjamin.


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#10 19-08-2009 19:40:04

Eureka
Membre
Date d'inscription: 18-07-2009
Messages: 81

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Extrêmement intéressant !

Quelques questions et/ou idées :
1/ En charge des objets. Le contrôleur manipule objet métier et service. Ok. Mais concrètement qui est en charge par exemple de la création d'un nouvel objet métier et qui est en charge de la récupération d'un objet existant ?
Dans le premier cas est-ce le contrôleur qui va faire un $myObject = new Model_Object() et le peuplé pour finalement le transmettre au service qui le sauvegardera ?
Et dans le second cas le contrôleur demandera au service de lui retourner un objet, via une instruction du type $myObject = $this->_testService->getObjectById(3) ?

2/ Activation des services via le helper. Si on se soucie peu de la performance, ne serait-il pas plus commode, à la place d'utiliser la méthode init() pour autoriser ou non les services, de définir un format spécifique pour la propriété qui réceptionne le service, et via le Helper de récupérer toutes les propriétés matchant ce format afin de leur fournir la classe de service attendue ? (dans l'idée au final de ne pas avoir à implémenter init() juste pour ça).

Au passage, property_exists() ne peut-il pas être remplacé par une réflexion qui vérifierait si la propriété existe bien et est publique ? (tout comme je pensais à la reflexion pour rapratrier toutes les propriétés attendant un service dans le contrôleur)

J'ai également lu tes participations au post "[ZF 1.7]Objets métiers et model" ( http://www.z-f.fr/forum/viewtopic.php?id=3679 ). De là en découlent du coup des questions annexes.

3/ Service et Mapper : Désormais ce n'est plus le mapper qui se charge de construire l'objet métier par rapport à la persistance, il se chargera seulement de faire des requêtes et d'en transmettre le résultat au Service qui lui construira l'objet métier ?

Si tel est le cas, qu'est ce que va transmettre précisément le mapper ? Un tableau associatif ?

Dernière modification par Eureka (19-08-2009 19:40:35)

Hors ligne

 

#11 20-08-2009 14:00:16

nORKy
Membre
Date d'inscription: 06-03-2008
Messages: 1098

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Des choses intéressantes à apprendre pt etre ici aussi : (et surement sur les liens qu'il donne)
http://www.whitewashing.de/blog/articles/117

Dernière modification par nORKy (20-08-2009 14:01:33)


----
Gruiiik !

Hors ligne

 

#12 21-08-2009 11:08:58

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Eureka a écrit:

1/ En charge des objets. Le contrôleur manipule objet métier et service. Ok. Mais concrètement qui est en charge par exemple de la création d'un nouvel objet métier et qui est en charge de la récupération d'un objet existant ?
Dans le premier cas est-ce le contrôleur qui va faire un $myObject = new Model_Object() et le peuplé pour finalement le transmettre au service qui le sauvegardera ?
Et dans le second cas le contrôleur demandera au service de lui retourner un objet, via une instruction du type $myObject = $this->_testService->getObjectById(3) ?

C'est tout à fait ça. Le controlleur connait l'objet métier, et connait les services qu'il peut consommer (petit détail, avec ma solution et du PHPDoc on a l'auto-completion sur les services). Le service lui, connait le metier, et il sait comment accéder à la persistance au travers des mappers, il va donc assurer la rencontre des deux.

En fait le service a plusieurs rôles. Il permet d'abord de découper et d'organiser correctement la logique de l'application. Il permet de cacher la complexité et l'organisation des modèles au controlleur, tout comme la façade, mais attention, contrairement à la façade les services assurent aussi du traitement, ils ne sont pas juste une interface, ils déterminent la logique applicative.

Un service doit donc fournir des méthodes qui répondent à la fois au métier et à la dynamique de l'application. Le controlleur lui n'a qu'un rôle de contrôle comme son nom l'indique. Il prend les informations de l'utilisateur, les vérifie, et les transmet à un service qui s'occupera du reste. Le controlleur sait ce qui doit être fait d'un objet, mais ne sait pas comment et n'a pas besoin de le savoir.

Le service agit donc dans les deux sens, il est capable de recevoir un objet métier et d'en faire quelque chose sur ordre du controlleur, et dans l'autre sens il doit être capable de retourner n'importe quel objet métier à la demande du controlleur.

Pour moi, et c'est une vision, il y en a d'autres. Les objets métiers sont les seuls entités que tout le monde connait, et ils sont la monnaie courante d'échange. Le controlleur ne devrait jamais rien manipuler d'autre concernant le modèle. Il a des données utilisateurs, il les valide et les filtres, il sait quel objet métier il doit peupler avec et sait sur quel bouton appuyer (méthode de service) pour déclencher la suite des opérations. Il reçoit une réponse, la formate et la rend à l'utilisateur.
Ou, l'utilisateur réclame une information, le controlleur sait à qui la demander (le service) et sous quelle forme il va la recevoir (objet métier) et renvoie tout ça à la vue (utilisateur). Son rôle s'arrête là en ce qui concerne le modèle.

Eureka a écrit:

2/ Activation des services via le helper. Si on se soucie peu de la performance, ne serait-il pas plus commode, à la place d'utiliser la méthode init() pour autoriser ou non les services, de définir un format spécifique pour la propriété qui réceptionne le service, et via le Helper de récupérer toutes les propriétés matchant ce format afin de leur fournir la classe de service attendue ? (dans l'idée au final de ne pas avoir à implémenter init() juste pour ça).

Alors, je change peu à peu d'avis. Je me rend compte que c'est très rare que les services ne soient pas utilisés dans les controlleurs. Je pense donc fournir plutôt une méthode disable() que enable() via le helper. Si on doit alors essayer de gagner la moindre milliseconde, il suffira de désactiver les services sur les controlleur qui ne les utilisent pas, ce qui évitera quelques boucles. Mais là il s'agit vraiment de micro-optimisation qui à mon avis ne rentre même pas en compte.

Pour ta solution, en ce qui concerne les performances, c'est kif kif, il faudra de toute façon parcourir les propriétés pour trouver celles qui match et les services pour y injecter le bon.
Comme je code avec un développeur Java, je voulais me rapprocher le plus possible du fonctionnent des bean, que je trouve d'ailleurs bien pratiques (xml).


Eureka a écrit:

Au passage, property_exists() ne peut-il pas être remplacé par une réflexion qui vérifierait si la propriété existe bien et est publique ? (tout comme je pensais à la reflexion pour rapratrier toutes les propriétés attendant un service dans le contrôleur)

Je ne veux justement pas que les propriétés soient publiques, elles doivent être protégées. La méthode property_exists me semble être la plus appropriée, PHP5.3 parlant bien sûr.

Eureka a écrit:

3/ Service et Mapper : Désormais ce n'est plus le mapper qui se charge de construire l'objet métier par rapport à la persistance, il se chargera seulement de faire des requêtes et d'en transmettre le résultat au Service qui lui construira l'objet métier ?

Si tel est le cas, qu'est ce que va transmettre précisément le mapper ? Un tableau associatif ?

Non, quand le controlleur réclame des données, il invoque le service, le service sait qui doit intervenir dans la persistance, il crée l'objet métier approprié, fait appel au mapper et le mapper peuple l'objet (passage par référence). L'objet est ensuite renvoyé au controlleur.


nORKy a écrit:

Des choses intéressantes à apprendre pt etre ici aussi : (et surement sur les liens qu'il donne)
http://www.whitewashing.de/blog/articles/117

J'avais lu cet article, il y a de l'idée, mais tout ceci ne permet pas d'injecter les services dans les controlleurs.
C'est ce point particulier que j'aborde ici, pas l'IOC au sens large. Ça viendra peut-être après, mais je suppose que Zend se penche déjà sur le problème, et qu'ils ne tarderont pas trop à proposer une solution.

A+ benjamin.


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#13 21-08-2009 14:01:30

Eureka
Membre
Date d'inscription: 18-07-2009
Messages: 81

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Delprog a écrit:

Alors, je change peu à peu d'avis. Je me rend compte que c'est très rare que les services ne soient pas utilisés dans les controlleurs. Je pense donc fournir plutôt une méthode disable() que enable() via le helper. Si on doit alors essayer de gagner la moindre milliseconde, il suffira de désactiver les services sur les controlleur qui ne les utilisent pas, ce qui évitera quelques boucles. Mais là il s'agit vraiment de micro-optimisation qui à mon avis ne rentre même pas en compte.

Pour ta solution, en ce qui concerne les performances, c'est kif kif, il faudra de toute façon parcourir les propriétés pour trouver celles qui match et les services pour y injecter le bon.
Comme je code avec un développeur Java, je voulais me rapprocher le plus possible du fonctionnent des bean, que je trouve d'ailleurs bien pratiques (xml).

Je ne connais pas du tout les bean donc la relation m'échappe.

En revanche pour en revenir aux éventuelles méthodes <disable|enable>(), je pense à un contrôleur qui utiliserait 2 services dans une action et 3 dans une autre action (services communs ou différents d'une action à l'autre) : avec la méthode que tu proposes actuellement tous les services sont instanciés même s'il ne sont pas utilisés.

NB: ce qui suit est plus une tentative d'optimisation sur quelques points et de n'avoir que peu de code à insérer dans le contrôleur.

Pour en revenir à notre histoire, ne serait-il pas intéressant d'intégrer la notion d'action (au sens "action de contrôleur") dans l'activation des services du coup ?

Ou bien peut-être s'orienter vers une approche qui consisterait, lorsque le helper parse les services à utiliser pour un contrôleur, à n'affecter à la propriété du contrôleur non pas l'objet Service (instancié) mais simplement le nom de la classe à utiliser. Dès lors dans le contrôleur on pourrait avoir un fonctionnement tel que :

Code:

class IndexController extends Zend_Controller_Action
{        
    /**
     * @var string|Service_TestService
     */
    protected $_testService;
        
    public function setTestService($testServiceClassName) {
        $this->_testService = $testServiceClassName;
    }
    
    public function getTestService() {
        if (is_string($this->_testService)) {
            $this->_testService = $this->_testService();
        }
        return $this->_testService();
    }

    
    public function indexAction() {        
        $this->getTestService->test();
    }
}

Dès lors le service n'est instancié que si le besoin en se fait ressentir.

Autre point, mais du coup qui va à l'encontre de l'un des atouts de ta proposition, à savoir l'autocomplétion et la visibilité directe des services utilisables, j'aurais plutôt opté pour une injection directe des services sans se soucier de l'instance en cours afin de pas devoir définir les propriétés des services et ne définir qu'une seule méthode pour en ajouter.

Code:

class IndexController extends Zend_Controller_Action
{
    /**
     * @example injection de l'objet Service_TestService
     */
    public function setService(Service_Interface $service) {
        $property = sprintf('_%sService', $service->getName());
        $this->$property = $service;
    }

    public function indexAction() {        
        $this->_testService->test();
    }
}

Et, en poussant le vice de manière croissante on pourrait :
-> combiner les deux exemples de code que j'ai donnés

-> détourner le rôle de l'Helper : le contrôleur se charge d'appeler le Helper pour qu'il lui retourne le service attendu. Ca pourrait être mis en place en utiliser __get() qui dans le cas où la propriété attendue match _<NOM>Service appelle l'Helper puis crée la propriété au sein du contrôleur. Rien ne serait définit dans la config, et n'importe quel contrôleur pourrait appeler n'importe quel service, tout ça avec un simple __get() de défini dans le contrôleur je pense. L'Helper n'est plus en charge de transmettre le service, c'est le contrôleur qui le demande (on perd totalement l'objectif attendu il me semble...):

Code:

class IndexController extends Zend_Controller_Action
{
    /**
     * @example injection de l'objet Service_TestService
     */
    public function __get($name) {
        // if '_<name>Service'
        if (substr($name, 0, 1) == '_' && substr($name, -6) == 'Service') {

            // get service '<name>'
            $serviceName = substr($name, 1, strlen($namen) - 7);
            $service = $this->service()->getService($serviceName);
            
            // set property in controller
            $property = sprintf('_%sService', $serviceName);
            $this->$property = $service;
            
            // renvoit l'objet. Aux appels suivant si l'objet est créé __get() ne sera
            // pas appelé car la propriété existera
            return $this->$property;
        }
    }

    public function indexAction() {        
        $this->_testService->test();
    }
}

Voilà pour l'idée du moment.

Hors ligne

 

#14 27-08-2009 15:02:57

Delprog
Administrateur
Date d'inscription: 29-09-2008
Messages: 670

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

Salut,

La première idée pourrait effectivement faire l'objet d'une amélioration.

Par contre pour le reste, attention, l'injection de dépendances et aussi là (surtout ?) pour faciliter les tests unitaires. Le deuxième ne serait donc pas convenable, mais aussi hors conventions :p

Les conventions veulent qu'à une propriété corresponde un setter et/ou un getter. L'IOC ne fait qu'appeler le setter propre à la propriété à injecter.


A+ benjamin.


http://www.anonymation.com/ - anonymation - Studio de création.
http://code.anonymation.com/ - anonymation - blog - développement et architecture web

Hors ligne

 

#15 23-07-2010 11:17:23

Moimeme
Membre
Date d'inscription: 19-04-2007
Messages: 120

Re: [Réflexion/Proposition]Services, ZF et Injection de dépendances (IOC)

J'ai du mal a piger l'intérêt de l'injection de dépendances depuis le helper.

Quel intérêt par rapport a ca qui est simplissime et qui demande rien d'autre :

Code:

<?php
class News_CreateController extends Zend_Controller_Action
{        
        protected $_newsService;
    
        public function init(){
              $this->_newsService = new Service_NewsService();
        }

        // suite du controlleur

}

Dernière modification par Moimeme (23-07-2010 11:18:21)

Hors ligne

 

Pied de page des forums

Propulsé par PunBB
© Copyright 2002–2005 Rickard Andersson
Traduction par punbb.fr

Graphisme réalisé par l'agence Rodolphe Eveilleau
Développement par Kitpages