Zend FR

Consultez la FAQ sur le ZF avant de poster une question

Vous n'êtes pas identifié.

#1 27-10-2015 11:36:10

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Le serviceLocator dans les services

Bonjour,

Marco Pivetta à récemment créé un petit slider sur les best practise selon sa propre opinion et il est clairement contre l'idée d'avoir le serviceLocatorAwareInterface dans ses services :
https://ocramius.github.io/zf2-best-practices/#/56

Le but n'est pas de lancer un débat, je comprends tout à fait pourquoi il ne le veut pas, cela rend la classe un peu comme une poubelle ou, vu que le service locator peut récupérer n'importe quoi, on ne sais au final plus ce que la classe a besoin.

Ma question est :

J'ai dans pas mal de mes services un besoin d'appeler plein d'autre objets au sein d'un même module.

Ces objets ont parfois besoin de dépendances donc j'ai des factory de ces objets.

Si je ne veux pas utiliser le ServiceLocator dans mes services, alors je n'ai qu'une seule solution pour ramener ces objects : l'injection de dépendance dans le constructeur donc.
Mais parfois selon le service j'ai plus de 7 / 8 objets a charger dans le constructeur. Est-ce un problème, est-ce la bonne solution ?

Ce que je trouve con, c'est qu'on considère que le serviceLocator ne doit pas être appelé dans les controlleur car c'est une mauvaise pratique et que cela sera retiré dans ZF3. Mais aussi que le serviceLocator ne dois pas apparaître dans les services car cela rend les services trop "poubelle". Donc on utilise les serviceLocator que dans les factory au final. Est-ce un anti-pattern ?
Ou est-ce que finalement l'utiliser dans les factory est la seule manière viable de l'utiliser ?


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#2 27-10-2015 18:12:41

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

Bonjour !

Je pense que l'idée est de rendre le code plus testable en mettant uniquement des dépendances fixes et annoncées du départ. Je crois que le service locator va être supprimé plus ou moins dans ZF3 de toutes façons.

Pour les gros objets, je pense vu le franc parler d'Ocramius qu'il dirait que c'est un problème de design... Normalement la classe devrait faire peut de choses, et donc avoir peut de dépendances. Chaque dépendance peut cependant avoir d'autres dépendances, et donc faire des chaines d'objets inclus dans d'autres plutôt qu'un seul objet avec beaucoup de dépendances. Je ne sais pas si mes propos ont un sens, hésite pas si je suis pas clair smile

Du coup par contre ça vaut le coup dans ce cas d'utiliser un module comme EDP superluminal pour concatener les classes dans un seul fichier et réduire les latences...

Hors ligne

 

#3 28-10-2015 09:43:22

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

Ok, je vois ce que tu veux dire.

Mais le serviceLocator est donc bien une bête noir crée par Zend avant de voir que les dev ne devrait pas l'utiliser. Un peu comme le fait d'acheter un lecteur de Divx, mais de ne jamais graver de divx car c'est illégal (pour la partie films du moins).

Mon soucis, selon le client qui fait la requête je dois charger des objet qui lui sont propre, le service locator me permet d'appeller directement la factory de cet objet dont l'appel est dynamique et cela m'économise un switch case de 150 cas en gros...

Pour palier a ce genre de problème quoi d'autre que le ServiceLocator ?


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#4 28-10-2015 10:13:23

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

Je pense que tout comme le Zend_Registry en ZF1, le service locator a semblé être une bonne idée (permettant une flexibilité dans le développement), mais fini par s'avérer dangereux en terme d'usage...

Il y a eu plusieurs discussions à ce propos et j'ai pas tout suivi, mais en tout cas le service locator ne sera plus injecté par défaut. Après je ne sais pas si il existera encore pour injection spécifique...

