Bonjour à tous,
Existe il un générateur sérieux PHP de Model en PHP pour Zend_db soit à partir d'un schéma de base existant ou bien d'un XML ?
Si non est ce une idée pertinente d'en écrire un ?
Bonjour à tous,
Existe il un générateur sérieux PHP de Model en PHP pour Zend_db soit à partir d'un schéma de base existant ou bien d'un XML ?
Si non est ce une idée pertinente d'en écrire un ?
C'est une très bonne question, que je me pose moi même depuis quelques temps. Générer un Model et un Data Mapper est quelque chose de compliqué, qu'il est impossible d'automatiser à 100%.
Pourtant, ça prends énormément de temps à créer initialement avec beaucoup d'éléments répétitifs...
Je ne peux pas t'aider pour ce qui est de savoir si une solution correcte existe pour générer les modèles, je n'ai pas encore cherché, même si à ma connaissance les générateurs se bornent souvent à générer des objets fortement liés à une base de données (principalement des ActiveRecords), et on s'éloigne de ce que je recherche.
Par contre, j'ai hier soir (les grands esprits se rencontrent) créé un petit prototype de générateur de modèle qui utilise un Zend_Db_Table_Abstract et qui donne rapidement un résultat assez satisfaisant.
Il manque encore (au moins) deux choses fondamentales :
- Externaliser les algorithmes de normalisation des membres du modèle par rapport aux champs de la base. L'externaliser sous forme d'une Stratégie permettrait de plus facilement personnaliser les règles de nommage et de les adapter au cas par cas.
- Externaliser la gestion du typage, car pour ma part un modèle doit être fortement typé et forcer un cast pour les types natifs (int, string, bool...) ou une définition de méthode typée pour les objets dans ses méthodes set*. Je pense qu'il faut l'externaliser car personnellement je fournis les champs de type date d'une base de données dans un objet Zend_Date pour les modèles, ce qui facilite l’interaction application / data layer, mais ça peut ne pas convenir tout le temps, et encore moins à tout le monde (surtout sur le plan des performances).
A mon avis ce genre d'outils n'a d'utilité qu'en création pour gagner un peu de temps : elle implique après coup tellement de réécriture (relation entre modèles, cas où les modèles et les tables ne correspondent pas 1 à 1, nommage à adapter à cause d'une base mal nommée...) qu'une fois le modèle finalisé il ne peut plus "évoluer" grâce à cet outil si la table change, il faudra tout faire à la mano.
Voici le code de la classe, je suis preneur de tout avis bien évidemment, mais si l'outil peut être utile (et si la roue n'a pas déjà été inventée ailleurs) ça vaut le coup d'approfondir le concept et d'étendre Zend_Tool pour l'exploiter en ligne de commande.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 class ModelGenerator extends Zend_CodeGenerator_Php_Class { /** * * @var Zend_Db_Table_Abstract */ protected $_dbTable; /** * * @var string */ protected $_appNamespace = 'Application'; /** * * @return Zend_Db_Table_Abstract */ public function getDbTable () { return $this->_dbTable; } /** * * @param Zend_Db_Table_Abstract $dbTable * @return Zend_Db_Table_Abstract */ public function setDbTable ($dbTable) { $this->_dbTable = $dbTable; return $this; } /** * * @return string */ public function getAppNamespace () { return $this->_appNamespace; } /** * * @param string $appNamespace * @return ModelGenerator */ public function setAppNamespace ($appNamespace) { $this->_appNamespace = $appNamespace; return $this; } /** * * @return string * @see Zend_CodeGenerator_Php_Class::generate() */ public function generate() { $dbTable = $this->getDbTable(); $appNamespace = $this->getAppNamespace(); if (null !== $dbTable) { $tableInfos = $dbTable->info(); $tableName = $tableInfos['name']; $className = $appNamespace . '_Model_' . $tableName; $this->setName($className); $this->setDocblock(array( 'shortDescription' => "$tableName Model", 'tags' => array( array( 'name' => 'category', 'description' => $appNamespace ), array( 'name' => 'package', 'description' => 'Default' ), array( 'name' => 'subpackage', 'description' => 'Model' ), ) )); foreach ($tableInfos['metadata'] as $name => $infos) { $this->addProperty($name, $infos['DATA_TYPE'], $className); } } return parent::generate(); } public function addProperty($name, $type, $className) { $this->setProperty(array( 'docBlock' => array( 'tags' => array( array( 'name' => 'var', 'description' => $type ) ) ), 'visibility' => 'protected', 'name' => $name )); $this->setMethod(array( 'docBlock' => array( 'tags' => array( array( 'name' => 'return', 'description' => $type ) ) ), 'body' => 'return $this->_' . $name . ';', 'visibility' => 'public', 'name' => 'get' . ucfirst($name) )); $this->setMethod(array( 'docBlock' => array( 'tags' => array( array( 'name' => 'param', 'description' => $type . ' $value' ), array( 'name' => 'return', 'description' => $className ) ) ), 'parameters' => array( array( 'name' => 'value', 'type' => $type ) ), 'body' => '$this->_' . $name . ' = $value;' . "\n" . 'return $this;', 'visibility' => 'public', 'name' => 'set' . ucfirst($name) )); } }
Merci beaucoup pour ta réponse,
Je suis entièrement d'accord et ce fait et celui d'avoir utiliser Hibernate pendant des années qui m'ont amener à cette réflexion. Comme c'est un processus qui est dans tous les ORM java que j'ai utilisé je suis très étonné de ne pas avoir trouvé ceci dans Zend_Db.ça prends énormément de temps à créer initialement avec beaucoup d'éléments répétitifs...
Mon souhait est d'avoir quelque chose qu'y se rapproche d'Hibernate dans la génération des objets proxy. Ce type d'outils permet :
- de gagner beaucoup de temps
- de fortement réduire les erreurs par rapport à une écriture manuelle
- de générer sur le même norme tous ses objets du model
Pour Zend_Date je fais pareil. Le but est l’intégration dans Zend Framework il faut pas commencer par quelque chose trop ambitieux.Je pense qu'il faut l'externaliser car personnellement je fournis les champs de type date d'une base de données dans un objet Zend_Date pour les modèles, ce qui facilite l’interaction application / data layer, mais ça peut ne pas convenir tout le temps, et encore moins à tout le monde
A mon avis pas complétement si les classes générés sont exploitées seulement en héritage sans jamais y insérer de code manuel ça réduirait ce problème. Je prend pour exemple d'Hibernate. Si la Bdd change il suffit de régénérer le code.qu'une fois le modèle finalisé il ne peut plus "évoluer" grâce à cet outil si la table change, il faudra tout faire à la mano.
Si par chance ce ne sont que ajout de colonnes ça passe comme une lettre à la poste.
Si c'est autre chose ça fera toujours du temps de gagner.
[hors sujet]:
Pour l'application je trouve que c'est plus pratique de lancer sous Apache pour faire des rapport Web et debugger et pas en standalone.
A tout hasard, Doctrine ne répondrais pas à tes besoins ? http://www.doctrine-project.org/
C'est une question très "naïve", je n'ai pour ma part pas encore eut le temps de m'y "plonger" pour voir si il permettait de faire ce que je cherche, mais de loin ça ne m'a pas paru être le cas.
Par contre pour le coup, je ne pense pas que Zend_Db puisse être considéré comme un ORM : il fournit "simplement" une interface d'accès à une base de donnée via Zend_Db_Adapter et une implémentation d'un Table Data Gateway
avec Zend_Db_Table, laissant le développeur libre de ses propres choix en terme de mapping.
En effet, par contre je pense qu'effectuer un casting sur les types natifs et typer fortement les autres (Zend_Date...) est un minimum pour l'intérêt de la solution, sans ça il faudrait modifier à la main tous les setters...
Je ne suis pas sûr de te suivre sur ce terrain... Les relations base / modèles ne sont pas toujours du 1 / 1 : il se peut qu'une seule table génère 3 ou 4 modèles sans liens de parenté les uns avec les autres.
Dans ce contexte, ça me paraît dangereux de re-générer les modèles une fois le premier travail de mapping terminé...
Surcharger les modèles systématiquement me paraît aussi assez "dangereux" : explosion du nombre de classes, complexité accrue et... même de perte de temps, au final.
Mais bon, je pense que c'est une question de sensibilité personnelle plus qu'autre chose, et ça ne change pas le besoin de base : générer les modèles...
Même si j'aime bien le "support" CLI, je pense que l'interfaçage Zend_Tool est plus un objectif secondaire qu'une finalité : il ne faut pas empêcher de piloter la création de modèles directement.
J'essaye de bricoler un prototype plus aboutis ce week-end car je vais rapidement avoir besoin d'une solution viable pour divers projets.
J'ai beaucoup hésité entre Doctrine et Zend_DB.A tout hasard, Doctrine ne répondrais pas à tes besoins ?
Doctrine est lui un ORM et est plus proche d'hibernate et par conséquent c'est nettement plus proche de que je voudrais. Par contre je n'ai pas trouvé de fonction de retro création à partir d'un schéma existant. Etant donné que je n'ai jamais utilisé doctrine je ne me suis pas étendu fortement en recherche sur le sujet.
Le souci avec Doctrine c'est que dans Zend Framework 1.X n'a pas d' "adapter" pour Doctrine du coup plein de classe de Zend Framework ne peuvent l'utiliser nativement.
Je prend l'exemple de Zend_Paginator il prend en paramètre un Select de Zend_db et pas Doctrine. A priori dans ZF2 ce sera possible (au conditionnel).
C'est mon constat aussi jusqu'ici.
J'ai travaillé un peu sur le sujet, et j'ai désormais un résultat acceptable, bien entendu à éprouver sur des conventions de nommage différentes...
A partir de la table suivante, issue d'un projet perso :
Il me génère le code suivant :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 CREATE TABLE IF NOT EXISTS `ActivityItem` ( `idActivityItem` INT NOT NULL AUTO_INCREMENT , `titleActivityItem` VARCHAR(255) NOT NULL , `descriptionActivityItem` VARCHAR(255) NULL , `linkActivityItem` VARCHAR(255) NOT NULL , `externalIdActivityItem` VARCHAR(255) NOT NULL , `publicationDateActivityItem` DATETIME NOT NULL , `addedDateActivityItem` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , `idActivitySourceActivityItem` INT NOT NULL );
Comme le projet commence à prendre en envergure (8 classes maintenant) si ça t'intéresse je peux créer un dépôt Github, ça sera plus simple que de mettre des extraits de code source ici.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196 /** * ActivityItem Model * * @category Application * @package Default * @subpackage Model */ class Application_Model_ActivityItem { /** * @var integer */ protected $_id = null; /** * @var string */ protected $_title = null; /** * @var string */ protected $_description = null; /** * @var string */ protected $_link = null; /** * @var string */ protected $_externalId = null; /** * @var Zend_Date */ protected $_publicationDate = null; /** * @var Zend_Date */ protected $_addedDate = null; /** * @var integer */ protected $_idActivitySource = null; /** * @return integer */ public function getId() { return $this->_id; } /** * @param integer $value * @return Application_Model_ActivityItem */ public function setId($value) { $this->_id = (integer) $value; return $this; } /** * @return string */ public function getTitle() { return $this->_title; } /** * @param string $value * @return Application_Model_ActivityItem */ public function setTitle($value) { $this->_title = (string) $value; return $this; } /** * @return string */ public function getDescription() { return $this->_description; } /** * @param string $value * @return Application_Model_ActivityItem */ public function setDescription($value) { $this->_description = (string) $value; return $this; } /** * @return string */ public function getLink() { return $this->_link; } /** * @param string $value * @return Application_Model_ActivityItem */ public function setLink($value) { $this->_link = (string) $value; return $this; } /** * @return string */ public function getExternalId() { return $this->_externalId; } /** * @param string $value * @return Application_Model_ActivityItem */ public function setExternalId($value) { $this->_externalId = (string) $value; return $this; } /** * @return Zend_Date */ public function getPublicationDate() { return $this->_publicationDate; } /** * @param Zend_Date $value * @return Application_Model_ActivityItem */ public function setPublicationDate(Zend_Date $value) { $this->_publicationDate = $value; return $this; } /** * @return Zend_Date */ public function getAddedDate() { return $this->_addedDate; } /** * @param Zend_Date $value * @return Application_Model_ActivityItem */ public function setAddedDate(Zend_Date $value) { $this->_addedDate = $value; return $this; } /** * @return integer */ public function getIdActivitySource() { return $this->_idActivitySource; } /** * @param integer $value * @return Application_Model_ActivityItem */ public function setIdActivitySource($value) { $this->_idActivitySource = (integer) $value; return $this; } }
Partager