Development

/plugins/sfPropelAlternativeSchemaPlugin/README

You must first sign up to be able to contribute.

root/plugins/sfPropelAlternativeSchemaPlugin/README

Revision 8162, 14.7 kB (checked in by francois, 3 months ago)

sfPropelAlternativeSchemaPlugin Removed fix for a bug fixed in the symfony trunk (closes #3048)

Line 
1 = sfPropelAlternativeSchemaPlugin - Extension to the Propel schema syntax =
2
3 == Overview ==
4
5 This plugin extends the symfony model generator, based on Propel, to allow a schema to override another one. Using this plugin, you can add columns, change table names, or the database connection without modifying existing schemas.
6
7 It also provides a new optional YAML syntax for defining database schemas, more explicit and more readable than the current one. Finally, this alternative schema syntax adds a few new features to schemas, such as the ability to define a behavior from a schema.
8
9 == Installation ==
10  
11 To install the plugin for a symfony project, the usual process is to use the symfony command line:
12 {{{
13 $ php symfony plugin-install http://plugins.symfony-project.com/sfPropelAlternativeSchemaPlugin
14 }}}
15
16 Alternatively, if you don't have PEAR installed, you can download the latest package attached to this plugin's wiki page and extract it under your project's `plugins/` directory.
17
18 Clear the cache to enable the autoloading to find the new classes:
19 {{{
20 $ php symfony cc
21 }}}
22
23 That's it, you are ready to override existing schemas and write schemas with the new syntax.
24
25 == Customizing an existing schema ==
26
27 Once the plugin is installed, schemas can be customized by other schemas. This is great for plugin schemas, for instance, to allow users to override some of the plugin's schema settings (such as the connection, the table names, etc), or to allow one plugin to extend another plugin.
28
29 When building the model, the plugin will look for custom YAML files for each schema, following this rule:
30
31 || '''Original schema name'''             || '''Custom schema name'''       ||
32 || config/schema.yml                      || schema.custom.yml              ||
33 || config/foobar_schema.yml               || foobar_schema.custom.yml       ||
34 || plugins/myPlugin/config/schema.yml     || myPlugin_schema.custom.yml     ||
35 || plugins/myPlugin/config/foo_schema.yml || myPlugin_foo_schema.custom.yml ||
36
37 Custom schemas will be looked for in the application's and plugins' `config/` directories, so a plugin can override another plugin's schema, and there can be more than one customization per schema.
38
39 The plugin will merge the two schemas in a smart way, as follows:
40
41 {{{
42 # Original schema
43 #################
44 propel:
45   article:
46     _attributes:    { phpName: Article }
47     title:          varchar(50)
48     user_id:        { type: integer }
49     created_at:
50
51 # Custom schema
52 ###############
53 propel:
54   article:
55     _attributes:    { phpName: Article, package: foo.bar.lib.model }
56     stripped_title: varchar(50)
57
58 # Resulting schema
59 ##################
60 propel:
61   article:
62     _attributes:    { phpName: Article, package: foo.bar.lib.model }
63     title:          varchar(50)
64     user_id:        { type: integer }
65     created_at:
66     stripped_title: varchar(50)
67 }}}
68
69 When merging two tables, the plugin will consider the table's `phpName` as a key, and therefore you can change the name of a table in the database, provided that you keep the same `phpName` in the schema. Here is a more complex example:
70
71 {{{
72 # Original schema
73 #################
74 propel:
75   cd_user:
76     _attributes:    { phpName: User }
77     first_name:     { type: varchar, size: 255, default: "Anonymous" }
78     last_name:      varchar(50)
79     age:            { type: integer, required: true, index: true }
80     created_at:
81  
82   ij_article:
83     _attributes:    { phpName: Article }
84     title:          varchar(50)
85     user_id:        { type: integer }
86     created_at:
87     _foreignKeys:
88       -
89         foreignTable: cd_user
90         onDelete:     cascade
91         references:
92           - { local: user_id, foreign: id }
93
94 # Custom schema
95 ###############
96 myConnection:
97   ab_group:
98     _attributes:    { phpName: Group, package: foo.bar.lib.model }
99     id:
100     name:           varchar(50)
101  
102   ef_user:
103     _attributes:    { phpName: User, isI18N: true, i18nTable: cd_user_i18n }
104     ab_group_id:
105  
106   ij_article:
107     updated_at:
108
109 # Resulting schema
110 ##################
111 myConnection:
112   ef_user:
113     _attributes:    { phpName: User, isI18N: true, i18nTable: cd_user_i18n }
114     first_name:     { type: varchar, size: 255, default: "Anonymous" }
115     last_name:      varchar(50)
116     age:            { type: integer, required: true, index: true }
117     created_at:
118     ab_group_id:
119  
120   ij_article:
121     _attributes:    { phpName: Article }
122     title:          varchar(50)
123     user_id:        { type: integer }
124     created_at:
125     updated_at:
126     _foreignKeys:
127       -
128         foreignTable: cd_user
129         onDelete:     cascade
130         references:
131           - { local: user_id, foreign: id }
132
133   ab_group:
134     _attributes:    { phpName: Group, package: foo.bar.lib.model }
135     id:
136     name:           varchar(50)
137 }}}
138
139 == New schema syntax ==
140
141 As an alternative to the current `schema.yml` syntax (which still works), this plugin proposes a new way to define a database schema.
142
143 Consider the following schema, using the current syntax:
144
145 {{{
146 propel:
147   _attributes:      { noXsd: false, defaultIdMethod: none, package: lib.model }
148   ab_group:
149     _attributes:    { phpName: Group, package: foo.bar.lib.model }
150     id:
151     name:           varchar(50)
152    
153   cd_user:
154     _attributes:    { phpName: User, isI18N: true, i18nTable: cd_user_i18n }
155     first_name:     { type: varchar, size: 255, default: "Anonymous" }
156     last_name:      varchar(50)
157     age:            { type: integer, required: true, index: true }
158     ab_group_id:
159     created_at:
160  
161   cd_user_i18n:
162     description:    longvarchar
163    
164   ef_article:
165     title:          { type: longvarchar, required: true, index: unique }
166     stripped_title: { type: longvarchar, required: true, primaryKey: true, sequence: my_custom_sequence_name }
167     user_id:
168     my_group:       { type: integer, foreignTable: ab_group, foreignReference: id, onDelete: setnull }
169     created_at:     timestamp
170     updated_at:
171
172   ij_article:
173     _attributes:    { phpName: Article }
174     title:          varchar(50)
175     user_id:        { type: integer }
176     _foreignKeys:
177       -
178         foreignTable: cd_user
179         onDelete:     cascade
180         references:
181           - { local: user_id, foreign: id }
182     created_at:
183     _indexes:
184       my_index:       [title, user_id]
185     _uniques:
186       my_other_index: [created_at]
187  
188   ab_group_i18n:
189     motto:            longvarchar
190 }}}
191
192 With the alternative syntax, you can write it as follows:
193
194 {{{
195 connection:           propel
196 noXsd:                false
197 defaultIdMethod:      none
198 package:              lib.model
199
200 classes:
201   Group:
202     tableName:        ab_group
203     package:          foo.bar.lib.model
204     columns:
205       id:
206       name:           varchar(50)
207  
208   User:
209     tableName:        cd_user
210     isI18N:           true
211     i18nTable:        cd_user_i18n
212     columns:
213       first_name:     { type: varchar, size: 255, default: "Anonymous" }
214       last_name:      varchar(50)
215       age:            { type: integer, required: true, index: true }
216       ab_group_id:
217       created_at:
218  
219   CdUserI18n:
220     columns:
221       description:    longvarchar
222  
223   EfArticle:
224     columns:
225       title:          { type: longvarchar, required: true, index: unique }
226       stripped_title: { type: longvarchar, required: true, primaryKey: true, sequence: my_custom_sequence_name }
227       user_id:
228       my_group:       { type: integer, foreignClass: Group, foreignReference: id, onDelete: setnull }
229       created_at:     timestamp
230       updated_at:
231  
232   Article:
233     tableName:        ij_article
234     columns:
235       title:          varchar(50)
236       user_id:        { type: integer }
237       created_at:
238     foreignKeys:
239       -
240         foreignTable: cd_user
241         onDelete:     cascade
242         references:
243           - { local: user_id, foreign: id }
244     indexes:
245       my_index:       [title, user_id]
246     uniques:
247       my_other_index: [created_at]
248  
249   AbGroupI18n:
250     columns:
251       motto:          longvarchar
252 }}}
253
254 The main difference is that you declare classes, not tables, using the table `phpName` as a key. If you don't define a `tableName`, the plugin will determine one automatically based on the `phpName` using `sfInflector::underscore()`.
255
256 This alternative syntax is also more explicit, since you must create entries for `classes` and `columns`. But it gets rid of the ugly `_attributes` hack of the current syntax.
257
258 The `connection` parameter is optional. If it is not set, it will take `propel` as a default value.
259
260 Note that you can define foreign keys either with the usual `foreignTable` attribute, which expects a table name, or via the new `foreignClass` attribute, which expects a class name.
261
262 Last but not least, all the 'magic' of the current syntax is still there (auto definition of primary keys, foreign keys, i18n tables, etc.).
263
264 Once you have defined such a schema, rebuild the model as usual:
265
266 {{{
267 $ php symfony propel-build-model
268 }}}
269
270 The plugin will recognize the alternative syntax automatically. Note that you can have, in a project, schemas with mixed current and alternative syntax.
271
272 == Behaviors ==
273
274 The alternative schema syntax allows you to define behaviors directly from the schema itself. To allow the support for these schema behaviors, you must change two lines in the 'Builder Settings' section of your project's `propel.ini`:
275
276 {{{
277 // In config/propel.ini
278 ; builder settings
279 propel.builder.peer.class              = plugins.sfPropelAlternativeSchemaPlugin.lib.SfAlternativePeerBuilder
280 propel.builder.object.class            = plugins.sfPropelAlternativeSchemaPlugin.lib.SfAlternativeObjectBuilder
281 }}}
282
283 Now you can add a `behaviors` section for each class that you define in a schema, as follows:
284
285 {{{
286 classes:
287   Article:
288     columns:
289       title:          varchar(50)
290     behaviors:
291       paranoid:       { column: deleted_at }
292 }}}
293
294 Of course, don't forget to rebuild the model after you modify your schema.
295
296 It is also possible to define behaviors in the current syntax if you enabled the custom builder in the `propel.ini`. Just add a leading underscore before the `behaviors` key and define behaviors the same a above:
297
298 {{{
299 propel:
300   ij_article:
301     _attributes:    { phpName: Article }
302     title:          varchar(50)
303     _behaviors:
304       paranoid:     { column: deleted_at }
305 }}}
306
307
308 Note: Incidentally, behaviors entered this way are registered both in the model and peer classes, which seems to solve some problems with behaviors (like #1229).
309
310 == Mixed schemas ==
311
312 The schema customization works whatever the original schema syntax and whatever the custom schemas syntax. This means that you can customize an existing schema with the old syntax using a custom schema with the new syntax, and vice-versa. The plugin will do the conversion internally so that the merge is always possible.
313
314 Note that the schema merge is easier to understand when considering the alternative syntax for both the original and the custom schema. In fact, this is the internal format used by the plugin for the merge. The following listing is the same example as the one in the "Customizing an existing schema" section, except it uses the alternative schema syntax... and behaviors.
315
316 {{{
317 # Original schema
318 #################
319 classes:
320   User:
321     tableName:        cd_user
322     columns:
323       first_name:     { type: varchar, size: 255, default: "Anonymous" }
324       last_name:      varchar(50)
325       age:            { type: integer, required: true, index: true }
326       created_at:
327  
328   Article:
329     tableName:        ij_article
330     columns:
331       title:          varchar(50)
332       user_id:        { type: integer }
333       created_at:
334     foreignKeys:
335       -
336         foreignTable: cd_user
337         onDelete:     cascade
338         references:
339           - { local: user_id, foreign: id }
340
341 # Custom schema
342 ###############
343 connection: myConnection
344 classes:
345   Group:
346     tableName:        ab_group
347     package:          foo.bar.lib.model
348     behaviors:        [paranoid]
349     columns:
350       id:
351       name:           varchar(50)
352
353   User:
354     tableName:        ef_user
355     isI18N:           true
356     i18nTable:        cd_user_i18n
357     columns:
358       ab_group_id:
359  
360   Article:
361     columns:
362       updated_at:
363
364 # Resulting schema
365 ##################
366 connection: myConnection
367 classes:
368   Group:
369     tableName:        ab_group
370     package:          foo.bar.lib.model
371     behaviors:        [paranoid]
372     columns:
373       id:
374       name:           varchar(50)
375  
376   User:
377     tableName:        cd_user
378     isI18N:           true
379     i18nTable:        cd_user_i18n
380     columns:
381       first_name:     { type: varchar, size: 255, default: "Anonymous" }
382       last_name:      varchar(50)
383       age:            { type: integer, required: true, index: true }
384       ab_group_id:
385       created_at:
386  
387   Article:
388     tableName:        ij_article
389     columns:
390       title:          varchar(50)
391       user_id:        { type: integer }
392       created_at:
393       updated_at:
394     foreignKeys:
395       -
396         foreignTable: cd_user
397         onDelete:     cascade
398         references:
399           - { local: user_id, foreign: id }
400 }}}
401
402 For clarity, it is recommended to use the alternative schema syntax as much as possible.
403
404 == Checking that the plugin is installed ==
405
406 Alternative schemas can sometimes look like normal YAML schemas. If this plugin is not installed, the usual schema interpreter may try to transform a schema with the alternative syntax into an XML schema, but based on the usual syntax. This will most probably cause problems.
407
408 To avoid this, you should check that the plugin is installed before trying to interpret an alternative YAML schema. To do so, a good trick is to take advantage of the fact that YAML files are executed as PHP files before being converted to arrays. So add the following line on top of every alternative YAML schema:
409
410 {{{
411 <?php if(!is_callable(array('sfPropelDatabaseSchema', 'convertOldToNewYaml'))) throw new Exception('You must install the sfPropelAlternativeSchemaPlugin to use this schema') ?>
412 }}}
413
414 == Todo ==
415
416  * Refactor `sfPropelDatabaseSchema` to use the new schema syntax internally
417  
418 == Changelog ==
419
420 === 2008-£03-30 | Trunk ===
421
422  * francois: Removed fix for a bug fixed in the symfony trunk
423  * francois: Made the plugin compatble with sfPropelVersionableBehaviorPlugin
424  * lking: Fixed problem on alternative schema detection on Windows systems
425
426 === 2007-10-18 | 1.0.0 Stable ===
427
428  * francois: Added support for behaviors in the old syntax, too
429  * francois: Added new/old YAML syntax conversion. It is now possible to customize an old schema even if it doesn't use the alternative syntax.
430  * francois: Added a fix for the too late initialization of behaviors in symfony when adding hooks to custom class
431  * francois: Added a way to define behaviors from the schema
432  * francois: Added a new `foreignClass` column attribute to define a foreign key from a phpName rather than from a tableName
433  * francois: Added section about plugin check in README
434  * francois: Fixed a problem with model class names beginning with a lowercase character
435
436 === 2007-10-05 | 0.9.0 Beta ===
437
438  * francois: initial release
Note: See TracBrowser for help on using the browser.