Consultez la FAQ sur le ZF avant de poster une question
Vous n'êtes pas identifié.
Bonjour à tous,
Tout d'abord merci pour ce site et pour ce partage de connaissances en français, c'est agréable de ne pas avoir à se casser la tête à traduire un message qui n'est pas toujours simple à rédiger même dans sa propre langue !!!
Je pratique ZF1 depuis quelques années déjà et j'en ai donc une bonne connaissance. Il est grand temps maintenant que je passe à son successeur qui semble prometteur mais, comme il a été souvent dit sur ce forum, pas franchement accessible de prime abord.
Je suis vraiment désolé, je pense que je vais ramener un sujet sur la table qui a été plusieurs fois abordé, à savoir l'architecture d'un projet multi-sites.
Voilà l'architecture de mon projet à base de ZF1 que j'aimerais migrer progressivement en ZF2 (Bidon étant un nom factice de mon projet) :
Les libs communes :
- bidon-persistence :
Couche de persistence bas niveau dans laquelle j'ai une implémentation Doctrine et une implémentation Zend_DB de ma classe Repository
- bidon-model
Tous les objets de mon modèle
- bidon-infrastructure
Implémentations (Zend_DB et doctrine) de mes repositories spécifiques (ex. UserRepository)
- bidon-services
Couche service, pour l'accès à un moteur d'indexation elasticsearch (pour l'instant)
- bidon-plugins
Tous les plugins Zend_Controller_Plugin_Abstract communs à mes 3 webapps
Les webapps :
- bidon-web
Contrôleurs / Vues / Layout / CSS / JS spécifiques à la version web de mon site
- bidon-mobile
Contrôleurs / Vues / Layout / CSS / JS spécifiques à la version mobile de mon site
- bidon-admin
Contrôleurs / Vues / Layout / CSS / JS spécifiques au Back Office de mon site
Un batch :
- bidon-batch
J'utilise ant pour construire une archive pour chaque webapp et qui recopie dans le répertoire library de chacune le code de chaque lib commune.
Pendant la phase de dev, mes virtualhost apache pointent directement sur mes 3 répertoires public, et les pathes des libs communes sont ajoutés dans l'include_path à la main dans la conf de dev.
J'ai splitté en plusieurs webapps car j'ai une url différente et donc un virtualhost apache pour chacune (elles ne tournent d'ailleurs pas toutes sur les mêmes serveurs).
Si j'ai bien compris les discussions qu'il y a déjà eu sur les modules ZF2, l'approche est sensiblement différente de la mienne. L'idée serait apparemment de faire une séparation fonctionnelle alors que la mienne était plutôt technique.
1ère question : dois-je vraiment selon vous abandonner mon approche pour coller aux principes du framework, ou bien est-ce que je peux me contenter de transformer chacune de mes libs en un module sans trop trahir le framework ?
2ème question : comment gérer mon multi-site ?
- cas 1 : une seule webapp, avec séparation des contrôleurs dans différents modules fonctionnels qui implémenteraient chacun une version web, admin et mobile ? (pas sûr de l'intérêt dans mon cas, car les 3 webapps n'ont vraiment pas les mêmes fonctionnalités, y compris web et mobile)
- cas 2 : je reste sur une séparation en plusieurs webapps, auquel cas comment je gère la mutualisation de mes modules communs (qu'ils soient fonctionnels ou techniques ... fonction de la réponse à la 1ère question) ? via composer ? pourquoi pas mais comment du coup gérer facilement l'environnement de développement (je ne veux pas avoir à commiter chaque modif sur mes libs communes pour les tester dans une des webapps) ?
- autre cas ?
Voilà désolé si j'ai été un peu long mais j'hésite vraiment sur l'orientation à prendre, et vu le boulot qui m'attend pour cette migration je ne veux vraiment pas me tromper sur l'architecture
Merci d'avance,
Manu
Hors ligne
Salut Manu, enfin quelqu'un qui prend le temps de rédiger ses questions . Je vais essayer de répondre à tout sachant qu'il y a pas mal de choses à dire ^^.
Tout d'abord l'architecture ZF2 fonctionne différemment de l'architecture ZF1 puisqu'on a pas besoin de toucher aux sources du framework (sauf bug etc ...) ou d'aller rajouter des fichiers dedans tu peux donc rester clean sur ce point là. Ceci permet de faire évoluer le framework sans se casser la tête à faire attention de ne pas perdre nos modifications. Ce qui change aussi c'est les modules qui sont au final des briques d'application plus ou moins autonomes. Dans la théorie un module est activé ou désactivé l'application fonctionne toujours sans que ça pose de problèmes seule les fonctionnalités apportées par le module désactivé ne fonctionnent plus. Dans la pratique ça nécessite est rigueur assez important et une très bonne maitrise du framework en passant par des events, des aides de vue etc ... C'est pas toujours simple mais quand on y arrive c'est très beau !
Pour en revenir à ton problème, je pense qu'effectivement tu ne pourras pas redécouper tes applications de la même manière, enfin si tu pourras mais ça ne sera pas forcément dans la logique d'un module. Il y a surement plusieurs façon de faire. Je vais essayer de te donner quelques billes pour que tu puisses te décider.
Tu peux partir sur un découpage fonctionnel avec un module qui va apporter la partie messagerie, un autre la partie gestion utilisateur, une autre le blog, une autre la boutique et enfin le paiement en ligne. Dans ce cas chaque module apporte sa partie mobile, sa partie admin et sa partie classique.
Tu peux partir sur un autre découpage fonctionnel qui serait un module mobile, un module admin et un module classique.
Personnellement je choisirais la première solution ce qui permet d'avoir une application qui plus modulable. Pour une raison X ou Y tu veux bloquer les paiements parce que bug ou problème de livraison etc ... C'est un fichier à modifier et ça désactive tout. En pratique tu auras sans doute une case à cochée dans la partie admin pour le bloquer mais ça reste un exemple.
Ensuite là où j'ai un peu de mal à saisir ton découpage actuel c'est qu'au final tu as des webapp différentes. Si c'est juste des webapps qui répondent à des services rest par exemple les serveurs qui les hébergent n'ont pas besoin d'avoir accès à la partie métier de l'application puisqu'elle sera interrogée via des URLs donc ça peut très bien être sur un autre serveur et en fonction de la webapp les routes sont différentes. (/admin/bidon, /mobile/bidon, /classique/bidon) De ce fait tu as un découpage au niveau de l'application. Après ça peut aussi être la webapp qui récupère partout les mêmes données et en fonction de la webapp va pas forcément afficher les mêmes infos (on peut imaginer moins de fonctionnalité sur la partie mobile par exemple). Dans ce cas là je pense pas qu'il soit nécessaire de développer plus après là c'est à toi de t'organiser comme tu veux.
Dans l'autre cas qui est le cas où les pages sont retournées par le serveur donc on parle plus vraiment de webapp mais ça n'empêche pas d'avoir des ihm différentes. Dans ce cas il faut effectivement tout mettre en module. Je vais prendre bidon-web et bidon-mobile comme exemple.
Bidon mobile va avoir besoin de la même chose niveau métier que bidon-web les seules différences seront dans les vues, donc les contrôleurs (à moins d'aller plus en profondeur et de modifier la vue en fonction de la route : /mobile/bidon /web/bidon). Bref il va y avoir énormément de choses en commun pour cela tu n'auras juste qu'à ajouter les modules dont tu as besoin suivant le découpage que tu auras choisi. Ceci est très simple gérer avec github ou svn. Tu ajoutes un modules depuis le svn, tu travailles sur le module et ensuite tu le mets à jour via svn ou github partout où tu en as besoin.
Ensuite je voudrais revenir sur un point, tu parles d'un module de persistance et de repository et ça il n'y en a plus besoin. Il existe un module ZF2 sur github qui permet d'intégrer doctrine 2 (DoctrineModule et DoctrineORMModule (pour la partie ORM)). Tes répository et tes models seront présent dans le module qui les implémente. On peut imaginer que ton module gestion utilisateur va apporter certains models et donc des repository. Ceci permet d'avoir des modules "out-of-the-box".
De ce fait tu gagnes certains module, tu n'as pas à les redévelopper, tu peux participer pour les améliorer et il n'en sera que meilleur. Grosso modo un module sera découpé par une arborescence de fichiers/dossiers. La partie service dans un dossier service au sein du module, la partie repository pareil etc ...
Dans ton architecture j'ai l'impression que tu découpes comme on a l'habitude de le faire en java avec un module couche service, un module couche DAO etc ... Tu peux faire la même chose sur le ZF2 mais tu perds la notion de module out-of-the-box puisque le module couche DAO ne pourra fonctionne qu'avec une seule couche service tandis qu'avec un module qui gère toutes ses couches tu peux l'ajouter dans une application futur. Imaginons un module de messagerie ça peut être réutilisé partout et surtout si tu as vraiment bien penser à tout et que tu utilises les events (je développe pas trop je maitrise pas très bien les events) si l'event existe (apporté par le module messagerie) alors il sera capturé et des actions seront exécutés; s'il n'existe pas rien ne se passera et ton application fonctionnera toujours normalement. C'est ça qui est génial !
Voila j'espère avoir répondu à pas mal de tes questions. Si tu en as d'autres n'hésites pas, j'ai peut être pas tout compris dans tes problèmes et je n'ai peut être pas non plus été toujours très clair .
Hors ligne
Merci beaucoup de prendre le temps de répondre de manière aussi complète à mes questions
Orkin a écrit:
Ensuite là où j'ai un peu de mal à saisir ton découpage actuel c'est qu'au final tu as des webapp différentes.
En fait j'ai bien trois webapps différentes générées à partir d'un seul et même code source. Grâce à ant je construit trois livrables ayant cette structure :
application/
library/
library/Bidon
library/Bidon/Persistence/Doctrine
library/Bidon/Persistence/ZendDB
library/Bidon/Infrastructure/Doctrine
library/Bidon/Infrastructure/ZendDB
library/Bidon/Model
library/Bidon/Services
public/
Mes trois livrables auront l'intégralité de mes libs communes copiées dans leur répertoire library.
Etant donné que mes trois applications sont déployées sur des serveurs différents avec chacune leur sous-domaine (www.bidon.com, m.bidon.com et admin.bidon.com), ça me permet de mutualiser mes différentes couches applicatives sans forcément mettre en place un webservice pour gérer tout ça.
Orkin a écrit:
Bidon mobile va avoir besoin de la même chose niveau métier que bidon-web les seules différences seront dans les vues.
En réalité non, les différences ne se trouvent pas uniquement dans les vues ... J'ai des comportements relativement différents entre web et mobile, et ça se retrouve également au niveau du contrôleur, c'est pour cette raison que j'ai fait deux webapps différentes pour ces 2 là. Je n'ai que très peu de duplication de code au niveau contrôleurs.
Orkin a écrit:
Ensuite je voudrais revenir sur un point, tu parles d'un module de persistance et de repository et ça il n'y en a plus besoin. Il existe un module ZF2 sur github qui permet d'intégrer doctrine 2 (DoctrineModule et DoctrineORMModule (pour la partie ORM)).
En fait ma lib de persistence se situe à un niveau un peu plus abstrait que DoctrineORMModule (enfin à première vue ...). Elle est là pour fournir plusieurs implémentations différentes de mon interface Repository (une implémentation Doctrine et une implémentation Zend DB) :
[lang=php] interface Repository { public function add($entity); public function getAll(); public function size(); public function getById($id); public function update($entity); public function remove($entity); }
Selon l'approche Domain Driven Design (que j'essaye de respecter), un repository doit fonctionner comme une liste d'objets en mémoire. L'idée est qu'on ne doit jamais manipuler (que ce soit en entrée ou en sortie) autre chose que des entités du domaine (excepté size() qui retourne un entier). Le but est de découpler un maximum la couche de persistence.
Donc au final mon module de persistence sera toujours là, mais sera probablement dépendant de DoctrineORMModule (à vérifier), mais également de son homologue pour Zend DB.
Pourquoi j'ai besoin des deux implémentations ? Tout simplement parce que mon batch effectue des traitements en masse sur les données, et doctrine n'est vraiment pas fait pour ça, j'avais des performances catastrophiques.
Orkin a écrit:
Dans ton architecture j'ai l'impression que tu découpes comme on a l'habitude de le faire en java avec un module couche service, un module couche DAO etc ...
Je suis démasqué
Orkin a écrit:
Tu peux faire la même chose sur le ZF2 mais tu perds la notion de module out-of-the-box
Oui dans l'idéal la notion de module out-of-the-box est intéressante, mais dans les faits mon scope fonctionnel est relativement spécifique, et mis à part la gestion des utilisateurs, je ne vois pas ce que je vais pouvoir réutiliser dans d'autres projets ...
Ceci dit une fois que j'aurais réussi ma migration sur le scope fonctionnel actuel, pourquoi pas y intégrer des modules de la communauté type messagerie, blog, forum, etc.
Au final mon coeur balance ... si je veux coller à l'esprit du framework ma migration sera beaucoup plus pénible que prévue, et ne pourra pas se faire progressivement (j'entends webapp par webapp). Sans compter que je vais devoir oublier mon découpage en couches techniques auquel je suis tant attaché
Si je pars sur un découpage fonctionnel de mes modules, avec pour chacun une partie web / mobile / admin, comment est-ce que je dois gérer les sous-domaines ?
a+
Manu
Hors ligne
goten4 a écrit:
Si je pars sur un découpage fonctionnel de mes modules, avec pour chacun une partie web / mobile / admin, comment est-ce que je dois gérer les sous-domaines ?
Avec les routes tu peux faire des choses assez sympa alors je ne pense pas que ça répondra à tes problèmes.
Ce que je ferais en partant sur ce découpage tu vas donc avoir des contrôleurs et des actions différentes donc tu peux gérer ça via les routes. Tu peux très bien avoir une route mère /mobile et des routes filles /mobile/users, /mobile/admin etc ... Comme ça au niveau applicatif tu peux dissocier les différents contrôleurs. Ce que je n'avais pas pensé c'est qu'au final l'application dédiée au mobile va embarquer tout un tas de contrôleurs dont elle n'a pas besoin. Donc ça peut être intéressant de faire un module spécifique pour chaque webapp.
Je m'explique, comme au final chaque application a le même code source métier (j'entends par là services, DAO etc ...) tu peux peut être partir sur un découpage un peu différent. C'est à dire que tu peux découper de façon fonctionnelle mais ne pas remonter plus haut que la couche service. Tu auras donc tes modèles, tes répository, tes services etc ... qui seront commun. Là je ne pense pas qu'il y ai des comportements bien bien différent. Dans ces modules tu ne mets pas de couche "View" : contrôleurs, template etc ... mais tu fais un module pour ça par application qui contiendra ces actions.
Alors là se pose le problèmes de module out-of-the-box qui peut être résolu de deux manières :
1) soit on oublie
2) soit on essaie de le conserver au mieux
Si on l'oublie, dans ce cas on se pose plus de questions, faut quand même essayer de garder une architecture propre et ça sort un peu de l'ordinaire pour la partie "View"
Si on essaie de le conserver alors il te faudra pour chaque module fonctionnel un module "View" associé à la fonctionnalité.
L'architecture de tes applis ressemblerait donc à ça :
Cas 1 :
ApplicationMobile/
BidonViewMobile
BidonUser
BidonMessagerie
BidonVitrine
BidonPaiement
ApplicationWeb/
BidonViewWeb
BidonUser
BidonMessagerie
BidonVitrine
BidonPaiement
Dans le cas 2 : (ça implique quand même des dépendances entre View"Fonctionnalité" et Bidon"Fonctionnalité"
ApplicationMobile/
BidonViewMobileUser
BidonUser
BidonViewMobileMessagerie
BidonMessagerie
BidonViewMobileVitrine
BidonVitrine
BidonViewMobilePaiement
BidonPaiement
ApplicationWeb/
BidonViewWebUser
BidonUser
BidonViewWebMessagerie
BidonMessagerie
BidonViewWebVitrine
BidonVitrine
BidonViewWebPaiement
BidonPaiement
Sinon, le découpage par couche est loin d'être idiot, je viens aussi du Java et j'aurais tendance à faire pareil. Là il faut prendre en compte la spécificité du ZF2 qui a une architecture modulaire et qui permet de produire des modules out-of-the-box mais dans le cas contraire j'aurais aussi reproduit ce qui se fait en java avec maven 2 ou ant.
Hors ligne
Je suis pas sûr de bien comprendre l'architecture que tu proposes pour les deux cas ...
Si on reprends le modèle de module du tuto zf2 :
zf2-tutorial/
/module
/Album
/config
/src
/Album
/Controller
/Form
/Model
/view
/album
/album
Ça se présenterait comment exactement ?
Hors ligne
En fait c'était pour imager. Dans une application ZF2 tu as 2 endroits possible pour mettre un module : soit tu le mets comme dans le quickstart (qui est le module Album, c'est généralement au même niveau qu'Album qu'on les met), soit tu le mets dans le dossier vendor de l'application.
En général on met dans le dossier vendor des modules externes à l'application genre DoctrineORMModule, ZfcTwig etc ... Dans ton cas tu auras les modules de tes différentes couches. Dans le dossier module on met des modules interne à l'application mais dans ton cas je ne pense pas que ça s'applique puisque tu souhaites mutualiser mais au final ça fonctionne de la même manière que ça soit dans vendor comme dans module. La seule différence c'est que le composer dépose les modules dans vendor.
Donc pour toi tu peux avoir deux architectures suivant comment tu veux procéder.
Cas 1 : (je te met pas toute l'arbo des fichiers)
application-mobile/
/module
/BidonViewMobile
/BidonUser
/BidonMessagerie
/BidonVitrine
/BidonPaiement
/vendor
/DoctrineModule
/DoctrineORMModule
ou
application-mobile/
/module
/BidonViewMobile // ici uniquement contrôleur et vue pour toute l'application
/vendor
/BidonUser
/BidonUser
/BidonMessagerie
/BidonVitrine
/BidonPaiement
/DoctrineModule
/DoctrineORMModule
Cas 2 :
application-mobile/
/module
/BidonViewMobileUser
/BidonUser
/BidonViewMobileMessagerie
/BidonMessagerie
/BidonViewMobileVitrine
/BidonVitrine
/BidonViewMobilePaiement
/BidonPaiementmodule
/vendor
/DoctrineModule
/DoctrineORMModule
ou
application-mobile/
/module
/BidonBase // ici uniquement un module pour lancer l'application (je suis pas sûr que ça fonctionne sans module
/vendor
/BidonViewMobileUser
/BidonUser
/BidonViewMobileMessagerie
/BidonMessagerie
/BidonViewMobileVitrine
/BidonVitrine
/BidonViewMobilePaiement
/BidonPaiement
/DoctrineModule
/DoctrineORMModule
Je sais pas si je suis clair :s
Hors ligne
Ok je comprend mieux ce que tu suggères !
Dernière petite question, y a t'il un moyen d'aller chercher des modules ailleurs que dans vendor/ ou module/ uniquement pour l'environnement de dev ?
Je m'explique, pendant que je développe et que je modifie un de mes modules externes, avant de commiter ma modif j'aime bien pouvoir la valider dans le navigateur.
En ZF1 je me débrouille en ajoutant les répertoires de mes libs externes dans le include_path.
Comment faire ça proprement en ZF2 ?
Hors ligne
Ok je crois que je viens de trouver la réponse à ma dernière question ...
Dans config/application.config.php, j'imagine qu'il suffit de rajouter les pathes de mes modules :
[lang=php] 'module_listener_options' => array( 'module_paths' => array( './module', './vendor', ) )
Un grand merci à toi pour tous ces éclaircissements, ça m'a beaucoup aidé !
a+
Manu
Hors ligne
quand le module manager va charger un module, il s'arrêter au premier qu'il trouve, ce qui veut dir que si un module toto se trouve dans vendor, et qu'un deuxième se trouve dans module, il chargera celui dans module.
pour le forcer à choir un module spécifique en dev, on peut lui spécifier comme ceci :
return array( 'modules' => array( 'Application', 'Blog', 'MonModule', // <----------------------------------- ton module ), 'module_listener_options' => array( 'config_glob_paths' => array( 'config/autoload/{,*.}{global,local}.php', ), 'module_paths' => array( './module', './vendor', 'MonModule' => '/path/to/MonModule', // <----- on veut charger celui-là ), ), );
Concernant le découpage des modules, un module peut apporter une fonctionnalité technique ou fonctionnelle. une simple classe Module suffit pour créer un module dans ZF2 et on y met ce que l'on veut.
pour te monter la liberté que tu a pour modéliser ton appli, prenons un cas simple. si un module B est chargé après un module A, la config du module B peut surcharger/remplacer la config du module A, car à la fin du processus de chargement des modules, le moduleManager concatène la config des différents modules et l'enregistre dans la config de l'application. ainsi le module B peut simplement déclarer des templates à utiliser à la place de ceux dans le module A, et redéclarant le template_map du view_manager. c'est pareil pour les déclarations de classes invokables/factories dans les différents serviceManagers (principale, controllers, plugin manager, helper broker, etc...)
ainsi tu peut surcharger un module commun, par un module qui apporte quelques fonctionnalité spécifique à une webapp
(j'ai écrit un tuto sur les modules (http://aromatix.fr/?p=329) en m'inspirant du webinar de evan coury qui est à l'origine de ce mode de fonctionnement des modules dans ZF2)
dans le cas où tes applis web et mobile divergent juste au niveau de ta couche présentation, tu peut par exemple activer/ajouter des stratégie de vue dans le viewManager pour détecter dans la requète quelle rendu est attendu. et ainsi renvoyer un résultat html dans un layout, ou du json(supprime la layout et renvoi un JsonModel ), rss+xml, ( autre format xml en développant une stratégie de vue). ceci peut permettre par exemple d'avoir un résultat différent selon la requette, une appli android peut attendre du json, une requette ajax aussi alors que normalement.
tu peut aussi utiliser une approche puissante en te basant sur une architecture orientée évènements.
le principe est simple à comprendre :
1 -tu peut enregistrer des écouteur (qui écoutent des évènements)
2 - tu enclenche des évènement dans ton applis (ça peut être dans une action, un modèle, un formulaire, ailleurs )
je reprend un exemple que j'ai déjà cité :
tu peut par exemple dans le cas d’un module d’authentification générer des évènements (ex : ‘userLogin‘, ‘userLogin.post‘, ‘userLogout‘, ‘authBadPassword‘ etc…). dans ce cas un autre module pourra l’étendre sans surcharger son code : tu pourra par exemple avoir un module qui va vérifier les derniers messages si l’événement ‘userLogin.post‘ est enclenché, un autre module qui pourra vider des caches de messagerie/profile/etc si ‘userLogout‘ est enclenché, un module de sécurité pourrai par exemple comptabiliser les tentatives infructueuses de logins à chaque enclenchement de l’événement ‘authBadPassword‘ et donc agir en conséquence comme par exemple écrire dans une log ou afficher un captcha, etc…comme vous pouvez le comprendre, on peut apporter des fonctionnalités suplémentaire à l’application sans être obligé de modifier l’éxistant, mais plutôt en enclenchant des traitement avant/pendant/après un évènement enclenché par l’application
ça doit te donner des idées pour mieux découper ta nouvelle appli
pour ce qui est de la persistance orkin a raison, tu n'a pas grand chose à faire. tu déclare comme d'hab tes entités et tes modèles séparément. des hydrateurs dans la stdlib de zf2 te faciliterons la vie pour mapper tes entité avec les formulaires/etc... (aussi les validateurs/filtres peuvent être déclaré dans tes entitées pour les réutiliser avec n'importe quel formulaire, ou permettre au model de filter si besoin . etc...)
Hors ligne
aromatix a écrit:
quand le module manager va charger un module, il s'arrêter au premier qu'il trouve, ce qui veut dir que si un module toto se trouve dans vendor, et qu'un deuxième se trouve dans module, il chargera celui dans module.
pour le forcer à choir un module spécifique en dev, on peut lui spécifier comme ceci :Code:
return array( 'modules' => array( 'Application', 'Blog', 'MonModule', // <----------------------------------- ton module ), 'module_listener_options' => array( 'config_glob_paths' => array( 'config/autoload/{,*.}{global,local}.php', ), 'module_paths' => array( './module', './vendor', 'MonModule' => '/path/to/MonModule', // <----- on veut charger celui-là ), ), );
C'est bon à savoir, merci pour l'info !
Hors ligne