Je ne suis pas sur de ton cas, mais je passerais probablement par une abstract factory qui prends en paramètre ton router (ou n'importe quoi qui te permet de déterminer ton client), et ensuite je tenterais de voir si la classe correspondante existe. Le plus simple dans ce cas est d'avoir un namespace client je pense.

Si tu as un exemple concret à discuter hésites pas.

Une autre solution serait d'overrider la config de ton service manager pour sélectionner les objets en question du départ, mais j'aime moins (en gros modifier l'injection directement)...

Hors ligne

 

#5 28-10-2015 10:22:38

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

Je veux bien en discuter si tu as du temps oui.

J'ai un site web en ligne qui permet a nos client de tous horizons de se connecter et de commander (B to B) chaque client a une configuration propre et fonctionne différemment, donc l'application s'adapte au client et non l'inverse. (grand groupe VS PME, grand groupe Wins)

De là, j'ai donc des spécificités client à gérer, mais je veux les gérer intelligemment pour éviter les dérives du passé, la dette technique propre a un client dans le code.

De là ma bête noir c'est d'éviter une condition, basé sur un client précis en plein milieu du flux global de traitement. Depuis 2 ans, mes ruses ont fonctionnés mais je me heurte a de plus gros écueils au fur et a mesure que ma maîtrise du framework s'affine, car je souhaite respecter les bonnes pratique pour plus de maintenabilité.



Donc :

J'ai une classe qui me permet de facturer des clients et de rapatrier des donnés d'un ancien système vers le nouveau.
Un client a toutefois besoin de donnée supplémentaire et il faut que j'opère un nettoyage sur cette donnée qui n'existera que pour lui.
De la je comptais créer un dossier propre a ce client, mettre un service a l'intérieur et effectuer ces traitements.

Mais bien que cette solution ait un avantage, d'isoler ces traitements spécifiques, le gros problème survient lorsque nous perdons le client (si on le perd) il sera difficile de nettoyer ce code.

Je n'ai encore jamais mis en place une abstract factory est j'ai l'impression que c'est une méthode sympa. Serais tu capable de m'expliquer comment mettre un pareil systeme en place et m'expliquer le principe s'il te plait ?


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#6 28-10-2015 10:58:50

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

Alors d'un point de vue pratique, tu trouveras des abstract factories pour les Zend Db par exemple. En gros tu as deux méthodes qui vont être appelées :

Code:

[lang=php]canCreateServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)

Code:

[lang=php]createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)

Dans ton cas, je partirais sur un truc du genre :

Code:

[lang=php]
interface ClientInterface
{
    /**
     * Get the client name
     *
     * @return string
     */
    public function getName();

    /**
     * Returns a namespace for the client specific classes
     *
     * @return string
     */
    public function getNamespace();
}

Code:

[lang=php]
<?php

namespace MyProduct\Module;

use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ClientInvoiceServiceFactory implements AbstractFactoryInterface
{
    /**
     * @var ClientInterface
     */
    protected $client;

    /**
     * Can we create an client invoice service by the requested name?
     *
     * @param  ServiceLocatorInterface $services
     * @param  string $name
     * @param  string $requestedName
     * @return bool
     */
    public function canCreateServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
    {
        return class_exists($this->client->getNamespace() . 'InvoiceService');
    }

    /**
     * Create a ClientService
     *
     * @param  ServiceLocatorInterface $services
     * @param  string $name
     * @param  string $requestedName
     * @return ClientService
     */
    public function createServiceWithName(ServiceLocatorInterface $services, $name, $requestedName)
    {
        $service = $this->client->getNamespace() . 'InvoiceService';
        return new $service();
    }

    public function __construct(ClientInterface $client)
    {
        $this->client = $client;
    }
}

C'est un exemple à l'arrache qui ne marche probablement pas, mais j'essayerais surement un truc dans ce goût. Pour les classes clientes, soit tu les mets dans un "module" (framework agnostic, donc plus ou moins un dossier), et tu autoload en PSR4 de sorte à ce que le class_exists marche, soit tu crée un package sur un repository à part et ça te permet d'inclure ou supprimer les clients en faisant des composer update.

Vu qu'on joue sur l'autoload, j'imagine que tu dois aussi pouvoir jouer sur la config de PHP direct (include_path) pour ajouter ou supprimer tes clients, mais franchement c'est pas top car ta config dépends de ton déploiement.

Pour d'autres exemples tu peux aussi regarder du côté de Doctrine (adapters) et Flysystem (adapters aussi), et voir comment ils fonctionnent, ça peut être une bonne piste, car dans ton cas un client est un "Adapter" je pense.

Hors ligne

 

#7 28-10-2015 11:16:38

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

