Changeset 5564
- Timestamp:
- 10/17/07 16:21:41 (1 year ago)
- Files:
-
- plugins/sfPropelAlternativeSchemaPlugin/README (modified) (8 diffs)
- plugins/sfPropelAlternativeSchemaPlugin/lib/sfPropelDatabaseSchema.class.php (modified) (4 diffs)
- plugins/sfPropelAlternativeSchemaPlugin/test/unit/fixtures/new_schema.yml (modified) (1 diff)
- plugins/sfPropelAlternativeSchemaPlugin/test/unit/sfPropelDatabaseSchemaTest.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
plugins/sfPropelAlternativeSchemaPlugin/README
r5561 r5564 3 3 == Overview == 4 4 5 This plugin extends the symfony model generator, based on Propel, to allow a schema to override another one. It also provides a new YAML syntax for defining database schemas, more explicit and more readable. This new syntax is completely backward compatible with symfony's current `schema.yml` syntax, so installing this plugin will not break your applications. Additionally, the new syntax adds a few new features to schemas, such as the ability to define a behavior from a schema. 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. 6 8 7 9 == Installation == … … 19 21 }}} 20 22 21 That's it, you are ready to write schemas with the new syntax and override existing schemas. 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 -----------------------------------------|------------------------ 33 config/schema.yml | schema.custom.yml 34 config/foobar_schema.yml | foobar_schema.custom.yml 35 plugins/myPlugin/config/schema.yml | myPlugin_schema.custom.yml 36 plugins/myPlugin/config/foo_schema.yml | myPlugin_foo_schema.custom.yml 37 38 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. 39 40 The plugin will merge the two schemas in a smart way, as follows: 41 42 {{{ 43 # Original schema 44 ################# 45 propel: 46 cd_user: 47 _attributes: { phpName: User } 48 first_name: { type: varchar, size: 255, default: "Anonymous" } 49 last_name: varchar(50) 50 age: { type: integer, required: true, index: true } 51 created_at: 52 53 ij_article: 54 _attributes: { phpName: Article } 55 title: varchar(50) 56 user_id: { type: integer } 57 created_at: 58 _foreignKeys: 59 - 60 foreignTable: cd_user 61 onDelete: cascade 62 references: 63 - { local: user_id, foreign: id } 64 65 # Custom schema 66 ############### 67 myConnection: 68 ab_group: 69 _attributes: { phpName: Group, package: foo.bar.lib.model } 70 id: 71 name: varchar(50) 72 73 ef_user: 74 _attributes: { phpName: User, isI18N: true, i18nTable: cd_user_i18n } 75 ab_group_id: 76 77 ij_article: 78 updated_at: 79 80 # Resulting schema 81 ################## 82 myConnection: 83 ef_user: 84 _attributes: { phpName: User, isI18N: true, i18nTable: cd_user_i18n } 85 first_name: { type: varchar, size: 255, default: "Anonymous" } 86 last_name: varchar(50) 87 age: { type: integer, required: true, index: true } 88 created_at: 89 ab_group_id: 90 91 ij_article: 92 _attributes: { phpName: Article } 93 title: varchar(50) 94 user_id: { type: integer } 95 created_at: 96 updated_at: 97 _foreignKeys: 98 - 99 foreignTable: cd_user 100 onDelete: cascade 101 references: 102 - { local: user_id, foreign: id } 103 104 ab_group: 105 _attributes: { phpName: Group, package: foo.bar.lib.model } 106 id: 107 name: varchar(50) 108 }}} 109 110 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. 22 111 23 112 == New schema syntax == … … 74 163 }}} 75 164 76 With the newsyntax, you can write it as follows:165 With the alternative syntax, you can write it as follows: 77 166 78 167 {{{ … … 138 227 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()`. 139 228 140 This 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.229 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. 141 230 142 231 The `connection` parameter is optional. If it is not set, it will take `propel` as a default value. … … 152 241 }}} 153 242 243 The plugin will recognize the alternative syntax automatically. Note that you can have, in a project, schemas with mixed current and alternative syntax. 244 154 245 == Behaviors == 155 246 156 The new schema syntax alows 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`:247 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`: 157 248 158 249 {{{ … … 176 267 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). 177 268 178 == Customizing an existing schema == 179 180 Schemas using this new syntax 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). 181 182 When dealing with a schema with the alternative syntax, the symfony builder will look for custom YAML files for this schema, following this rule: 183 184 Original schema name | Custom schema name 185 -----------------------------------------|------------------------ 186 config/schema.yml | schema.custom.yml 187 config/foobar_schema.yml | foobar_schema.custom.yml 188 plugins/myPlugin/config/schema.yml | myPlugin_schema.custom.yml 189 plugins/myPlugin/config/foo_schema.yml | myPlugin_foo_schema.custom.yml 190 191 Custom schemas will be looked for in the application's and plugins' `config/` directories, so a plugin can override another plugin's schema. 192 193 Custom schemas must respect the same alternative syntax. The plugin will simply merge the two schemas, as follows: 269 == Mixed schemas == 270 271 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. 272 273 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. 194 274 195 275 {{{ … … 279 359 }}} 280 360 361 For clarity, it is recommended to use the alternative schema syntax as much as possible. 362 281 363 == Checking that the plugin is installed == 282 364 … … 286 368 287 369 {{{ 288 <?php if(!is_callable(array('sfPropelDatabaseSchema', 'loadNewYaml'))) throw new Exception('You must install the sfPropelAlternativeSchemaPlugin to use this schema') ?> 289 }}} 290 370 <?php if(!is_callable(array('sfPropelDatabaseSchema', 'convertOldToNewYaml'))) throw new Exception('You must install the sfPropelAlternativeSchemaPlugin to use this schema') ?> 371 }}} 372 373 == Todo == 374 375 * Refactor `sfPropelDatabaseSchema` to use the new schema syntax internally 376 291 377 == Changelog == 292 378 293 379 === Trunk === 294 380 381 * 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. 295 382 * francois: Added a fix for the too late initialization of behaviors in symfony when adding hooks to custom class 296 383 * francois: Added a way to define behaviors from the schema plugins/sfPropelAlternativeSchemaPlugin/lib/sfPropelDatabaseSchema.class.php
r5561 r5564 29 29 public function loadArray($schema) 30 30 { 31 $ this->database = array();32 $ this->connection_name = '';31 $database = array(); 32 $connection_name = ''; 33 33 34 34 if(isset($schema['classes'])) 35 35 { 36 36 // New schema syntax 37 $this->loadNewYAML($schema); 38 } 39 else 40 { 41 if (count($schema) > 1) 42 { 43 throw new sfException('A schema.yml must only contain 1 database entry.'); 44 } 45 46 $tmp = array_keys($schema); 47 $this->connection_name = array_shift($tmp); 48 49 if ($this->connection_name) 50 { 51 $this->database = $schema[$this->connection_name]; 52 } 53 } 54 37 $schema = $this->convertNewToOldYaml($schema); 38 } 39 40 if (count($schema) > 1) 41 { 42 throw new sfException('A schema.yml must only contain 1 database entry.'); 43 } 44 45 $tmp = array_keys($schema); 46 $connection_name = array_shift($tmp); 47 48 if ($connection_name) 49 { 50 $database = $schema[$connection_name]; 51 } 52 53 $this->connection_name = $connection_name; 54 $this->database = $database; 55 55 56 $this->fixYAMLDatabase(); 56 57 $this->fixYAMLI18n(); … … 61 62 { 62 63 $schema_array = sfYaml::load($file); 63 64 65 if(!isset($schema_array['classes'])) 66 { 67 // Old schema syntax: we keep a copy 68 $old_schema_array = $schema_array; 69 // And we convert it 70 $schema_array = $this->convertOldToNewYaml($schema_array); 71 } 72 64 73 // the following should be in the pake task, but unfortunately it can't be overridden... 65 74 // so we do it only if in the pake context 66 if( isset($schema_array['classes']) &&class_exists('pakeFinder'))75 if(class_exists('pakeFinder')) 67 76 { 68 77 // New schema syntax, we should look for custom schemas … … 82 91 } 83 92 $custom_schema_array = sfYaml::load($custom_schema); 93 if(!isset($custom_schema_array['classes'])) 94 { 95 // Old schema syntax: we convert it 96 $custom_schema_array = $this->convertOldToNewYaml($custom_schema_array); 97 } 84 98 $schema_array = sfToolkit::arrayDeepMerge($schema_array, $custom_schema_array); 85 99 } 86 100 } 87 101 88 $this->loadArray( $schema_array);102 $this->loadArray(isset($old_schema_array) ? $old_schema_array : $schema_array); 89 103 } 90 104 91 public function loadNewYaml($schema) 105 public function convertOldToNewYaml($schema) 106 { 107 $new_schema = array(); 108 109 $tmp = array_keys($schema); 110 $connection_name = array_shift($tmp); 111 $new_schema['connection'] = $connection_name; 112 113 $classes = array(); 114 foreach($schema[$connection_name] as $table => $table_params) 115 { 116 if($table == '_attributes') 117 { 118 // Database attributes 119 $new_schema = array_merge($new_schema, $table_params); 120 } 121 else 122 { 123 // Table 124 $phpName = sfInflector::camelize($table); 125 if(isset($table_params['_attributes'])) 126 { 127 $table_attributes = $table_params['_attributes']; 128 unset($table_params['_attributes']); 129 if(isset($table_attributes['phpName'])) 130 { 131 $phpName = $table_attributes['phpName']; 132 unset($table_attributes['phpName']); 133 } 134 } 135 else 136 { 137 $table_attributes = array(); 138 } 139 $classes[$phpName] = $table_attributes; 140 $classes[$phpName]['columns'] = array(); 141 foreach($table_params as $column => $column_params) 142 { 143 switch($column) 144 { 145 case '_foreignKeys': 146 $classes[$phpName]['foreignKeys'] = $column_params; 147 break; 148 case '_indexes': 149 $classes[$phpName]['indexes'] = $column_params; 150 break; 151 case '_uniques': 152 $classes[$phpName]['uniques'] = $column_params; 153 break; 154 default: 155 $classes[$phpName]['columns'][$column] = $column_params; 156 } 157 } 158 } 159 } 160 161 $new_schema['classes'] = $classes; 162 163 return $new_schema; 164 } 165 166 public function convertNewToOldYaml($schema) 92 167 { 93 168 94 169 if(isset($schema['connection'])) 95 170 { 96 $ this->connection_name = $schema['connection'];171 $connection_name = $schema['connection']; 97 172 unset($schema['connection']); 98 173 } 99 174 else 100 175 { 101 $ this->connection_name = 'propel';176 $connection_name = 'propel'; 102 177 } 103 178 … … 178 253 } 179 254 180 $this->database = $database;255 return array($connection_name => $database); 181 256 } 182 257 plugins/sfPropelAlternativeSchemaPlugin/test/unit/fixtures/new_schema.yml
r5519 r5564 33 33 user_id: 34 34 my_group: { type: integer, foreignTable: ab_group, foreignReference: id, onDelete: setnull } 35 my_other_group: { type: integer, foreign Class: Group, foreignReference: id, onDelete: setnull }35 my_other_group: { type: integer, foreignTable: ab_group, foreignReference: id, onDelete: setnull } 36 36 created_at: timestamp 37 37 updated_at: plugins/sfPropelAlternativeSchemaPlugin/test/unit/sfPropelDatabaseSchemaTest.php
r5548 r5564 49 49 } 50 50 51 $t = new my_lime_test( 153, new lime_output_color());51 $t = new my_lime_test(251, new lime_output_color()); 52 52 53 53 require_once(dirname(__FILE__).'/../../lib/sfPropelDatabaseSchema.class.php'); … … 68 68 $t->is_line_by_line($p->asXML(), $target); 69 69 70 $t->diag('New YAML to Old YAML conversion'); 71 $old_yml_target = sfYaml::load(dirname(__FILE__).'/fixtures/schema.yml'); 72 $p = new sfPropelDatabaseSchema(); 73 $new_yml_transformed = $p->convertNewToOldYaml(sfYaml::load(dirname(__FILE__).'/fixtures/new_schema.yml')); 74 $t->is_array_explicit($new_yml_transformed, $old_yml_target); 75 76 $t->diag('Old YAML to New YAML conversion'); 77 $new_yml_target = sfYaml::load(dirname(__FILE__).'/fixtures/new_schema.yml'); 78 $p = new sfPropelDatabaseSchema(); 79 $old_yml_transformed = $p->convertOldToNewYaml(sfYaml::load(dirname(__FILE__).'/fixtures/schema.yml')); 80 $t->is_array_explicit($old_yml_transformed, $new_yml_target); 81 70 82 71 83 $t->todo('XML and classical YAML internal representation');