Development

Changeset 5564

You must first sign up to be able to contribute.

Changeset 5564

Show
Ignore:
Timestamp:
10/17/07 16:21:41 (1 year ago)
Author:
francois
Message:

sfPropelAlternativeSchemaPlugin Added new/old YAML syntax conversion. It is now possible to customize an old schema even if it doesn t use the alternative syntax

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • plugins/sfPropelAlternativeSchemaPlugin/README

    r5561 r5564  
    33== Overview == 
    44 
    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. 
     5This 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 
     7It 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. 
    68 
    79== Installation == 
     
    1921}}} 
    2022 
    21 That's it, you are ready to write schemas with the new syntax and override existing schemas. 
     23That's it, you are ready to override existing schemas and write schemas with the new syntax. 
     24 
     25== Customizing an existing schema == 
     26 
     27Once 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 
     29When 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 
     38Custom 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 
     40The plugin will merge the two schemas in a smart way, as follows: 
     41 
     42{{{ 
     43# Original schema 
     44################# 
     45propel: 
     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############### 
     67myConnection: 
     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################## 
     82myConnection: 
     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 
     110When 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. 
    22111 
    23112== New schema syntax == 
     
    74163}}} 
    75164 
    76 With the new syntax, you can write it as follows: 
     165With the alternative syntax, you can write it as follows: 
    77166 
    78167{{{ 
     
    138227The 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()`. 
    139228 
    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. 
     229This 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. 
    141230 
    142231The `connection` parameter is optional. If it is not set, it will take `propel` as a default value. 
     
    152241}}} 
    153242 
     243The plugin will recognize the alternative syntax automatically. Note that you can have, in a project, schemas with mixed current and alternative syntax. 
     244 
    154245== Behaviors == 
    155246 
    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`: 
     247The 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`: 
    157248 
    158249{{{ 
     
    176267Note: Incidentally, behaviors entered this way are registered both in the model and peer classes, which seems to solve some problems with behaviors (like #1229). 
    177268 
    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 
     271The 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 
     273Note 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. 
    194274 
    195275{{{ 
     
    279359}}} 
    280360 
     361For clarity, it is recommended to use the alternative schema syntax as much as possible. 
     362 
    281363== Checking that the plugin is installed == 
    282364 
     
    286368 
    287369{{{ 
    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  
    291377== Changelog == 
    292378 
    293379=== Trunk === 
    294380 
     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. 
    295382 * francois: Added a fix for the too late initialization of behaviors in symfony when adding hooks to custom class 
    296383 * francois: Added a way to define behaviors from the schema 
  • plugins/sfPropelAlternativeSchemaPlugin/lib/sfPropelDatabaseSchema.class.php

    r5561 r5564  
    2929  public function loadArray($schema) 
    3030  { 
    31     $this->database = array(); 
    32     $this->connection_name = ''; 
     31    $database = array(); 
     32    $connection_name = ''; 
    3333     
    3434    if(isset($schema['classes'])) 
    3535    { 
    3636      // 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     
    5556    $this->fixYAMLDatabase(); 
    5657    $this->fixYAMLI18n(); 
     
    6162  { 
    6263    $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     
    6473    // the following should be in the pake task, but unfortunately it can't be overridden... 
    6574    // so we do it only if in the pake context 
    66     if(isset($schema_array['classes']) && class_exists('pakeFinder')) 
     75    if(class_exists('pakeFinder')) 
    6776    { 
    6877      // New schema syntax, we should look for custom schemas 
     
    8291        } 
    8392        $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        } 
    8498        $schema_array = sfToolkit::arrayDeepMerge($schema_array, $custom_schema_array); 
    8599      } 
    86100    } 
    87101     
    88     $this->loadArray($schema_array); 
     102    $this->loadArray(isset($old_schema_array) ? $old_schema_array : $schema_array); 
    89103  } 
    90104   
    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) 
    92167  { 
    93168     
    94169    if(isset($schema['connection'])) 
    95170    { 
    96       $this->connection_name = $schema['connection']; 
     171      $connection_name = $schema['connection']; 
    97172      unset($schema['connection']); 
    98173    } 
    99174    else 
    100175    { 
    101       $this->connection_name = 'propel'; 
     176      $connection_name = 'propel'; 
    102177    } 
    103178     
     
    178253    } 
    179254     
    180     $this->database = $database
     255    return array($connection_name => $database)
    181256  } 
    182257 
  • plugins/sfPropelAlternativeSchemaPlugin/test/unit/fixtures/new_schema.yml

    r5519 r5564  
    3333      user_id: 
    3434      my_group:       { type: integer, foreignTable: ab_group, foreignReference: id, onDelete: setnull } 
    35       my_other_group: { type: integer, foreignClass: Group, foreignReference: id, onDelete: setnull } 
     35      my_other_group: { type: integer, foreignTable: ab_group, foreignReference: id, onDelete: setnull } 
    3636      created_at:     timestamp 
    3737      updated_at: 
  • plugins/sfPropelAlternativeSchemaPlugin/test/unit/sfPropelDatabaseSchemaTest.php

    r5548 r5564  
    4949} 
    5050 
    51 $t = new my_lime_test(153, new lime_output_color()); 
     51$t = new my_lime_test(251, new lime_output_color()); 
    5252 
    5353require_once(dirname(__FILE__).'/../../lib/sfPropelDatabaseSchema.class.php'); 
     
    6868$t->is_line_by_line($p->asXML(), $target); 
    6969 
     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 
    7082 
    7183$t->todo('XML and classical YAML internal representation');