Merci pour ton exemple, en effet le principe pourrait marcher.

J'ai crée pour l'instant ma classe 'Client' directement sous le module de facturation. Avec le namespace suivant:

Code:

[lang=php]
Invoice\Data\Specific.

Ensuite le nom du client en tant que nom de la classe.

Une propriété getName retourne le nom de ce client.

Du coup j'ai les élement dont j'ai besoin pour faire cette classe abstraite, mais il me manque encore un petit bout :

Cette classe abstraite, après l'avoir déclarée dans

Code:

[lang=php]
'abstract_factories' => array(
            'Invoice\Factory\AbstractFactory\SpecificAbstractFactory',
        ),

Je vais bien avoir besoin du serviceLocator pour appeler une clé correspondante non ?

un truc du genre :

Code:

[lang=php]
$this->serviceLocator->get($client->getName());

Non ?


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#8 28-10-2015 11:39:26

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

J'ai toujours tendance à mettre le nom du client en namespace plutôt qu'en nom de classe, car tu peux vouloir définir différentes choses.

Par exemple, tu as un module Invoice et un module Import, et ils comportent un InvoiceServiceInterface ou AbstractInvoiceService ainsi qu'un InportServiceInterface/AbstractImportService. Ces classes / interfaces ont vocation a être remplacées par ton client specific.
Si tu utilises Invoice\Data\Specific\Client1 et Invoice\Data\Specific\Client2, tes données clients sont mélangées à ta logique applicative globale, et tu devras créer Import\Data\Specific\Client1 et Import\Data\Specific\Client2, avec les difficultés associées pour enlever le tout si besoin.
Si tu utilises MonApp\Client1\Invoice\Data et MonApp\Client2\Invoice\Data, Client1 et Client2 étant chargés avec composer, tu peux ajouter MonApp\Client1\Import\Data et MonApp\Client2\Import\Data. La suppression consiste à enlever la dépendance dans composer ou supprimer le dossier et son autoload dans composer ou tout autre autoloader que tu utilises.

Ensuite tu n'as pas forcément besoin du serviceLocator, même si il a été ajouté pour répondre aux interfaces de ZF, un simple class_exists te permet de voir si tu as une classe pour ce client. Si tu as une interface ou une abstract, tu dois définir une classe par client, donc class_exists ou throw exception ClientNotFound. Si tu as une BaseClass qui défini un comportement commun à la plupart de tes clients, class_exists or  new BaseClass.

Encore une fois, hésite pas si je suis pas clair !

Hors ligne

 

#9 28-10-2015 12:59:08

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

C'est vrai, je vois clairement ce que tu veux dire et je le vis déjà en fait avec un autre module ou j'ai fait comme ça. Et comme ton exemple, c'est pour de l'Export... Donc oui j'aime ta manière de construire ce genre de chose.

Après des modules je vais en avoir beaucoup, afin de garder une lisibilité dans les modules doivent il être mis dans Vendor par exemple ? Ou tu pense que les mettre dans /module est la meilleure manière ?


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#10 28-10-2015 13:17:06

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

La place des modules c'est une préférence personnelle et une question d'infrastructure et de gestion de projet.

Dans la dernière entreprise où j'ai mis ça en place (sur un projet Laravel), j'avais séparé les spécifiques dans des repositories différents. Ensuite on avait des "commons", qui sont responsables des fonctions communes (serialisation, Abstract, interfaces, base classes, etc). Enfin, des "applications", qui utilisent les commons et spécifiques selon leurs besoins. Par exemple, si tu as un nouvel import à faire pour un nouveau client, tu montes une box sur un VPS sur laquelle tu mets une application qui utilises les commons et spécifiques de ce client, pour la fonction import seulement.

Pour faire ça, j'ai installé un satis, une sorte de packagist privé pour lister des repositories, avec un cron pour faire le refresh régulièrement. A la place du cron, on pourrait utiliser un post commit hook sur tous les repositories de sorte à mettre à jour uniquement lors de changements.

Avec Satis, tu ajoutes juste un truc dans ce gout dans ton composer json:

Code:

[lang=json]
{
    "repositories": [
        {
            "type": "composer",
            "url": "http://packages.mycompany.com"
        }
    ]
}

Hors ligne

 

