diff --git a/classes/generate.php b/classes/generate.php index fcc50f5d..0c15cc26 100644 --- a/classes/generate.php +++ b/classes/generate.php @@ -29,12 +29,31 @@ class Generate public static $scaffolding = false; - private static $_default_constraints = array( - 'varchar' => 255, - 'char' => 255, - 'int' => 11, + protected static $_field_defaults = array( + '_default_' => array( + 'null' => false, + 'key' => NULL, + ), + 'varchar' => array( + 'constraint' => '255', + ), + 'char' => array( + 'constraint' => '255', + ), + 'int' => array( + 'constraint' => '11', + ), + 'decimal' => array( + 'constraint' => '10,2', + ), + 'float' => array( + 'constraint' => '10,2', + ), ); + /** + * + */ public static function config($args) { $file = strtolower(array_shift($args)); @@ -132,6 +151,9 @@ public static function config($args) } } + /** + * + */ public static function controller($args, $build = true) { if ( ! ($name = \Str::lower(array_shift($args)))) @@ -245,10 +267,15 @@ public function view() $build and static::build(); } + /** + * + */ public static function model($args, $build = true) { $singular = \Inflector::singularize(\Str::lower(array_shift($args))); + $args = static::normalize_args($args); + if (empty($singular) or strpos($singular, ':')) { throw new Exception("Command is invalid.".PHP_EOL."\tphp oil g model [: |: |..]"); @@ -279,43 +306,36 @@ public static function model($args, $build = true) // Uppercase each part of the class name and remove hyphens $class_name = \Inflector::classify(str_replace(array('\\', '/'), '_', $singular), false); - // Turn foo:string into "id", "foo", - $properties = implode(",\n\t\t", array_map(function($field) { - - // Only take valid fields - if (($field = strstr($field, ':', true))) - { - return "'".$field."'"; - } - - }, $args)); - - // Add a comma to the end of the list - $properties = "\n\t\t" . $properties . ","; - - $contents = ''; - // Generate with test? - $with_test = \Cli::option('with-test'); - if ($with_test) { + if ($with_test = \Cli::option('with-test')) + { static::_create_test('Model', $class_name, $base_path); } + // storage for the generated contents + $contents = ''; + + // deal with Model_Crud models first if (\Cli::option('crud')) { - // Make sure an id is present - strpos($properties, "'id'") === false and $properties = "'id',".$properties; - + // model properties if ( ! \Cli::option('no-properties')) { $contents = << array("; + $contents .= PHP_EOL."\t\t\t\"label\" => \"".\Inflector::humanize($arg['name'])."\","; + $contents .= PHP_EOL."\t\t\t\"data_type\" => \"".$arg['data_type']."\","; + if (isset($arg['default'])) + { + $contents .= PHP_EOL."\t\t\t\"default\" => \"".$arg['default']."\","; + } + if ($arg['data_type'] == 'enum' and isset($arg['options'])) + { + $contents .= PHP_EOL."\t\t\t\"options\" => array(".$arg['constraint']."),"; + } - $args = array_merge(array($tree_id.':int:unsigned'), $args); + $contents .= PHP_EOL."\t\t),"; } + $contents .= << '$created_at', + protected static \$_observers = array( CONTENTS; - } - else - { - $created_at = ''; - } - - if(($updated_at = \Cli::option('updated-at')) and is_string($updated_at)) - { - $updated_at = << '$updated_at', -CONTENTS; - } - else - { - $updated_at = ''; - } + $updated_at = \Cli::option('updated-at', 'updated_at'); + is_string($updated_at) or $updated_at = 'updated_at'; $contents .= << array( 'events' => array('before_insert'), - 'mysql_timestamp' => $mysql_timestamp,$created_at + 'property' => '$created_at', + 'mysql_timestamp' => $mysql_timestamp, ), 'Orm\Observer_UpdatedAt' => array( 'events' => array('before_update'), - 'mysql_timestamp' => $mysql_timestamp,$updated_at + 'property' => '$updated_at', + 'mysql_timestamp' => $mysql_timestamp, ), - ); CONTENTS; - } - if (\Cli::option('soft-delete')) - { - if($deleted_at !== 'deleted_at') - { - $deleted_at = << '{$deleted_at}', CONTENTS; - } - else - { - $deleted_at = ''; - } - $contents .= << $mysql_timestamp,$deleted_at + 'mysql_timestamp' => $mysql_timestamp, + 'deleted_field' => '{$deleted_at}', ); + CONTENTS; } + + // add fields required for temporal models elseif (\Cli::option('temporal')) { - if($temporal_start !== 'temporal_start') - { - $start_column = << '{$temporal_start}', -CONTENTS; - } - else - { - $start_column = ''; - } - - if($temporal_end !== 'temporal_end') - { - $end_column = << '{$temporal_end}', -CONTENTS; - } - else - { - $end_column = ''; - } + $temporal_end = \Cli::option('temporal-end', 'temporal_end'); + is_string($temporal_end) or $temporal_end = 'temporal_end'; $contents .= << $mysql_timestamp,$start_column$end_column - ); + 'mysql_timestamp' => $mysql_timestamp, + 'start_column' => '{$temporal_start}', + 'end_column' => '{$temporal_end}', - protected static \$_primary_key = array('id', '{$temporal_start}', '{$temporal_end}'); + ); CONTENTS; } + + // add fields required for nestedset models elseif (\Cli::option('nestedset')) { - if($left_id !== 'left_id') - { - $left_field = << '{$left_id}', + $contents .= << '{$right_id}', + is_string($title) or $title = 'title'; + $contents .= << '{$title}', + CONTENTS; } - else - { - $right_field = ''; - } - if($tree_id) + if ($tree_id = \Cli::option('tree-id', false)) { - $tree_field = << '{$tree_id}', + CONTENTS; } - else - { - $tree_field = ''; - } - if($title) - { - $title_field = << '{$title}', + $left_id = \Cli::option('left-id', 'left_id'); + is_string($left_id) or $left_id = 'left_id'; + $contents .= << '{$left_id}', + +CONTENTS; + + + $right_id = \Cli::option('right-id', 'right_id'); + is_string($right_id) or $right_id = 'right_id'; + $contents .= << '{$right_id}', + CONTENTS; - } - else - { - $title_field = ''; - } if($read_only = \Cli::option('read-only') and is_string($read_only)) { @@ -624,37 +558,63 @@ class Model_{$class_name} extends \Model_Crud $read_only = "'" . implode("', '", $read_only) . "'"; $read_only = << array($read_only), + CONTENTS; } - else - { - $read_only = ''; - } - if (! empty($left_field) or ! empty($right_field) or ! empty($tree_field) or ! empty($title_field)) - { - $fields = array($left_field, $right_field, $tree_field, $title_field, $read_only); - $fields = array_filter($fields); - $fields = implode("\n", $fields); + $contents .= <<'>\n"; + $subnav .= "\t
  • ".PHP_EOL; } foreach ($args as $action) @@ -863,11 +829,29 @@ public static function views($args, $subfolder, $build = true) $build and static::build(); } + /** + * + */ public static function migration($args, $build = true) { // Get the migration name $migration_name = \Str::lower(str_replace(array('-', '/'), '_', array_shift($args))); + // what type of migration do we have? + $type = explode('_', $migration_name); + + // tell the generator not to standardize the fieldlist for these types + if (in_array($type[0], array('add', 'delete', 'rename', 'drop'))) + { + \Cli::set_option('no-standardisation', true); + } + + // normalize the arguments if needed + if ( ! empty($args)) + { + $args = static::normalize_args($args); + } + if (empty($migration_name) or strpos($migration_name, ':')) { throw new Exception("Command is invalid.".PHP_EOL."\tphp oil g migration [: |: |..]"); @@ -911,6 +895,10 @@ public static function migration($args, $build = true) throw new Exception("Unable to read existing migrations. Path does not exist, or you may have an 'open_basedir' defined"); } + // save the migration name, it's also used as table name + $table_name = $migration_name; + + // deal with duplicates to make sure the migration name is unique if (count($duplicates) > 0) { // Don't override a file @@ -946,7 +934,7 @@ public static function migration($args, $build = true) foreach ($methods as $method_name) { // If the miration name starts with the name of the action method - if (substr($migration_name, 0, strlen($method_name)) === $method_name) + if (substr($table_name, 0, strlen($method_name)) === $method_name) { /** * Create an array of the subject the migration is about @@ -961,7 +949,7 @@ public static function migration($args, $build = true) * */ $subjects = array(false, false); - $matches = explode('_', str_replace($method_name . '_', '', $migration_name)); + $matches = explode('_', str_replace($method_name . '_', '', $table_name)); // create_{table} if (count($matches) == 1) @@ -985,9 +973,9 @@ public static function migration($args, $build = true) elseif (count($matches) >= 5 && in_array('to', $matches) && in_array('in', $matches)) { $subjects = array( - implode('_', array_slice($matches, array_search('in', $matches)+1)), implode('_', array_slice($matches, 0, array_search('to', $matches))), implode('_', array_slice($matches, array_search('to', $matches)+1, array_search('in', $matches)-array_search('to', $matches)-1)), + implode('_', array_slice($matches, array_search('in', $matches)+1)), ); } @@ -1003,7 +991,7 @@ public static function migration($args, $build = true) // create_{table} or drop_{table} (with underscores in table name) elseif (count($matches) !== 0) { - $name = str_replace(array('create_', 'add_', 'drop_', '_to_'), array('create-', 'add-', 'drop-', '-to-'), $migration_name); + $name = str_replace(array('create_', 'add_', 'drop_', '_to_'), array('create-', 'add-', 'drop-', '-to-'), $table_name); if (preg_match('/^(create|drop|add)\-([a-z0-9\_]*)(\-to\-)?([a-z0-9\_]*)?$/i', $name, $deep_matches)) { @@ -1027,124 +1015,13 @@ public static function migration($args, $build = true) break; } - // We always pass in fields to a migration, so lets sort them out here. - $fields = array(); - foreach ($args as $field) - { - $field_array = array(); - - // Each paramater for a field is seperated by the : character - $parts = explode(":", $field); - - // We must have the 'name:type' if nothing else! - if (count($parts) >= 2) - { - $field_array['name'] = array_shift($parts); - foreach ($parts as $part_i => $part) - { - preg_match('/([a-z0-9_-]+)(?:\[([0-9a-z_\-\,\s]+)\])?/i', $part, $part_matches); - array_shift($part_matches); - - if (count($part_matches) < 1) - { - // Move onto the next part, something is wrong here... - continue; - } - - $option_name = ''; // This is the name of the option to be passed to the action in a field - $option = $part_matches; - - // The first option always has to be the field type - if ($part_i == 0) - { - $option_name = 'type'; - $type = $option[0]; - if ($type === 'string') - { - $type = 'varchar'; - } - elseif ($type === 'integer') - { - $type = 'int'; - } - - if ( ! in_array($type, array('text', 'blob', 'datetime', 'date', 'timestamp', 'time'))) - { - if ( ! isset($option[1]) || $option[1] == NULL) - { - if (isset(self::$_default_constraints[$type])) - { - $field_array['constraint'] = self::$_default_constraints[$type]; - } - } - else - { - // should support field_name:enum[value1,value2] - if ($type === 'enum') - { - $values = explode(',', $option[1]); - $option[1] = '"'.implode('","', $values).'"'; - - $field_array['constraint'] = $option[1]; - } - // should support field_name:decimal[10,2] - elseif (in_array($type, array('decimal', 'float'))) - { - $field_array['constraint'] = $option[1]; - } - else - { - $field_array['constraint'] = (int) $option[1]; - } - - } - } - $option = $type; - } - else - { - // This allows you to put any number of :option or :option[val] into your field and these will... - // ... always be passed through to the action making it really easy to add extra options for a field - $option_name = array_shift($option); - if (count($option) > 0) - { - $option = $option[0]; - } - else - { - $option = true; - } - } - - // deal with some special cases - switch ($option_name) - { - case 'auto_increment': - case 'null': - case 'unsigned': - $option = (bool) $option; - break; - } - - $field_array[$option_name] = $option; - - } - $fields[] = $field_array; - } - else - { - // Invalid field passed in - continue; - } - } - // Call the magic action which returns an array($up, $down) for the migration - $migration = call_user_func(__NAMESPACE__ . "\Generate_Migration_Actions::{$method_name}", $subjects, $fields); + $migration = call_user_func(__NAMESPACE__ . "\Generate_Migration_Actions::{$method_name}", $subjects, $args); } } // Build the migration - list($up, $down)=$migration; + list($up, $down) = $migration; // If we don't have any, bail out if (empty($up) and empty($down)) @@ -1182,6 +1059,9 @@ public function down() $build and static::build(); } + /** + * + */ public static function task($args, $build = true) { @@ -1295,6 +1175,9 @@ class {$class_name} $build and static::build(); } + /** + * + */ public static function help() { $output = << __DIR__ . '/classes/{$name}/driver.php',"; if (is_array($drivers)) { foreach ($drivers as $driver) @@ -1564,7 +1450,7 @@ class {$class_name}_{$driver_name} extends {$class_name}_Driver } CLASS; - $bootstrap .= "\n\t'{$class_name}\\\\{$class_name}_{$driver_name}' => __DIR__ . '/classes/{$name}/{$driver}.php',"; + $bootstrap .= PHP_EOL."\t'{$class_name}\\\\{$class_name}_{$driver_name}' => __DIR__ . '/classes/{$name}/{$driver}.php',"; static::create($path . 'classes' . DS . $name . DS . $driver . '.php', $output); } } @@ -1687,6 +1573,9 @@ public function set_config(\$key, \$value) $build and static::build(); } + /** + * + */ public static function create($filepath, $contents, $type = 'file') { $directory = dirname($filepath); @@ -1717,6 +1606,9 @@ public static function create($filepath, $contents, $type = 'file') ); } + /** + * + */ public static function build() { foreach (static::$create_folders as $folder) @@ -1751,13 +1643,348 @@ public static function build() return $result; } + /** + * + */ public static function class_name($name) { return str_replace(array(' ', '-'), '_', ucwords(str_replace('_', ' ', $name))); } + /** + * + */ + public static function normalize_args(array $args) + { + // normalized result + $normalized = array('id' => null); + + // loop over the field names passed + foreach ($args as $field) + { + // check what we got + if (is_array($field)) + { + // make sure we have the correct format + if (isset($field['name'])) + { + // deal with some generics + if ($field['data_type'] === 'string') + { + $field['data_type'] = 'varchar'; + } + elseif ($field['data_type'] === 'integer') + { + $field['data_type'] = 'int'; + } + elseif (strpos($field['data_type'], ' unsigned') !== false) + { + $field['data_type'] = explode(' ', $field['data_type']); + $field['data_type'] = $field['data_type'][0]; + $field['unsigned'] = true; + } + + // deal with some constraint quirks + if (empty($field['constraint'])) + { + if (isset($field['display'])) + { + $field['constraint'] = $field['display']; + } + elseif (isset($field['numeric_precision'])) + { + $field['constraint'] = $field['numeric_precision'].','.$field['numeric_scale']; + } + } + + // deal with the different constraint types + if ($field['data_type'] === 'enum' or $field['data_type'] === 'set' ) + { + // avoid double quoting + if (strpos($field['constraint'], '"') !== 0) + { + $values = explode(',', $field['constraint']); + $field['constraint'] = '"'.implode('","', $values).'"'; + } + } + + // should support field_name:decimal[10,2] + elseif (in_array($field['data_type'], array('decimal', 'float', 'double'))) + { + // leave as-is + } + + // should support any other constraint + elseif (isset($field['constraint'])) + { + $field['constraint'] = (int) $field['constraint']; + } + + // output from list_columns, we're done here! + $normalized[$field['name']] = $field; + } + } + else + { + // we need to split a field string into components + $field_array = array(); + + // Each paramater for a field is seperated by the : character + $parts = explode(":", $field); + + // We must have the 'name:type' if nothing else! + if (count($parts) >= 2) + { + // make sure we have default values + $field_array = static::$_field_defaults['_default_']; + + // extract the field name + $field_array['name'] = array_shift($parts); + + // process the remaining parts + foreach ($parts as $part_i => $part) + { + // split the part + preg_match('/([a-z0-9_-]+)(?:\[([0-9a-z_\-\,\s]+)\])?/i', $part, $part_matches); + array_shift($part_matches); + + if ( ! count($part_matches)) + { + // Move onto the next part, something is wrong here... + continue; + } + + // The first option always has to be the field type + if (empty($field_array['data_type'])) + { + // determine the field datatype + $type = $part_matches[0]; + // deal with some generics + if ($type === 'string') + { + $type = 'varchar'; + } + elseif ($type === 'integer') + { + $type = 'int'; + } + + // add the defaults for this datatype + if (isset(static::$_field_defaults[$type])) + { + $field_array = array_merge(static::$_field_defaults[$type], $field_array); + } + + // deal with any field constraints + if (isset($part_matches[1]) and $part_matches[1]) + { + // should support field_name:enum[value1,value2] and field_name:set[value1,value2] + if ($type === 'enum' or $type === 'set') + { + $values = explode(',', $part_matches[1]); + $part_matches[1] = '"'.implode('","', $values).'"'; + + $field_array['constraint'] = $part_matches[1]; + } + + // should support field_name:decimal[10,2] + elseif (in_array($type, array('decimal', 'float'))) + { + $field_array['constraint'] = $part_matches[1]; + } + + // should support any other constraint + else + { + $field_array['constraint'] = (int) $part_matches[1]; + } + } + + // so we can add this next + $option = 'data_type'; + $part_matches = $type; + } + else + { + // This allows you to put any number of :option or :option[val] into your field and these will... + // ... always be passed through to the action making it really easy to add extra options for a field + $option = array_shift($part_matches); + if (count($part_matches) > 0) + { + $option = $part_matches[0]; + } + else + { + $part_matches = true; + } + } + + // deal with some special cases + switch ($option) + { + case 'auto_increment': + case 'null': + case 'unsigned': + $part_matches = (bool) $part_matches; + break; + } + + $field_array[$option] = $part_matches; + } + + $normalized[$field_array['name']] = $field_array; + } + else + { + // Invalid field passed in + continue; + } + } + } + + // Check if we have a primary key + $pk = false; + foreach ($args as $arg) + { + if (isset($arg['key']) and $arg['key'] == 'PRI') + { + $pk = true; + } + } + + // add a PK if none are present + if ( ! $pk and ! \Cli::option('no-standardisation')) + { + $normalized['id'] = array('name' => 'id', 'data_type' => 'int', 'unsigned' => true, 'null' => false, 'auto_increment' => true, 'key' => 'PRI', 'constraint' => '11');; + } + elseif ($normalized['id'] === null) + { + // remove the dummy + unset($normalized['id']); + } + + // some other optional columns in case of ORM + if ( ! \Cli::option('crud')) + { + $time_type = (\Cli::option('mysql-timestamp')) ? 'timestamp' : 'int'; + $no_timestamp_default = false; + + // closure used to add a new field + $add_field = function($args, $name, $type, $options = array()) { + + // create the field + $field = static::$_field_defaults['_default_']; + + // add the defaults for this datatype + if (isset(static::$_field_defaults[$type])) + { + $field = array_merge(static::$_field_defaults[$type], $field); + } + + // add the data + $field['name'] = $name; + $field['type'] = $type; + $field['data_type'] = $type; + + // return the result + return array_merge($args, array($name => array_merge($field, $options))); + }; + + + // additional column for soft-delete models + if ( \Cli::option('soft-delete')) + { + $deleted_at = \Cli::option('deleted-at', 'deleted_at'); + is_string($deleted_at) or $deleted_at = 'deleted_at'; + if ( ! isset($normalized[$deleted_at])) + { + $normalized = $add_field($normalized, $deleted_at, $time_type, array('null' => true, 'unsigned' => true)); + } + } + + // additional column for temporal models + elseif (\Cli::option('temporal')) + { + $temporal_start = \Cli::option('temporal-start', 'temporal_start'); + is_string($temporal_start) or $temporal_start = 'temporal_start'; + if ( ! isset($normalized[$temporal_start])) + { + $normalized = $add_field($normalized, $temporal_start, $time_type, array('key' => 'PRI', 'null' => true, 'unsigned' => true)); + } + + $temporal_end = \Cli::option('temporal-end', 'temporal_end'); + is_string($temporal_end) or $temporal_end = 'temporal_end'; + if ( ! isset($normalized[$temporal_end])) + { + $normalized = $add_field($normalized, $temporal_end, $time_type, array('key' => 'PRI', 'null' => true, 'unsigned' => true)); + } + + \Cli::set_option('no-timestamp', true); + } + + // additional columns for nestedset models + elseif (\Cli::option('nestedset')) + { + if ($title = \Cli::option('title', false)) + { + is_string($title) or $title = 'title'; + if ( ! isset($normalized[$title])) + { + $normalized = $add_field($normalized, $title, 'varchar', array('null' => true, 'constraint' => '50')); + } + } + + if ($tree_id = \Cli::option('tree-id', false)) + { + is_string($tree_id) or $tree_id = 'tree_id'; + if ( ! isset($normalized[$tree_id])) + { + $normalized = $add_field($normalized, $tree_id, 'int', array('constraint' => '11', 'unsigned' => true)); + } + } + + $left_id = \Cli::option('left-id', 'left_id'); + is_string($left_id) or $left_id = 'left_id'; + if ( ! isset($normalized[$left_id])) + { + $normalized = $add_field($normalized, $left_id, 'int', array('constraint' => '11', 'unsigned' => true)); + } + + $right_id = \Cli::option('right-id', 'right_id'); + is_string($right_id) or $right_id = 'right_id'; + if ( ! isset($normalized[$right_id])) + { + $normalized = $add_field($normalized, $right_id, 'int', array('constraint' => '11', 'unsigned' => true)); + } + } + + if ( ! \Cli::option('no-timestamp') and ! \Cli::option('no-standardisation')) + { + $created_at = \Cli::option('created-at', 'created_at'); + is_string($created_at) or $created_at = 'created_at'; + if ( ! isset($normalized[$created_at])) + { + $normalized = $add_field($normalized, $created_at, $time_type, array('null' => true, 'unsigned' => true)); + } + + $updated_at = \Cli::option('updated-at', 'updated_at'); + is_string($updated_at) or $updated_at = 'updated_at'; + if ( ! isset($normalized[$updated_at])) + { + $normalized = $add_field($normalized, $updated_at, $time_type, array('null' => true, 'unsigned' => true)); + } + } + } + + // return the normalized result + return $normalized; + } + // Helper methods + /** + * + */ private static function _find_migration_number() { $base_path = APPPATH; @@ -1796,6 +2023,9 @@ private static function _find_migration_number() return str_pad($last + 1, 3, '0', STR_PAD_LEFT); } + /** + * + */ private static function _update_current_version($version) { if (is_file($app_path = APPPATH.'config'.DS.'migrations.php')) @@ -1817,6 +2047,9 @@ private static function _update_current_version($version) static::create($app_path, $contents, 'config'); } + /** + * + */ private static function _create_test($type, $class_name, $base_path, $nav_item = '') { $filepath = $base_path.strtolower('tests'.DS.$type.DS.ucwords($class_name)); diff --git a/classes/generate/migration/actions.php b/classes/generate/migration/actions.php index 6350d51c..333ffa5e 100644 --- a/classes/generate/migration/actions.php +++ b/classes/generate/migration/actions.php @@ -21,105 +21,147 @@ * @package Fuel * @subpackage Oil * @category Core - * @author Tom Arnfeld + * @author Tom Arnfeld, Harro Verton */ class Generate_Migration_Actions { + /***************************************************************************************************** + * Each migration action should return an array with two items, 0 being the up and 1 the being down. * + *****************************************************************************************************/ + /** - * Each migration action should return an array with two items, 0 being the up and 1 the being down. + * In the methods below, the subjects array contains two elements: + * + * - In a migration named 'create_users' the subject is 'users' since thats what we want to create + * So it would be the second object in the array + * array(false, 'users') + * + * - In a migration named 'add_name_to_users' the object is 'name' and the subject is 'users'. + * So again 'users' would be the second object, but 'name' would be the first + * array('name', 'users') + * + * - In case there are multiple objects, the array can have more objects. The subject will always be + * last. So in a migration name 'rename_fullname_to_lastname_in_users, the array would contain + * array('fullname', 'lastname', 'users') */ - // create_{tablename} + /** + * Generate the up and down migration code for table creation + * + * oil command: create_{tablename} + * + * @param array + * @param array + * + * @return array(up, down) + */ public static function create($subjects, $fields) { - $field_str = ''; - $defined_columns = array(); - $have_id = false; - - foreach($fields as $field) + if (count($subjects) != 2) { - $name = array_shift($field); - - $name === 'id' and $have_id = true; + throw new \FuelException('Incorrect number of arguments for "create"'); + } - $field_opts = array(); - foreach($field as $option => $val) - { - if($val === true) - { - $field_opts[] = "'$option' => true"; - } - else - { - if(is_int($val)) - { - $field_opts[] = "'$option' => $val"; - } - else - { - $field_opts[] = "'$option' => '$val'"; - } - } - } - $field_opts = implode(', ', $field_opts); + // temp storage + $table_prefix = ''; - $field_str .= "\t\t\t'$name' => array({$field_opts}),".PHP_EOL; - $defined_columns[$name] = true; + // if we didn't get generated data, take the prefix into account + if ( ! \Cli::option('no-standardisation')) + { + $active_db = \Config::get('db.active'); + $table_prefix = \Config::get('db.'.$active_db.'.table_prefix'); } - // ID Field - $have_id or $field_str = "\t\t\t'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true, 'unsigned' => true),".PHP_EOL . $field_str; + // generate the code for the fields + list($field_up_str, $not_used, $pks, $idx) = static::_generate_field_string($fields); $up = <<execute();"; + $down .= PHP_EOL."\t\t\\DB::query('DROP INDEX {$field}_idx ON {$table_prefix}{$subjects[1]}')->execute();"; + } + $down = ltrim($down, PHP_EOL).PHP_EOL.PHP_EOL; + } + + $down .= << $val) - { - if($val === true) - { - $field_opts[] = "'$option' => true"; - } - else - { - if(is_int($val)) - { - $field_opts[] = "'$option' => $val"; - } - else - { - $field_opts[] = "'$option' => '$val'"; - } - } - } + // same commands as for create + $result = static::create($subjects, $fields); - $field_opts = implode(', ', $field_opts); + // but then in reverse order + return array($result[1], $result[0]); + } - $field_up_str .= "\t\t\t'$name' => array({$field_opts}),".PHP_EOL; - $field_down[] = "\t\t\t'$name'".PHP_EOL; + /** + * Generate the up and down migration code for adding a field to a table + * + * oil command: add_{thing}_to_{tablename} + * + * @param array + * @param array + * + * @return array(up, down) + */ + public static function add($subjects, $fields) + { + if (count($subjects) != 2) + { + throw new \FuelException('Incorrect number of arguments for "add"'); } - $field_down_str = implode(',', $field_down); + // temp storage + $table_prefix = ''; + + // if we didn't get generated data, take the prefix into account + if ( ! \Cli::option('no-standardisation')) + { + $active_db = \Config::get('db.active'); + $table_prefix = \Config::get('db.'.$active_db.'.table_prefix'); + } + + // generate the code for the fields + list($field_up_str, $field_down_str, $pks, $idx) = static::_generate_field_string($fields); $up = << $constraint" : ''; + // make sure the old field exists, and the new field doesn't + $column = \DB::list_columns($table, $subjects[1]); + if ( ! empty($column)) + { + throw new \FuelException('Can not generate the migration. The field "'.$subjects[1].'" already exists in "'.$table.'"'); + } + $column = \DB::list_columns($table, $subjects[0]); + if (empty($column)) + { + throw new \FuelException('Can not generate the migration. The field "'.$subjects[0].'" does not exist in "'.$table.'"'); + } + + // generate the code for the fields + list($field_up_str, $not_used, $not_used, $not_used) = static::_generate_field_string($column); + + // modify for different dbutil syntax + $field_down_str = str_replace('array(', 'array(\'name\' => \''.$subjects[0].'\', ', str_replace($subjects[0], $subjects[1], $field_up_str)); + $field_up_str = str_replace('array(', 'array(\'name\' => \''.$subjects[1].'\', ', $field_up_str); $up = << array('name' => '{$subjects[2]}', 'type' => '{$column['data_type']}'$constraint_str) + \DBUtil::modify_fields('{$table}', array( +$field_up_str )); UP; $down = << array('name' => '{$subjects[1]}', 'type' => '{$column['data_type']}'$constraint_str) + \DBUtil::modify_fields('{$table}', array( +$field_down_str )); DOWN; return array($up, $down); } - // rename_table_{tablename}_to_{newtablename} + /** + * Generate the up and down migration code for deleting a field from a table + * + * oil command: rename_table_{tablename}_to_{newtablename} + * + * @param array + * @param array + * + * @return array(up, down) + */ public static function rename_table($subjects, $fields) { + if (count($subjects) != 2) + { + throw new \FuelException('Incorrect number of arguments for "rename_table"'); + } $up = << $field) { - switch ($column['type']) + // storage for the translated field options + $field_opts = array(); + + // loop over the field options + foreach($field as $option => $val) { - case 'float': - $constraint = '\''.$column['numeric_precision'].', '.$column['numeric_scale'].'\''; - break; - case 'int': - $constraint = $column['display']; - break; - case 'string': - switch ($column['data_type']) + // deal with key data first + if ($option == 'key') + { + // primary key field + if ($val == 'PRI') { - case 'binary': - case 'varbinary': - case 'char': - case 'varchar': - $constraint = $column['character_maximum_length']; - break; - - case 'enum': - case 'set': - $constraint = '"\''.implode('\',\'', $column['options']).'\'"'; - break; + $pks[] = $name; } - break; - } - - $constraint_str = isset($constraint) ? ", 'constraint' => $constraint" : ''; - $auto_increment = $column['extra'] == 'auto_increment' ? ", 'auto_increment' => true" : ''; - $default_str = $column['default'] != null ? ", 'default' => '{$column['default']}'" : ", 'null' => true"; + if ($val == 'MUL') + { + $idx[] = $name; + } + continue; + } - if ($column['key'] == 'PRI') - { - $primary_keys[] = "'{$column['name']}'"; - } - elseif ($column['key'] == 'MUL') - { - $indexes[] = $column['name']; - } + // skip option data from describe not supported by DBUtil::create_table() + if (in_array($option, array('max', 'min', 'name', 'type', 'ordinal_position', 'display', 'comment', 'privileges', 'collation_name', 'options', 'character_maximum_length', 'numeric_precision', 'numeric_scale', 'exact'))) + { + continue; + } - $field_str .= "\t\t\t'{$column['name']}' => array('type' => '{$column['data_type']}'{$default_str}{$constraint_str}{$auto_increment}),".PHP_EOL; - unset($constraint); - } + // skip empty constraints + if ($option == 'constraint' and empty($val)) + { + continue; + } - $primary_keys = implode(',', $primary_keys); - $down = <<execute();".PHP_EOL; + // create the options based on the value type + if ($val === true) + { + $field_opts[] = "'$option' => true"; + } + elseif ($val === false) + { + $field_opts[] = "'$option' => false"; + } + elseif (is_null($val)) + { + // skip value + } + elseif (is_int($val)) + { + $field_opts[] = "'$option' => $val"; + } + elseif (is_array($val)) + { + // skip value + } + else + { + $field_opts[] = "'$option' => '$val'"; + } } + $field_opts = implode(', ', $field_opts); + + $field_up_str .= "\t\t\t'$name' => array({$field_opts}),".PHP_EOL; + $fields_down[] = "\t\t\t'$name'".PHP_EOL; } - return array($up, $down); - } + $pks = "'".implode("', '", $pks)."'"; + $field_up_str = rtrim($field_up_str, PHP_EOL); + $field_down_str = rtrim(implode(',', $fields_down), PHP_EOL); + return array($field_up_str, $field_down_str, $pks, $idx); + } } diff --git a/tasks/fromdb.php b/tasks/fromdb.php index 5a4b0e33..5d09b473 100644 --- a/tasks/fromdb.php +++ b/tasks/fromdb.php @@ -247,17 +247,6 @@ protected static function fetch_tables($type) */ protected static function arguments($table, $type = 'model') { - // get the list of columns from the table - try - { - $columns = \DB::list_columns(trim($table), null, \Cli::option('db', null)); - } - catch (\Exception $e) - { - \Cli::write($e->getMessage(), 'red'); - exit(); - } - // construct the arguments list, starting with the table name switch ($type) { @@ -274,6 +263,17 @@ protected static function arguments($table, $type = 'model') $include_timestamps = false; $timestamp_is_int = true; + // get the list of columns from the table + try + { + $columns = \DB::list_columns(trim($table), null, \Cli::option('db', null)); + } + catch (\Exception $e) + { + \Cli::write($e->getMessage(), 'red'); + exit(); + } + // process the columns found foreach ($columns as $column) { @@ -295,23 +295,25 @@ protected static function arguments($table, $type = 'model') // check if we have such a column, and filter out some default values if (isset($column[$idx]) and ! in_array($column[$idx], array('65535', '4294967295'))) { - $constraint = '['.$column[$idx].']'; + $constraint = $column[$idx]; break; } } // if it's an enum column, list the available options if (in_array($column['data_type'], array('set', 'enum'))) { - $constraint = '['.implode(',', $column['options']).']'; + $constraint = implode(',', $column['options']); } + // store the constraint + $column['constraint'] = $constraint; + // store the column in the argument list - $arguments[] = $column['name'].':'.$column['data_type'].$constraint; + $arguments[] = $column; } - // set the switches for the code generation - \Cli::set_option('no-timestamp', $include_timestamps === false); - \Cli::set_option('mysql-timestamp', $timestamp_is_int === false); + // tell oil not to fiddle with column information + \Cli::set_option('no-standardisation', true); // return the generated argument list return $arguments;