Consultez la FAQ sur le ZF avant de poster une question
Vous n'êtes pas identifié.
Bonjour à tous,
Hier soir j'ai enfin eu le temps de me mettre un peu à jour vis-à-vis du ZF (oui j'ai honte, je travaille toujours en 1.7), j'ai donc télécharger la dernière version, et me suis lancé corps et âme dans le quickstart pour découvrir le magnifique Zend_Application.Et pour lui pas de problème, ça m'a l'air d'être du bon, même du très très bon.
Par contre au cours de ce tuto, m'est apparue la notion de Mapper. Et oui, étant autodidacte total, et ne travaillant en objet que depuis un peu plus d'un an, c'est quelque chose que j'ai un peu de mal à saisir. Non pas dans son fonctionnement, qui est, je le suppose dans ce cas ci (et je précise bien, celui-ci) assez simple. Non la question que je me pose est "pourquoi ?"
Pourquoi utiliser une class, qui va rajouter une couche entre le Db_Table_Abstract et le model ? Jusqu'a présent, j'avais dans mon projet un dossier models, qui contenait que des class issue de Zend_Db_Table_Abstract, et j'utilisais ces class directement dans mes controllers... Donc ici j'ai un peu de mal à saisir l'utilité de faire 2 class (le model et le mapperModel) pour acceder à une troisieme (la Db_Table) si ce n'est l'évidente clarté du code au niveau du controller. En effet, juste faire un new suivis d'un save, c'est joli et très propre.
j'ai bien lu le lien fournis par la doc au moment de creer le mapper qui explique un peu, mais je dois m'avouer vaincu par la subtilité de la langue anglaise dans ce cas ci (pour faire simple, j'ai lu mais j'ai rien compris ).
Donc si quelqu'un aurait la gentillesse de m'expliquer, ou de me donner des pistes de documentation, il en sera grandement remercié
Hors ligne
Salut,
L'intérêt du mapper est de découpler un peu plus le front de l'application de la persistance.
Dans un monde parfait, les controlleurs ne devraient jamais communiquer directement avec la couche de persistance (pour toi Zend_Db). Ils ne devraient que "contrôler" l'information et transmettre après contrôle à une autre couche pour la suite des opérations.
Le controlleur ne va donc manipuler que des objets entité (Entity Object), ou objets métier (Domain Object/Business Object).
Qu'est-ce qu'un objet métier ?
Ces objets représentent la plupart du temps les entités du domaine métier pour lequel l'application est conçue.
Ils sont pensés et implémentés par les développeurs et non pas par le framework. Ils sont totalement détachés de la persistance (base de données ou autre).
Par exemple, ton application doit afficher des galeries d'images, et ces galeries appartiennent à des utilisateurs.
Tu vas avoir une entité Utilisateur dans laquelle tu auras des galeries qui sont elle-mêmes des entités Galerie. C'est la définition des entités qui répondent au métier, au domaine abordé.
Le terme "modèle métier" est un peu utilisé à toutes les sauces. Le modèle métier (Domain model) représente l'ensemble des objets métiers, donc des entités, et les relations entre eux. Ni plus, ni moins.
Les objets métiers sont la plupart du temps peuplés avec des données des entités réelles (dans le métier) qu'ils représentent (mais pas toujours).
Le Mapper dans tout ça ?
Les objets métiers sont les seuls éléments qui sont connus dans toutes les couches de l'application.
Les controlleurs ne doivent pas connaitre l'organisation de la persistance, mais connaissent les objets métiers, c'est donc au travers de ceux là qu'ils vont travailler.
Ils assurent un contrôle aller/retour des informations vers le client, la dynamique de l'application (intéractions, etc.).
Sauf que, comment faire persister un objet métier, ou comment récupérer les données pour peupler un objet métier si le controlleur ne connait pas la persistance ? Et bien il passe par le mapper.
Le rôle du mapper, tel que Zend l'introduit, est de savoir comment récupérer les informations nécessaires dans la persistance pour peupler un objet métier. Ou bien de savoir comment demander à la persistance d'enregistrer un objet métier.
Le controlleur saura donc créer un objet métier, le peupler (depuis un formulaire par ex.) et demandera au mapper de faire persister cette objet. Ou dans l'autre sens, le controlleur a besoin d'un objet métier déjà peuplé, il demandera au mapper de lui retourner.
Si demain, la couche de persistance change, tu n'auras rien à refactoriser dans tes controlleurs, seuls les mappers vont changer.
Mais aussi, si tes mappers sont prêts, tu peux développer le front indépendamment du back et tu n'es pas obligé d'attendre après la conception de la base de données pour commencer ton dév.
Après, tout dépend de la dimension de l'application, beaucoup de développeurs décident qu'un Zend_Db_Table_Row = un objet métier, et ceci leur convient.
Pour moi ce n'est pas satisfaisant pour plusieurs raisons.
D'abord, une entité n'est pas forcément égale à une table, elle peut être construite à partir de plusieurs tables dans la BDD.
Ensuite, tout ceci lie très fortement ton front à une persistance type base de données. Si demain tu dois récupérer tes données depuis un webservice en XML ou depuis un annuaire il faudra tout refactoriser.
Enfin, la solution proposée par Zend dans le Quickstart est, selon moi, insuffisante. Par exemple, pour faire persister un objet métier, ils ont implémenté la méthode save() dans l'objet métier, ce qui, à mon sens est une abérration. Mais plus loin encore, pour un découplage réel de toutes les couches, la notion de mapper est plus qu'insuffisante et n'est qu'une brique de toute l'architecture.
Celà fait quelques jours que je me penche sur le sujet du découplage et d'une architecture découpées en Controlleurs/Services/ORM avec inversion de contrôles (IOC ou Di = injection de dépendances). Je vais très bientôt faire un retour de ce que j'en ai tiré sur le forum, ça pourra peut-être te donner des idées, sachant qu'il n'y a pas LA bonne solution, d'autres personnes t'en proposeront, parfois tout aussi efficaces, d'autres insuffisantes. Tout ceci demande beaucoup de réflexion.
A+ benjamin.
Hors ligne
Tout d'abord merci Delprog !!
Ça c'est de l'explication, et j'y vois effectivement beaucoup plus clair.
La seule chose qui me turlupine dans ton explication, c'est
Enfin, la solution proposée par Zend dans le Quickstart est, selon moi, insuffisante. Par exemple, pour faire persister un objet métier, ils ont implémenté la méthode save() dans l'objet métier, ce qui, à mon sens est une abérration.
Tu pourrais me dire, pourquoi de ton point de vue c'est une abérration ?
Sinon oui, il est clair qu'un objet métier n'est pas égale à une seule table, celà à d'ailleur souvent été un problème dans mon cas, car travaillant sur un intranet de société (dont les informations doivent justement être accessible en webservices), il n'est pas rare d'avoir des requetes avec 5 jointures, et si traiter les données en sortie est assez facile, pour faire un enregistrement c'est parfois plus délicat, et me retrouvais donc avec toutes ces notions directement dans le controller. Je vois que la notion de mapper, et de découplage en général est la réponses à une question que je ne m'étais pas clairement posée.
J'ai asisté lors du forum php à la session d'information sur l'injection de dépendance (présenée par le createur de symphony) et m'étais promis d'approfondir le sujet, malheureusement mon employeur s'obstine à me faire travailler sans me laisser le temps d'en apprendre d'avantage ^^.
J'atendrai donc avec impatience (et ne serai surement pas le seul) ton retour d'expérience là-dessus.
Merci Encore
Hors ligne
Merci Delprog pour cette analyse !
J'ajoute un article intéressant (téléchargez le PDF) vers une conf sur les différents types d'ORM en PHP (bon, Zend_Db_Mapper n'était pas encore sorti...).
http://maggienelson.com/2009/05/orm-in-the-php-world/
et le PDF :
http://maggienelson.com/conferences/php … _World.pdf
A+, Philippe
Hors ligne
Si je peux te donner un conseil : va voir ce qu'il se passe coté Java : Hibernate, Spring et autres EJB, et si t'as l'occasion d'attaquer un projet Ruby avec Rails, prend le vite vite vite. Après tu ne verras plus jamais PHP de la même manière.
Aussi, renseigne-toi sur les patterns (et des bouquins sur ça .... il en existe des tonnes et des tonnes, les meilleurs sont en anglais), c'est la base de tout développement informatique "moderne", quel que soit le langage.
Hors ligne
Asfaloth a écrit:
La seule chose qui me turlupine dans ton explication, c'est
Enfin, la solution proposée par Zend dans le Quickstart est, selon moi, insuffisante. Par exemple, pour faire persister un objet métier, ils ont implémenté la méthode save() dans l'objet métier, ce qui, à mon sens est une abérration.
Tu pourrais me dire, pourquoi de ton point de vue c'est une abérration ?
En fait, un objet métier peut avoir plusieurs "status". Il peut être une "entité" (c'est grossier mais je vais utiliser ce terme) et doit donc pouvoir être persisté. Il peut être un simple objet volatile (calculs par ex.), qui n'est donc pas persisté et ne représente pas d'entité en terme de métier.
Dans tous les cas, ces objets, quels qu'ils soient ne déterminent pas eux même leurs "status" et le save() n'est surement pas de leur ressort. Implémenter un save() dans l'objet métier revient à faire connaitre à l'objet autre chose que le métier. Dans le quickstart, l'objet métier fait appel au Mapper, c'est très "sale" :p
Julien a écrit:
Si je peux te donner un conseil : va voir ce qu'il se passe coté Java : Hibernate, Spring et autres EJB, et si t'as l'occasion d'attaquer un projet Ruby avec Rails, prend le vite vite vite. Après tu ne verras plus jamais PHP de la même manière.
Pour moi, hibernate et spring MVC sont la crème. Dans ce que je proposerai bientôt, je me suis inspiré sans retenue de leurs fonctionnement en essayant de l'adapter à un environnement script. Seule chose qui me laisse dubitatif dans leurs dernières versions, c'est l'utilisation des annotations, bien que pratique, je ne sais pas ça me dérange.
Sinon, JAVA est un langage très mûre, et Spring MVC couplé à Hibernate implémentent des patterns très très efficaces (Factory, Proxy, Service Layer, DAO, ORM pour ne citer que ceux là).
Julien a écrit:
Aussi, renseigne-toi sur les patterns (et des bouquins sur ça .... il en existe des tonnes et des tonnes, les meilleurs sont en anglais), c'est la base de tout développement informatique "moderne", quel que soit le langage.
Et si tu es passionné, chaque fois que tu découvriras ce que tu peux faire d'un pattern tu t'illumineras :p
A+ benjamin.
Hors ligne
Merci à tous pour ces réponses, c'est pour ça que j'adore la progra, on arrete jamais de découvrir de nouvelles choses, et il faut constamment se remettre en question, certains diront qu'on est mazo
@Delprog
Et si tu es passionné, chaque fois que tu découvriras ce que tu peux faire d'un pattern tu t'illumineras :p
Ça c'est certain, et chaques fois on se dit, mais pourquoi j'ai pas connu ça plus tôt, et on a envie de refactorisé tout ce qu'on a déjà fait ^^ (et encore plus quand cela fait à peine plus d'un an qu'on est sortis du fonctionnel )
Par contre, désolé d'insister mais j'aime bien aller au fond des choses, le fameux save de la doc, tu l'aurais placer où et comment ?
@philippe
J'ai parcouru en vitesse, ça éclaire pas mal de chose aussi, merci
@Julien
Promis dès que j'ai 10 min j'apprend le java et le ruby pour voir ce qui se fait de l'autre coté ^^
Sinon pour les bouquins si tu as un ouvrage ou une maison d'édition à me conseiller en particulier, je suis preneur (en attendant je vai voir si ya pas qqch qui traine chez o'reilly et eyrolle)
Hors ligne
Par contre, désolé d'insister mais j'aime bien aller au fond des choses, le fameux save de la doc, tu l'aurais placer où et comment ?
L'idée serait de placer le save dans le mapper, on aurait :
$mapper->save($objetMetier);
au lieu de
$objetMetier->save();
Sur le principe je suis plutôt d'accord avec Delprog. C'est particulièrement choquant pour un delete : quand tu fais $objetMetier->delete(), ton instance a disparu en base et tu continues à avoir un $objetMetier un peu zombi... c'est pas à l'objet métier de se détruire lui même, c'est plutôt au mappeur...
Cependant d'un point de vue pragmatique, je trouve que c'est souvent bien pratique d'avoir ces méthodes dans l'objet métier... Sur le principe ça me choque un peu, mais dans un code c'est tellement pratique que je suis un peu plus mesuré sur ce point... (il faut s'appuyer sur les principes, ils finiront bien par céder )
A+, Philippe
Hors ligne
philippe a écrit:
Cependant d'un point de vue pragmatique, je trouve que c'est souvent bien pratique d'avoir ces méthodes dans l'objet métier... Sur le principe ça me choque un peu, mais dans un code c'est tellement pratique que je suis un peu plus mesuré sur ce point... (il faut s'appuyer sur les principes, ils finiront bien par céder smile )
A ce moment là, il faut utiliser le pattern proxy.
Exemple:
Model_Entity_User = objet métier pure
Model_User = Proxy, étend Model_Entity_User et implémente la méthode save().
Dans ton code tu instancies toujours le proxy, dans les tests unitaires tu instancies l'entité :p
A+ benjamin.
Hors ligne
Bonjour,
Delprog a écrit:
Par exemple, ton application doit afficher des galeries d'images, et ces galeries appartiennent à des utilisateurs.
Tu vas avoir une entité Utilisateur dans laquelle tu auras des galeries qui sont elle-mêmes des entités Galerie. C'est la définition des entités qui répondent au métier, au domaine abordé.
Du coup ici on aurait deux objets metier, Utilisateur et Gallerie ? Dès lors l'appel au mapper pour récupérer un objet métier Utilisateur opèrerait (dans le cas d'une base de données avec 2 tables user/gallery) un select en base pour récupérer l'utilisateur (sur user), injectant les données dans un objet métier Utilisateur, puis ensuite un select pour récupérer toutes les galleries de l'utilisateur (sur gallery), les injectant chacune dans un objet métier Gallerie et ensuite injectant ces objets métier Gallerie dans une propriété (tableau) de l'objet métier Utilisateur (array galleries contenant donc les objets métiers gallerie) ?
Si c'est ainsi, est-ce qu'il convient que des mappers puissent communiquer entre eux ? A savoir que pour récupérer les galleries le mapper Utilisateur pourrait appeler le mapper Gallerie qui lui renverrait tous les objets Gallerie peuplés ?
Et lors d'une modification/ajout/suppression d'une gallerie à partir d'un objet métier Utilisateur le mapper Utilisateur passerait au mapper Gallerie les objets contenu dans l'objet métier l'Utilisateur pour qu'il les ajoute/modifie/supprime en base ?
Hors ligne
Salut,
Ce que tu décris s'appelle en fait du full loading. C'est à dire que toutes les dépendances sont peuplées dès le départ.
De mon côté j'ai tout implémenté en Lazy-load, c'est à dire que les dépendances ne sont chargées (comprendre requête + création et peuplage de/des entité(s)) que lorsqu'elles sont réclamées pour la première fois.
J'ai voulu conservé quelque chose du type $user->galleries[0]->name, et le pattern proxy a été la solution.
Du côté mapper, j'ai laissé tombé la solution un objet = un mapper, de même que j'ai laissé tomber le pattern Gateway et je n'ai plus de Zend_Db_Table pour chaque table.
J'ai un unique Mapper, un adapter Db et des DAO, les proxy, qui étendents les entités construisent eux-mêmes les requêtes et s'adressent directement au mapper. Après réflexion j'ai pris cette décision car j'ai implémenté un générateur de classes, qui génère aussi les proxy, et si la persistance change, je n'aurai à modifier que mon générateur.
J'essaie de trouver du temps pour expliquer tout ça en détail.
A+ benjamin.
Dernière modification par Delprog (14-09-2009 10:58:22)
Hors ligne
Delprog a écrit:
J'essaie de trouver du temps pour expliquer tout ça en détail.
Merci, comme d'habitude enrichissant.
La solution que tu étudies/testes semble relativement performante et facilement maintenable, bien pratique en soit.
A l'époque où tu utilisaient les mappers par objet, te souciais-tu du fait qu'une propriété d'un objet avait pu être modifiée ou non ?
Je pense à un Utilisateur ayant 100 objets Gallerie, au moment d'opérer un save de l'Utilisateur, comment savoir si tout ou partie des galleries devaient être également modifiées dans la persistance ? (éventuellement une notion de propriété (tableau) "$_modified" par objet, recensant les propriétés modifiées au sein d'un objet ?)
Hors ligne
Salut,
J'ai commencé à donner quelques indications ici : http://www.z-f.fr/forum/viewtopic.php?id=4027
Je donnerai des exemples de code un peu plus tard. Gardez à l'esprit que c'est une implémentation parmis d'autres, bien qu'éprouvée par d'autres langages, ce n'est pas LA solution ni l'unique possibilité.
Eureka a écrit:
A l'époque où tu utilisaient les mappers par objet, te souciais-tu du fait qu'une propriété d'un objet avait pu être modifiée ou non ?
Je pense à un Utilisateur ayant 100 objets Gallerie, au moment d'opérer un save de l'Utilisateur, comment savoir si tout ou partie des galleries devaient être également modifiées dans la persistance ? (éventuellement une notion de propriété (tableau) "$_modified" par objet, recensant les propriétés modifiées au sein d'un objet ?)
Je n'ai pas encore implémenté le save en cascade, uniquement le Lazy Load pour l'instant. C'est à l'étude
A+ benjamin.
Hors ligne