#11 28-10-2015 14:22:33

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

Je vois.

J'ai imaginé une solutions, je te la partage :

Je créée un dossier dans /module nommé "Clients".

De ce dossier je vais créer un Client1 et Client2 a l'intérieur. Deux modules Clients qui auront les spécificités dont mon application globale a besoin pour eux.

J'ai donc a mettre dans mes modules a charger un module avec un nom un peu spécial pour que ça marche  :

Code:

[lang=php]
return array(
    'modules' => array(
        'Application',
        'Clients\[mon_client]',
        'Clients\[mon_client2]',
    ),

Le module se charge bien, mais ais-je déclenché des problèmes de fond que je ne voit pas encore ?


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#12 28-10-2015 14:34:13

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

Du fait que tu utilises tes modules clients dans l'array module, tu dispatch des modules Zend (donc cherche une config, un Module.php, etc... Qui en fait ne sont utiles que si tu utilises les capacités d'un module Zend (routing, injection, etc).

Maintenant, tu pourrais avoir des clients agnostiques, qui implémentent l'interface qui va bien, mais dont les dépendances sont injectées depuis Application (qui lui connait ton service manager). Dans ce cas, tu peux faire un dossier client à côté de /module (/client donc), avec les noms des clients en sous modules. Ensuite dans ton composer.json, tu configures ton autoload :

Code:

[lang=json]
"autoload": {
    "psr-4": {
      "Client\\": "client/",
    }
  },

Ou encore

Code:

[lang=json]
"autoload": {
    "psr-4": {
      "Client\\Client1": "client/Client1/src",
      "Client\\Client2": "client/Client2/src",
    }
  },

Dans ce cas tu économises le temps de chargement des modules, composer crée l'autoload mapper (donc juste un array rapide à parcourir pour trouver si la classe existe), et tu t'embêtes pas du tout.

Hors ligne

 

#13 28-10-2015 15:45:45

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

En effet, bien joué c'est ce genre de truc que je voulais.

En ayant poussé la réflexion plus loin, je me suis dis que pour proposer a un client une vue selon ses propres désirs, qui serait donc aussi stocké dans son dossier, ainsi que ses propres traductions ($this->translate('key')). Est ce que j'aurais moyens de faire fonctionner via des appels & Evènements ce genre de dossier ? Vu que cela ne marche pas comme un module ce serait plus complexe de proposer des listeners basé sur l'EVENT_RENDER par exemple et de pouvoir fournir le template du client s'il y en as un pour la route donnée etc..

De même pour les traductions.

C'est un autre problème d'un B to B ou chaque client veut une appli a sa sauce.


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#14 28-10-2015 16:23:55

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

Il y a projet qui s'occupe de ça appelé Puli. Je ne l'ai jamais essayé et je ne peux donc pas confirmer la qualité...

Sinon en effet avec des évènements tu dois pouvoir essayer de changer les chemins... De toutes façons c'est ton contrôleur qui dispatch a ta vue, donc c'est dans un module. Seul le fichier de ressource va être déplacé dans le dossier de ton client, donc ton ClientInterface peut contenir un getInvoiceTemplate qui return realpath('../views/invoice.phtml') ou un truc du goût.

Qu'utilises-tu pour les traductions ?

Hors ligne

 

#15 28-10-2015 19:02:42

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

J'utilise le basique

Code:

[lang=php]
 'translator' => array(
        'locale' => 'fr_FR',
        'translation_file_patterns' => array(
            array(
                'type'     => 'phpArray',
                'base_dir' => __DIR__ . '/../language',
                'pattern'  => '%s.php',
            ),
        ),
    ),

et uniquement cela sous forme clé valeur :

Code:

[lang=php]
'ma_cle_en_snake_case' => 'Ma traduction dans la langue correspondante',

ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#16 28-10-2015 19:06:34

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

Je sais qu'on peut ajouter plusieurs file patterns, je l'ai fait dans un projet... Du coup à voir mais il doit être possible de rajouter un pattern pour le client en cours (en premier dans la liste par contre...) après le routing, je sais pas trop où dans le dispatch.

Le translator est de toutes façons disponible dans le service locator (MvcTranslator), donc injectable dans un event listener, et ensuite var_dump est ton ami je pense smile

Hors ligne

 

#17 28-10-2015 19:30:29

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

Tu m'a appris plein de truc sur cette partie là, merci pour le partage.


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#18 28-10-2015 23:08:31

Orkin
Administrateur
Lieu: Paris
Date d'inscription: 09-12-2011
Messages: 1261

Re: Le serviceLocator dans les services

Hello, j'ai pas tout lu mais dans l'ensemble quand tu as trop de dépendance c'est souvent un problème d'architecture. Après des fois j'ai l'impression qu'on a pas le choix. Découper pour découper quand ça sert qu'à un seul endroit je trouve ça un peu bête même si ça rend le code généralement plus lisible.

JGreco a écrit:

Mais le serviceLocator est donc bien une bête noir crée par Zend avant de voir que les dev ne devrait pas l'utiliser

Non ça n'a pas été inventé par Zend c'est un design pattern d'injection dépendance. Tu as l'équivalent dans différents framework. Il y a quand même une différence entre Zend_Registry du ZF1 et Service locator. Dans le premier tu pouvais vraiment mettre tout ce que tu veux et ça servait de fourre tout alors que dans le second non. Ok tu peux mettre beaucoup de chose mais tu pourras pas stocker une valeur que tu veux retourner plus tard. Alors ça serait faisable vu que les objets retournés pas le service locator sont des des singletons et donc tu pourrais avoir un objet qui contient ton fourre tout mais c'est pas le but. Le service locator spécialisés te permettent aussi de savoir vraiment quel genre d'objet tu vas récupérer.

Quant aux abstract factory c'est super pratique mais à utiliser avec parcimonie tout comme les delegator wink.

Hors ligne

 

#19 28-10-2015 23:19:29

tdutrion
Administrateur
Lieu: Dijon, Paris, Edinburgh
Date d'inscription: 23-12-2009
Messages: 614
Site web

Re: Le serviceLocator dans les services

Que penses-tu d'utiliser une abstract factory pour utiliser un objet spécifique à un client ? (c'est en essence ce dont on a discuté) ? (avec un pattern adapter en quelque sorte)

Après c'est sur que le service locator est moins pire que le Zend_Registry, mais dans le cas présent l'idée de le passer dans un service directement est pas terrible, ce qui était fait à l'aide des ServiceLocatorAware, et l'injection dans les controllers semble inutile au même titre. Du coup, sans ces usages on se rapproche du pattern comme il devrait être utilisé...

Hors ligne

 

#20 29-10-2015 08:50:54

JGreco
Administrateur
Date d'inscription: 22-12-2012
Messages: 432

Re: Le serviceLocator dans les services

Orkin a écrit:

Non ça n'a pas été inventé par Zend c'est un design pattern d'injection dépendance. Tu as l'équivalent dans différents framework.

My bad je me suis mal exprimé, bien sur que Zend n'a pas inventé le design pattern, mais il l'a implémenté dans Zf2. C'est le sens que je voulais donner smile


ZF2 et doctrine addict
profil stack overflow : http://stackoverflow.com/users/3333246/ … ab=profile

Hors ligne

 

#21 30-10-2015 10:40:07

Orkin
Administrateur
Lieu: Paris
Date d'inscription: 09-12-2011
Messages: 1261

Re: Le serviceLocator dans les services

Théocrite a écrit:

Que penses-tu d'utiliser une abstract factory pour utiliser un objet spécifique à un client ? (c'est en essence ce dont on a discuté) ? (avec un pattern adapter en quelque sorte)

Oui ça me semble une bonne idée après faut juste l'utiliser au minimum et pas en abuser.

Pour moi tu as cette manière de faire ou celle-ci :
Avoir une architecture ultra générique à base d'interface, chaque code pour chaque client implémente les bonnes interfaces et en gros tu déploies une application pour chaque client comme ça ça reste isolé. Parce que là le soucis que tu vas avoir avec une seule application monolitique c'est que tu risques d'impacter tous tes clients sur tu fais une modification pour un client qui pète tout. Alors que si tu joues sur des modules que tu actives ou non et qui sont déployé dans des environnements isolés. Un peu à la manière d'un mutualisé je trouve ça plus propre. Et c'est ton script de déploiement qui sait quel module activer pour chaque client.

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