You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2090 lines
48 KiB
2090 lines
48 KiB
<?php
|
|
/**
|
|
* Fuel is a fast, lightweight, community driven PHP 5.4+ framework.
|
|
*
|
|
* @package Fuel
|
|
* @version 1.8.1
|
|
* @author Fuel Development Team
|
|
* @license MIT License
|
|
* @copyright 2010 - 2018 Fuel Development Team
|
|
* @link http://fuelphp.com
|
|
*/
|
|
|
|
namespace Oil;
|
|
|
|
/**
|
|
* Oil\Generate Class
|
|
*
|
|
* @package Fuel
|
|
* @subpackage Oil
|
|
* @category Core
|
|
* @author Phil Sturgeon
|
|
*/
|
|
class Generate
|
|
{
|
|
public static $create_folders = array();
|
|
public static $create_files = array();
|
|
|
|
public static $scaffolding = false;
|
|
|
|
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));
|
|
|
|
if (empty($file))
|
|
{
|
|
throw new Exception('No config filename has been provided.');
|
|
}
|
|
|
|
$config = array();
|
|
|
|
// load the config
|
|
if ($paths = \Finder::search('config', $file, '.php', true))
|
|
{
|
|
// Reverse the file list so that we load the core configs first and
|
|
// the app can override anything.
|
|
$paths = array_reverse($paths);
|
|
foreach ($paths as $path)
|
|
{
|
|
$config = \Fuel::load($path) + $config;
|
|
}
|
|
}
|
|
unset($path);
|
|
|
|
// We always pass in fields to a config, so lets sort them out here.
|
|
foreach ($args as $conf)
|
|
{
|
|
// Each paramater for a config is seperated by the : character
|
|
$parts = explode(":", $conf);
|
|
|
|
// We must have the 'name:value' if nothing else!
|
|
if (count($parts) >= 2)
|
|
{
|
|
$config[$parts[0]] = $parts[1];
|
|
}
|
|
}
|
|
|
|
$overwrite = (\Cli::option('o') or \Cli::option('overwrite'));
|
|
|
|
// strip whitespace and add tab
|
|
$export = str_replace(array(' ', 'array ('), array("\t", 'array('), var_export($config, true));
|
|
|
|
$content = '<?php'.PHP_EOL.PHP_EOL.'return '.$export.';';
|
|
$content .= <<<CONF
|
|
|
|
|
|
/* End of file $file.php */
|
|
CONF;
|
|
|
|
$module = \Cli::option('module', \Cli::option('m'));
|
|
|
|
// add support for `php oil g config module::file arg1:value1`
|
|
if (strpos($file, '::') !== false)
|
|
{
|
|
list($module, $file) = explode('::', $file);
|
|
}
|
|
|
|
// get the namespace path (if available)
|
|
if ( ! empty($module) and $path = \Autoloader::namespace_path('\\'.ucfirst($module)))
|
|
{
|
|
// strip the classes directory as we need the module root
|
|
// and construct the filename
|
|
$path = substr($path, 0, -8).'config'.DS.$file.'.php';
|
|
$path_name = "\\".ucfirst($module).'::';
|
|
}
|
|
elseif ( ! empty($module))
|
|
{
|
|
throw new Exception("{$module} need to be loaded first, please use config always_load.modules.");
|
|
}
|
|
else
|
|
{
|
|
$path = APPPATH.'config'.DS.$file.'.php';
|
|
$path_name = 'APPPATH/';
|
|
}
|
|
|
|
if ( ! $overwrite and is_file($path))
|
|
{
|
|
throw new Exception("{$path_name}/config/{$file}.php already exist, please use --overwrite option to force update");
|
|
}
|
|
|
|
$path = pathinfo($path);
|
|
|
|
try
|
|
{
|
|
\File::update($path['dirname'], $path['basename'], $content);
|
|
\Cli::write("Created config: {$path_name}config/{$file}.php", 'green');
|
|
}
|
|
catch (\InvalidPathException $e)
|
|
{
|
|
throw new Exception("Invalid basepath, cannot update at ".$path_name."config".DS."{$file}.php");
|
|
}
|
|
catch (\FileAccessException $e)
|
|
{
|
|
throw new Exception($path_name."config".DS.$file.".php could not be written.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function controller($args, $build = true)
|
|
{
|
|
if ( ! ($name = \Str::lower(array_shift($args))))
|
|
{
|
|
throw new Exception('No controller name was provided.');
|
|
}
|
|
|
|
// Do we want a view or a presenter?
|
|
$with_presenter = \Cli::option('with-presenter') or \Cli::option('with-viewmodel');
|
|
|
|
$actions = $args;
|
|
|
|
$filename = trim(str_replace(array('_', '-'), DS, $name), DS);
|
|
|
|
$base_path = APPPATH;
|
|
|
|
if ($module = \Cli::option('module'))
|
|
{
|
|
if ( ! ($base_path = \Module::exists($module)) )
|
|
{
|
|
throw new Exception('Module '.$module.' was not found within any of the defined module paths');
|
|
}
|
|
}
|
|
|
|
$filepath = $base_path.'classes'.DS.'controller'.DS.$filename.'.php';
|
|
|
|
// Uppercase each part of the class name and remove hyphens
|
|
$class_name = \Inflector::classify(str_replace(array('\\', '/'), '_', $name), false);
|
|
|
|
// Generate with test?
|
|
$with_test = \Cli::option('with-test');
|
|
if ($with_test) {
|
|
static::_create_test('Controller', $class_name, $base_path);
|
|
}
|
|
|
|
// Stick "blog" to the start of the array
|
|
array_unshift($args, $filename);
|
|
|
|
// Create views folder and each view file
|
|
if (\Cli::option('crud'))
|
|
{
|
|
static::views($args, 'scaffolding'.DS.'crud'.DS.'views', false);
|
|
}
|
|
else
|
|
{
|
|
static::views($args, 'scaffolding'.DS.'orm'.DS.'views', false);
|
|
}
|
|
|
|
$actions or $actions = array('index');
|
|
|
|
$action_str = '';
|
|
foreach ($actions as $action)
|
|
{
|
|
$action_str .= '
|
|
public function action_'.$action.'()
|
|
{
|
|
$data["subnav"] = array(\''.$action.'\'=> \'active\' );
|
|
$this->template->title = \'' . \Inflector::humanize($name) .' » ' . \Inflector::humanize($action) . '\';
|
|
$this->template->content = View::forge(\''.$filename.'/' . $action .'\', $data);
|
|
}'.PHP_EOL;
|
|
}
|
|
|
|
$extends = \Cli::option('extends', 'Controller_Template');
|
|
$prefix = \Config::get('controller_prefix', 'Controller_');
|
|
|
|
// Build Controller
|
|
$controller = <<<CONTROLLER
|
|
<?php
|
|
|
|
class {$prefix}{$class_name} extends {$extends}
|
|
{
|
|
{$action_str}
|
|
}
|
|
|
|
CONTROLLER;
|
|
|
|
// Write controller
|
|
static::create($filepath, $controller, 'controller');
|
|
|
|
// Do you want a presenter with that?
|
|
if ($with_presenter)
|
|
{
|
|
$presenter_filepath = $base_path.'classes'.DS.'presenter'.DS.$filename;
|
|
|
|
// One Presenter per action
|
|
foreach ($actions as $action)
|
|
{
|
|
$presenter = <<<PRESENTER
|
|
<?php
|
|
|
|
class Presenter_{$class_name}_{$action} extends Presenter
|
|
{
|
|
public function view()
|
|
{
|
|
\$this->content = "{$class_name} » {$action}";
|
|
}
|
|
}
|
|
PRESENTER;
|
|
|
|
// Write presenter
|
|
static::create($presenter_filepath.DS.$action.'.php', $presenter, 'presenter');
|
|
}
|
|
}
|
|
|
|
// Generate with test?
|
|
$with_test = \Cli::option('with-test');
|
|
if ($with_presenter and $with_test) {
|
|
static::_create_test('Presenter', $class_name, $base_path);
|
|
}
|
|
|
|
$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 <modelname> [<fieldname1>:<type1> |<fieldname2>:<type2> |..]");
|
|
}
|
|
|
|
if (empty($args))
|
|
{
|
|
throw new Exception('No fields have been provided, the model will not know how to build the table.');
|
|
}
|
|
|
|
$plural = (\Cli::option('singular') or \Cli::option('no-standardisation')) ? $singular : \Inflector::pluralize($singular);
|
|
|
|
$filename = trim(str_replace(array('_', '-'), DS, $singular), DS);
|
|
$base_path = APPPATH;
|
|
|
|
if ($module = \Cli::option('module'))
|
|
{
|
|
if ( ! ($base_path = \Module::exists($module)) )
|
|
{
|
|
throw new Exception('Module '.$module.' was not found within any of the defined module paths');
|
|
}
|
|
|
|
$module_namespace = ucwords($module);
|
|
}
|
|
|
|
$filepath = $base_path.'classes'.DS.'model'.DS.$filename.'.php';
|
|
|
|
// Uppercase each part of the class name and remove hyphens
|
|
$class_name = \Inflector::classify(str_replace(array('\\', '/'), '_', $singular), false);
|
|
|
|
// Generate 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'))
|
|
{
|
|
// model properties
|
|
if ( ! \Cli::option('no-properties'))
|
|
{
|
|
$contents = <<<CONTENTS
|
|
protected static \$_properties = array(
|
|
CONTENTS;
|
|
foreach ($args as $arg)
|
|
{
|
|
$contents .= PHP_EOL."\t\t\"".$arg['name']."\",";
|
|
}
|
|
$contents .= <<<CONTENTS
|
|
|
|
);
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
// created-at field
|
|
if($created_at = \Cli::option('created-at'))
|
|
{
|
|
is_string($created_at) or $created_at = 'created_at';
|
|
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_created_at = '$created_at';
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
// updated-at field
|
|
if($updated_at = \Cli::option('updated-at'))
|
|
{
|
|
is_string($updated_at) or $updated_at = 'updated_at';
|
|
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_updated_at = '$updated_at';
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
// mysql-timestamp field
|
|
if(\Cli::option('mysql-timestamp'))
|
|
{
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_mysql_timestamp = true;
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
// table name used by this model
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_table_name = '{$plural}';
|
|
|
|
CONTENTS;
|
|
|
|
// namespace and class definition
|
|
if ($module)
|
|
{
|
|
$model = <<<MODEL
|
|
<?php namespace {$module_namespace};
|
|
|
|
class Model_{$class_name} extends \Model_Crud
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
else
|
|
{
|
|
$model = <<<MODEL
|
|
<?php
|
|
|
|
class Model_{$class_name} extends \Model_Crud
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
}
|
|
|
|
// ORM models
|
|
else
|
|
{
|
|
|
|
// model properties
|
|
if ( ! \Cli::option('no-properties'))
|
|
{
|
|
$contents = <<<CONTENTS
|
|
protected static \$_properties = array(
|
|
CONTENTS;
|
|
foreach ($args as $arg)
|
|
{
|
|
$contents .= PHP_EOL."\t\t\"".$arg['name']."\" => 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']."),";
|
|
}
|
|
|
|
$contents .= PHP_EOL."\t\t),";
|
|
}
|
|
$contents .= <<<CONTENTS
|
|
|
|
);
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
// determine the type of timestamp used
|
|
$mysql_timestamp = (\Cli::option('mysql-timestamp')) ? 'true' : 'false';
|
|
|
|
// add date observers if needed
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_observers = array(
|
|
CONTENTS;
|
|
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';
|
|
|
|
$updated_at = \Cli::option('updated-at', 'updated_at');
|
|
is_string($updated_at) or $updated_at = 'updated_at';
|
|
|
|
$contents .= <<<CONTENTS
|
|
|
|
'Orm\Observer_CreatedAt' => array(
|
|
'events' => array('before_insert'),
|
|
'property' => '$created_at',
|
|
'mysql_timestamp' => $mysql_timestamp,
|
|
),
|
|
'Orm\Observer_UpdatedAt' => array(
|
|
'events' => array('before_update'),
|
|
'property' => '$updated_at',
|
|
'mysql_timestamp' => $mysql_timestamp,
|
|
),
|
|
CONTENTS;
|
|
}
|
|
|
|
$contents .= <<<CONTENTS
|
|
|
|
);
|
|
|
|
CONTENTS;
|
|
|
|
// add fields required 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';
|
|
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_soft_delete = array(
|
|
'mysql_timestamp' => $mysql_timestamp,
|
|
'deleted_field' => '{$deleted_at}',
|
|
);
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
// add fields required for temporal models
|
|
elseif (\Cli::option('temporal'))
|
|
{
|
|
$temporal_start = \Cli::option('temporal-start', 'temporal_start');
|
|
is_string($temporal_start) or $temporal_start = 'temporal_start';
|
|
|
|
$temporal_end = \Cli::option('temporal-end', 'temporal_end');
|
|
is_string($temporal_end) or $temporal_end = 'temporal_end';
|
|
|
|
$contents .= <<<CONTENTS
|
|
|
|
|
|
protected static \$_temporal = array(
|
|
'mysql_timestamp' => $mysql_timestamp,
|
|
'start_column' => '{$temporal_start}',
|
|
'end_column' => '{$temporal_end}',
|
|
|
|
);
|
|
CONTENTS;
|
|
}
|
|
|
|
// add fields required for nestedset models
|
|
elseif (\Cli::option('nestedset'))
|
|
{
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_tree = array(
|
|
|
|
CONTENTS;
|
|
|
|
if ($title = \Cli::option('title', false))
|
|
{
|
|
is_string($title) or $title = 'title';
|
|
$contents .= <<<CONTENTS
|
|
'title_field' => '{$title}',
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
if ($tree_id = \Cli::option('tree-id', false))
|
|
{
|
|
is_string($tree_id) or $tree_id = 'tree_id';
|
|
$contents .= <<<CONTENTS
|
|
'tree_field' => '{$tree_id}',
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
$left_id = \Cli::option('left-id', 'left_id');
|
|
is_string($left_id) or $left_id = 'left_id';
|
|
$contents .= <<<CONTENTS
|
|
'left_field' => '{$left_id}',
|
|
|
|
CONTENTS;
|
|
|
|
|
|
$right_id = \Cli::option('right-id', 'right_id');
|
|
is_string($right_id) or $right_id = 'right_id';
|
|
$contents .= <<<CONTENTS
|
|
'right_field' => '{$right_id}',
|
|
|
|
CONTENTS;
|
|
|
|
if($read_only = \Cli::option('read-only') and is_string($read_only))
|
|
{
|
|
$read_only = explode(',', $read_only);
|
|
$read_only = "'" . implode("', '", $read_only) . "'";
|
|
$read_only = <<<CONTENTS
|
|
'read_only' => array($read_only),
|
|
|
|
CONTENTS;
|
|
}
|
|
|
|
$contents .= <<<CONTENTS
|
|
);
|
|
|
|
CONTENTS;
|
|
|
|
}
|
|
|
|
// database table name
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_table_name = '{$plural}';
|
|
|
|
CONTENTS;
|
|
|
|
// primary keys
|
|
$keys = array();
|
|
foreach($args as $arg)
|
|
{
|
|
if (isset($arg['indexes']))
|
|
{
|
|
foreach ($arg['indexes'] as $idx)
|
|
{
|
|
if ($idx['primary'] === true)
|
|
{
|
|
$keys[$idx['order']] = $idx['column'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// define the primary keys
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_primary_key = array('
|
|
CONTENTS;
|
|
$contents .= implode("', '", $keys);
|
|
$contents .= <<<CONTENTS
|
|
');
|
|
|
|
CONTENTS;
|
|
|
|
// add empty relation structures
|
|
$contents .= <<<CONTENTS
|
|
|
|
protected static \$_has_many = array(
|
|
);
|
|
|
|
protected static \$_many_many = array(
|
|
);
|
|
|
|
protected static \$_has_one = array(
|
|
);
|
|
|
|
protected static \$_belongs_to = array(
|
|
);
|
|
|
|
CONTENTS;
|
|
|
|
// define the ORM class
|
|
$model = '';
|
|
if ( \Cli::option('soft-delete'))
|
|
{
|
|
if ($module)
|
|
{
|
|
$model .= <<<MODEL
|
|
<?php namespace {$module_namespace};
|
|
|
|
class Model_{$class_name} extends \Orm\Model_Soft
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
else
|
|
{
|
|
$model .= <<<MODEL
|
|
<?php
|
|
|
|
class Model_{$class_name} extends \Orm\Model_Soft
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
}
|
|
elseif ( \Cli::option('temporal'))
|
|
{
|
|
$model .= <<<MODEL
|
|
<?php
|
|
|
|
class Model_{$class_name} extends \Orm\Model_Temporal
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
elseif ( \Cli::option('nestedset'))
|
|
{
|
|
$model .= <<<MODEL
|
|
<?php
|
|
|
|
class Model_{$class_name} extends \Orm\Model_Nestedset
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
else
|
|
{
|
|
if ($module)
|
|
{
|
|
$model .= <<<MODEL
|
|
<?php namespace {$module_namespace};
|
|
|
|
class Model_{$class_name} extends \Orm\Model
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
else
|
|
{
|
|
$model .= <<<MODEL
|
|
<?php
|
|
|
|
class Model_{$class_name} extends \Orm\Model
|
|
{
|
|
{$contents}
|
|
}
|
|
|
|
MODEL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the model
|
|
static::create($filepath, $model, 'model');
|
|
|
|
if ( ! \Cli::option('no-migration'))
|
|
{
|
|
if ( ! empty($args))
|
|
{
|
|
array_unshift($args, 'create_'.$plural);
|
|
static::migration($args, false);
|
|
}
|
|
|
|
else
|
|
{
|
|
throw new \Exception('Not enough arguments to create this migration.');
|
|
}
|
|
}
|
|
|
|
$build and static::build();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function module($args)
|
|
{
|
|
if ( ! ($module_name = strtolower(array_shift($args)) ) )
|
|
{
|
|
throw new Exception('No module name has been provided.');
|
|
}
|
|
|
|
if ($path = \Module::exists($module_name))
|
|
{
|
|
throw new Exception('A module named '.$module_name.' already exists at '.$path);
|
|
}
|
|
|
|
$module_paths = \Config::get('module_paths');
|
|
$base = reset($module_paths);
|
|
|
|
if (count($module_paths) > 1)
|
|
{
|
|
\Cli::write('Your app has multiple module paths defined. Please choose the appropriate path from the list below', 'yellow', 'blue');
|
|
|
|
$options = array();
|
|
foreach ($module_paths as $key => $path)
|
|
{
|
|
$idx = $key+1;
|
|
\Cli::write('['.$idx.'] '.$path);
|
|
$options[] = $idx;
|
|
}
|
|
|
|
$path_idx = \Cli::prompt('Please choose the desired module path', $options);
|
|
|
|
$base = $module_paths[$path_idx - 1];
|
|
}
|
|
|
|
$module_path = $base.$module_name.DS;
|
|
|
|
static::$create_folders[] = $module_path;
|
|
static::$create_folders[] = $module_path.'classes/';
|
|
|
|
if ( ($folders = \Cli::option('folders')) !== true )
|
|
{
|
|
$folders = explode(',', $folders);
|
|
|
|
foreach ($folders as $folder)
|
|
{
|
|
static::$create_folders[] = $module_path.$folder;
|
|
}
|
|
}
|
|
|
|
static::$create_folders && static::build();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function views($args, $subfolder, $build = true)
|
|
{
|
|
$controller = strtolower(array_shift($args));
|
|
$controller_title = \Inflector::humanize($controller);
|
|
|
|
$base_path = APPPATH;
|
|
if ($module = \Cli::option('module'))
|
|
{
|
|
if ( ! ($base_path = \Module::exists($module)) )
|
|
{
|
|
throw new Exception('Module '.$module.' was not found within any of the defined module paths');
|
|
}
|
|
}
|
|
|
|
$view_dir = $base_path.'views/'.trim(str_replace(array('_', '-'), DS, $controller), DS).DS;
|
|
|
|
$args or $args = array('index');
|
|
|
|
// Make the directory for these views to be store in
|
|
is_dir($view_dir) or static::$create_folders[] = $view_dir;
|
|
|
|
// Add the default template if it doesn't exist
|
|
if ( ! is_file($app_template = $base_path.'views/template.php') )
|
|
{
|
|
static::create($app_template, file_get_contents(\Package::exists('oil').'views/scaffolding/template.php'), 'view');
|
|
}
|
|
|
|
$subnav = '';
|
|
foreach($args as $nav_item)
|
|
{
|
|
$subnav .= "\t<li class='<?php echo Arr::get(\$subnav, \"{$nav_item}\" ); ?>'><?php echo Html::anchor('{$controller}/{$nav_item}','".\Inflector::humanize($nav_item)."');?></li>".PHP_EOL;
|
|
}
|
|
|
|
foreach ($args as $action)
|
|
{
|
|
$view_title = (\Cli::option('with-presenter') or \Cli::option('with-viewmodel')) ? '<?php echo $content; ?>' : \Inflector::humanize($action);
|
|
|
|
$view = <<<VIEW
|
|
<ul class="nav nav-pills">
|
|
{$subnav}
|
|
</ul>
|
|
<p>{$view_title}</p>
|
|
VIEW;
|
|
|
|
// Generate with test?
|
|
$with_test = \Cli::option('with-test');
|
|
if ($with_test) {
|
|
static::_create_test('View', $controller, $base_path, $nav_item);
|
|
}
|
|
|
|
// Create this view
|
|
static::create($view_dir.$action.'.php', $view, 'view');
|
|
}
|
|
|
|
$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 <migrationname> [<fieldname1>:<type1> |<fieldname2>:<type2> |..]");
|
|
}
|
|
|
|
$base_path = APPPATH;
|
|
|
|
// Check if a migration with this name already exists
|
|
if ($module = \Cli::option('module'))
|
|
{
|
|
if ( ! ($base_path = \Module::exists($module)) )
|
|
{
|
|
throw new Exception('Module '.$module.' was not found within any of the defined module paths');
|
|
}
|
|
}
|
|
|
|
$duplicates = array();
|
|
foreach($migrations = new \GlobIterator($base_path.'migrations/*_'.$migration_name.'*') as $migration)
|
|
{
|
|
// check if it's really a duplicate
|
|
$part = explode('_', basename($migration->getFilename(), '.php'), 2);
|
|
if ($part[1] != $migration_name)
|
|
{
|
|
$part = substr($part[1], strlen($migration_name)+1);
|
|
if ( ! is_numeric($part))
|
|
{
|
|
// not a numbered suffix, but the same base classname
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$duplicates[] = $migration->getPathname();
|
|
}
|
|
|
|
// 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
|
|
if (\Cli::option('s', \Cli::option('skip')) === true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Tear up the file path and name to get the last duplicate
|
|
$file_name = pathinfo(end($duplicates), PATHINFO_FILENAME);
|
|
|
|
// Override the (most recent) migration with the same name by using its number
|
|
if (\Cli::option('f', \Cli::option('force')) === true)
|
|
{
|
|
list($number) = explode('_', $file_name);
|
|
}
|
|
|
|
// Name clashes but this is done by hand. Assume they know what they're doing and just increment the file
|
|
elseif (static::$scaffolding === false)
|
|
{
|
|
// Increment the name of this
|
|
$migration_name = \Str::increment(substr($file_name, 4), 2);
|
|
}
|
|
}
|
|
|
|
// See if the action exists
|
|
$methods = get_class_methods(__NAMESPACE__ . '\Generate_Migration_Actions');
|
|
|
|
// For empty migrations that dont have actions
|
|
$migration = array('', '');
|
|
|
|
// Loop through the actions and act on a matching action appropriately
|
|
foreach ($methods as $method_name)
|
|
{
|
|
// If the miration name starts with the name of the action method
|
|
if (substr($table_name, 0, strlen($method_name)) === $method_name)
|
|
{
|
|
/**
|
|
* Create an array of the subject the migration is about
|
|
*
|
|
* - 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')
|
|
*
|
|
*/
|
|
$subjects = array(false, false);
|
|
$matches = explode('_', str_replace($method_name . '_', '', $table_name));
|
|
|
|
// create_{table}
|
|
if (count($matches) == 1)
|
|
{
|
|
$subjects = array(false, $matches[0]);
|
|
}
|
|
|
|
// add_{field}_to_{table}
|
|
elseif (count($matches) == 3 && $matches[1] == 'to')
|
|
{
|
|
$subjects = array($matches[0], $matches[2]);
|
|
}
|
|
|
|
// delete_{field}_from_{table}
|
|
elseif (count($matches) == 3 && $matches[1] == 'from')
|
|
{
|
|
$subjects = array($matches[0], $matches[2]);
|
|
}
|
|
|
|
// rename_field_{field}_to_{field}_in_{table} (with underscores in field names)
|
|
elseif (count($matches) >= 5 && in_array('to', $matches) && in_array('in', $matches))
|
|
{
|
|
$subjects = array(
|
|
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)),
|
|
);
|
|
}
|
|
|
|
// rename_table
|
|
elseif ($method_name == 'rename_table')
|
|
{
|
|
$subjects = array(
|
|
implode('_', array_slice($matches, 0, array_search('to', $matches))),
|
|
implode('_', array_slice($matches, array_search('to', $matches)+1)),
|
|
);
|
|
}
|
|
|
|
// 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-'), $table_name);
|
|
|
|
if (preg_match('/^(create|drop|add)\-([a-z0-9\_]*)(\-to\-)?([a-z0-9\_]*)?$/i', $name, $deep_matches))
|
|
{
|
|
switch ($deep_matches[1])
|
|
{
|
|
case 'create' :
|
|
case 'drop' :
|
|
$subjects = array(false, $deep_matches[2]);
|
|
break;
|
|
|
|
case 'add' :
|
|
$subjects = array($deep_matches[2], $deep_matches[4]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// There is no subject here so just carry on with a normal empty migration
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Call the magic action which returns an array($up, $down) for the migration
|
|
$migration = call_user_func(__NAMESPACE__ . "\Generate_Migration_Actions::{$method_name}", $subjects, $args);
|
|
}
|
|
}
|
|
|
|
// Build the migration
|
|
list($up, $down) = $migration;
|
|
|
|
// If we don't have any, bail out
|
|
if (empty($up) and empty($down))
|
|
{
|
|
throw new \Exception('No migration could be generated. Please verify your command syntax.');
|
|
exit;
|
|
}
|
|
|
|
$migration_name = ucfirst(strtolower($migration_name));
|
|
|
|
$migration = <<<MIGRATION
|
|
<?php
|
|
|
|
namespace Fuel\Migrations;
|
|
|
|
class {$migration_name}
|
|
{
|
|
public function up()
|
|
{
|
|
{$up}
|
|
}
|
|
|
|
public function down()
|
|
{
|
|
{$down}
|
|
}
|
|
}
|
|
MIGRATION;
|
|
|
|
$number = isset($number) ? $number : static::_find_migration_number();
|
|
$filepath = $base_path.'migrations/'.$number.'_'.strtolower($migration_name).'.php';
|
|
|
|
static::create($filepath, $migration, 'migration');
|
|
|
|
$build and static::build();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function task($args, $build = true)
|
|
{
|
|
|
|
if ( ! ($name = \Str::lower(array_shift($args))))
|
|
{
|
|
throw new Exception('No task name was provided.');
|
|
}
|
|
|
|
if (empty($args))
|
|
{
|
|
\Cli::write("\tNo tasks actions have been provided, the TASK will only create default task.", 'red');
|
|
}
|
|
|
|
$args or $args = array('index');
|
|
|
|
// Uppercase each part of the class name and remove hyphens
|
|
$class_name = \Inflector::classify($name, false);
|
|
$filename = trim(str_replace(array('_', '-'), DS, $name), DS);
|
|
|
|
$base_path = APPPATH;
|
|
|
|
if ($module = \Cli::option('module'))
|
|
{
|
|
if ( ! ($base_path = \Module::exists($module)) )
|
|
{
|
|
throw new Exception('Module '.$module.' was not found within any of the defined module paths');
|
|
}
|
|
}
|
|
|
|
$filepath = $base_path.'tasks'.DS.$filename.'.php';
|
|
|
|
$action_str = '';
|
|
|
|
foreach ($args as $action)
|
|
{
|
|
$task_path = '\\'.\Inflector::humanize($name).'\\'.\Inflector::humanize($action);
|
|
|
|
if (!ctype_alpha($action[0])) {
|
|
throw new Exception('An action does not start with alphabet character. ABORTING');
|
|
}
|
|
|
|
$action_str .= '
|
|
/**
|
|
* This method gets ran when a valid method name is not used in the command.
|
|
*
|
|
* Usage (from command line):
|
|
*
|
|
* php oil r '.$name.':'.$action.' "arguments"
|
|
*
|
|
* @return string
|
|
*/
|
|
public function '.$action.'($args = NULL)
|
|
{
|
|
echo "\n===========================================";
|
|
echo "\nRunning task ['.\Inflector::humanize($name).':'. \Inflector::humanize($action) . ']";
|
|
echo "\n-------------------------------------------\n\n";
|
|
|
|
/***************************
|
|
Put in TASK DETAILS HERE
|
|
**************************/
|
|
}'.PHP_EOL;
|
|
|
|
$message = \Cli::color("\t\tPreparing task method [", 'green');
|
|
$message .= \Cli::color(\Inflector::humanize($action), 'cyan');
|
|
$message .= \Cli::color("]", 'green');
|
|
\Cli::write($message);
|
|
}
|
|
|
|
// Default RUN task action
|
|
$action = 'run';
|
|
$default_action_str = '
|
|
/**
|
|
* This method gets ran when a valid method name is not used in the command.
|
|
*
|
|
* Usage (from command line):
|
|
*
|
|
* php oil r '.$name.'
|
|
*
|
|
* @return string
|
|
*/
|
|
public function run($args = NULL)
|
|
{
|
|
echo "\n===========================================";
|
|
echo "\nRunning DEFAULT task ['.\Inflector::humanize($name).':'. \Inflector::humanize($action) . ']";
|
|
echo "\n-------------------------------------------\n\n";
|
|
|
|
/***************************
|
|
Put in TASK DETAILS HERE
|
|
**************************/
|
|
}'.PHP_EOL;
|
|
|
|
// Build Controller
|
|
$task_class = <<<CONTROLLER
|
|
<?php
|
|
|
|
namespace Fuel\Tasks;
|
|
|
|
class {$class_name}
|
|
{
|
|
{$default_action_str}
|
|
|
|
{$action_str}
|
|
}
|
|
/* End of file tasks/{$name}.php */
|
|
|
|
CONTROLLER;
|
|
|
|
// Write controller
|
|
static::create($filepath, $task_class, 'tasks');
|
|
|
|
$build and static::build();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function help()
|
|
{
|
|
$output = <<<HELP
|
|
Usage:
|
|
php oil [g|generate] [config|controller|views|model|migration|scaffold|admin|task|package] [options]
|
|
|
|
Runtime options:
|
|
-f, [--force] # Overwrite files that already exist
|
|
-s, [--skip] # Skip files that already exist
|
|
-q, [--quiet] # Supress status output
|
|
-t, [--speak] # Speak errors in a robot voice
|
|
|
|
Description:
|
|
The 'oil' command can be used to generate MVC components, database migrations
|
|
and run specific tasks.
|
|
|
|
Examples:
|
|
php oil generate controller <controllername> [<action1> |<action2> |..]
|
|
php oil g model <modelname> [<fieldname1>:<type1> |<fieldname2>:<type2> |..]
|
|
php oil g migration <migrationname> [<fieldname1>:<type1> |<fieldname2>:<type2> |..]
|
|
php oil g scaffold <modelname> [<fieldname1>:<type1> |<fieldname2>:<type2> |..]
|
|
php oil g scaffold/template_subfolder <modelname> [<fieldname1>:<type1> |<fieldname2>:<type2> |..]
|
|
php oil g config <filename> [<key1>:<value1> |<key2>:<value2> |..]
|
|
php oil g task <taskname> [<cmd1> |<cmd2> |..]
|
|
php oil g package <packagename>
|
|
|
|
Note that the next two lines are equivalent:
|
|
php oil g scaffold <modelname> ...
|
|
php oil g scaffold/orm <modelname> ...
|
|
|
|
Documentation:
|
|
http://docs.fuelphp.com/packages/oil/generate.html
|
|
HELP;
|
|
|
|
\Cli::write($output);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function package($args, $build = true)
|
|
{
|
|
$name = str_replace(array('/', '_', '-'), '', \Str::lower(array_shift($args)));
|
|
$class_name = ucfirst($name);
|
|
$vcs = \Cli::option('vcs', \Cli::option('v', false));
|
|
$path = \Cli::option('path', \Cli::option('p', PKGPATH));
|
|
$drivers = \Cli::option('drivers', \Cli::option('d', ''));
|
|
|
|
if (empty($name))
|
|
{
|
|
throw new \Exception('No package name has been provided.');
|
|
}
|
|
|
|
if ( ! in_array($path, \Config::get('package_paths')) and ! in_array(realpath($path), \Config::get('package_paths')) )
|
|
{
|
|
throw new \Exception('Given path is not a valid package path.');
|
|
}
|
|
|
|
\Str::ends_with($path, DS) or $path .= DS;
|
|
$path .= $name . DS;
|
|
|
|
if (is_dir($path))
|
|
{
|
|
throw new \Exception('Package already exists.');
|
|
}
|
|
|
|
if ($vcs)
|
|
{
|
|
$output = <<<COMPOSER
|
|
{
|
|
"name": "fuel/{$name}",
|
|
"type": "fuel-package",
|
|
"description": "{$class_name} package",
|
|
"keywords": [""],
|
|
"homepage": "http://fuelphp.com",
|
|
"license": "MIT",
|
|
"authors": [
|
|
{
|
|
"name": "AUTHOR",
|
|
"email": "AUTHOR@example.com"
|
|
}
|
|
],
|
|
"require": {
|
|
"composer/installers": "~1.0"
|
|
},
|
|
"extra": {
|
|
"installer-name": "{$name}"
|
|
}
|
|
}
|
|
|
|
COMPOSER;
|
|
|
|
static::create($path . 'composer.json', $output);
|
|
|
|
$output = <<<README
|
|
# {$class_name} package
|
|
Here comes some description
|
|
|
|
README;
|
|
|
|
static::create($path . 'README.md', $output);
|
|
}
|
|
|
|
if ( ! empty($drivers))
|
|
{
|
|
$drivers === true or $drivers = explode(',', $drivers);
|
|
|
|
$output = <<<CLASS
|
|
<?php
|
|
|
|
namespace {$class_name};
|
|
|
|
class {$class_name}Exception extends \FuelException {}
|
|
|
|
class {$class_name}
|
|
{
|
|
/**
|
|
* loaded instance
|
|
*/
|
|
protected static \$_instance = null;
|
|
|
|
/**
|
|
* array of loaded instances
|
|
*/
|
|
protected static \$_instances = array();
|
|
|
|
/**
|
|
* Default config
|
|
* @var array
|
|
*/
|
|
protected static \$_defaults = array();
|
|
|
|
/**
|
|
* Init
|
|
*/
|
|
public static function _init()
|
|
{
|
|
\Config::load('{$name}', true);
|
|
}
|
|
|
|
/**
|
|
* {$class_name} driver forge.
|
|
*
|
|
* @param string \$instance Instance name
|
|
* @param array \$config Extra config array
|
|
* @return {$class_name} instance
|
|
*/
|
|
public static function forge(\$instance = 'default', \$config = array())
|
|
{
|
|
is_array(\$config) or \$config = array('driver' => \$config);
|
|
|
|
\$config = \Arr::merge(static::\$_defaults, \Config::get('{$name}', array()), \$config);
|
|
|
|
\$class = '\\{$class_name}\\{$class_name}_' . ucfirst(strtolower(\$config['driver']));
|
|
|
|
if( ! class_exists(\$class, true))
|
|
{
|
|
throw new \FuelException('Could not find {$class_name} driver: ' . ucfirst(strtolower(\$config['driver'])));
|
|
}
|
|
|
|
\$driver = new \$class(\$config);
|
|
|
|
static::\$_instances[\$instance] = \$driver;
|
|
|
|
return \$driver;
|
|
}
|
|
|
|
/**
|
|
* Return a specific driver, or the default instance (is created if necessary)
|
|
*
|
|
* @param string \$instance
|
|
* @return {$class_name} instance
|
|
*/
|
|
public static function instance(\$instance = null)
|
|
{
|
|
if (\$instance !== null)
|
|
{
|
|
if ( ! array_key_exists(\$instance, static::\$_instances))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return static::\$_instances[\$instance];
|
|
}
|
|
|
|
if (static::\$_instance === null)
|
|
{
|
|
static::\$_instance = static::forge();
|
|
}
|
|
|
|
return static::\$_instance;
|
|
}
|
|
}
|
|
|
|
CLASS;
|
|
|
|
static::create($path . 'classes' . DS . $name . '.php', $output);
|
|
|
|
$output = <<<DRIVER
|
|
<?php
|
|
|
|
namespace {$class_name};
|
|
|
|
abstract class {$class_name}_Driver
|
|
{
|
|
/**
|
|
* Driver config
|
|
* @var array
|
|
*/
|
|
protected \$config = array();
|
|
|
|
/**
|
|
* Driver constructor
|
|
*
|
|
* @param array \$config driver config
|
|
*/
|
|
public function __construct(array \$config = array())
|
|
{
|
|
\$this->config = \$config;
|
|
}
|
|
|
|
/**
|
|
* Get a driver config setting.
|
|
*
|
|
* @param string \$key the config key
|
|
* @param mixed \$default the default value
|
|
* @return mixed the config setting value
|
|
*/
|
|
public function get_config(\$key, \$default = null)
|
|
{
|
|
return \Arr::get(\$this->config, \$key, \$default);
|
|
}
|
|
|
|
/**
|
|
* Set a driver config setting.
|
|
*
|
|
* @param string \$key the config key
|
|
* @param mixed \$value the new config value
|
|
* @return object \$this for chaining
|
|
*/
|
|
public function set_config(\$key, \$value)
|
|
{
|
|
\Arr::set(\$this->config, \$key, \$value);
|
|
|
|
return \$this;
|
|
}
|
|
}
|
|
|
|
DRIVER;
|
|
|
|
static::create($path . 'classes' . DS . $name . DS . 'driver.php', $output);
|
|
|
|
$bootstrap = PHP_EOL."\t'{$class_name}\\\\{$class_name}_Driver' => __DIR__ . '/classes/{$name}/driver.php',";
|
|
if (is_array($drivers))
|
|
{
|
|
foreach ($drivers as $driver)
|
|
{
|
|
$driver = \Str::lower($driver);
|
|
$driver_name = ucfirst($driver);
|
|
$output = <<<CLASS
|
|
<?php
|
|
|
|
namespace {$class_name};
|
|
|
|
class {$class_name}_{$driver_name} extends {$class_name}_Driver
|
|
{
|
|
/**
|
|
* Driver specific functions
|
|
*/
|
|
}
|
|
|
|
CLASS;
|
|
$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);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$output = <<<CLASS
|
|
<?php
|
|
|
|
namespace {$class_name};
|
|
|
|
class {$class_name}Exception extends \FuelException {}
|
|
|
|
class {$class_name}
|
|
{
|
|
/**
|
|
* Default config
|
|
* @var array
|
|
*/
|
|
protected static \$_defaults = array();
|
|
|
|
/**
|
|
* Driver config
|
|
* @var array
|
|
*/
|
|
protected \$config = array();
|
|
|
|
/**
|
|
* Init
|
|
*/
|
|
public static function _init()
|
|
{
|
|
\Config::load('{$name}', true);
|
|
}
|
|
|
|
/**
|
|
* {$class_name} driver forge.
|
|
*
|
|
* @param array \$config Config array
|
|
* @return {$class_name}
|
|
*/
|
|
public static function forge(\$config = array())
|
|
{
|
|
\$config = \Arr::merge(static::\$_defaults, \Config::get('{$name}', array()), \$config);
|
|
|
|
\$class = new static(\$config);
|
|
|
|
return \$class;
|
|
}
|
|
|
|
/**
|
|
* Driver constructor
|
|
*
|
|
* @param array \$config driver config
|
|
*/
|
|
public function __construct(array \$config = array())
|
|
{
|
|
\$this->config = \$config;
|
|
}
|
|
|
|
/**
|
|
* Get a config setting.
|
|
*
|
|
* @param string \$key the config key
|
|
* @param mixed \$default the default value
|
|
* @return mixed the config setting value
|
|
*/
|
|
public function get_config(\$key, \$default = null)
|
|
{
|
|
return \Arr::get(\$this->config, \$key, \$default);
|
|
}
|
|
|
|
/**
|
|
* Set a config setting.
|
|
*
|
|
* @param string \$key the config key
|
|
* @param mixed \$value the new config value
|
|
* @return object \$this for chaining
|
|
*/
|
|
public function set_config(\$key, \$value)
|
|
{
|
|
\Arr::set(\$this->config, \$key, \$value);
|
|
|
|
return \$this;
|
|
}
|
|
}
|
|
|
|
CLASS;
|
|
|
|
static::create($path . 'classes' . DS . $name . '.php', $output);
|
|
|
|
$bootstrap = "";
|
|
}
|
|
|
|
$output = <<<CONFIG
|
|
<?php
|
|
|
|
return array(
|
|
|
|
);
|
|
|
|
CONFIG;
|
|
|
|
static::create($path . 'config' . DS . $name . '.php', $output);
|
|
|
|
$output = <<<CLASS
|
|
<?php
|
|
|
|
Autoloader::add_core_namespace('{$class_name}');
|
|
|
|
Autoloader::add_classes(array(
|
|
'{$class_name}\\\\{$class_name}' => __DIR__ . '/classes/{$name}.php',
|
|
'{$class_name}\\\\{$class_name}Exception' => __DIR__ . '/classes/{$name}.php',
|
|
{$bootstrap}
|
|
));
|
|
|
|
CLASS;
|
|
static::create($path . 'bootstrap.php', $output);
|
|
|
|
$build and static::build();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function create($filepath, $contents, $type = 'file')
|
|
{
|
|
$directory = dirname($filepath);
|
|
is_dir($directory) or static::$create_folders[] = $directory;
|
|
|
|
// Check if a file exists then work out how to react
|
|
if (is_file($filepath))
|
|
{
|
|
// Don't override a file
|
|
if (\Cli::option('s', \Cli::option('skip')) === true)
|
|
{
|
|
// Don't bother trying to make this, carry on camping
|
|
return;
|
|
}
|
|
|
|
// If we aren't skipping it, tell em to use -f
|
|
if (\Cli::option('f', \Cli::option('force')) === null)
|
|
{
|
|
throw new Exception($filepath .' already exists, use -f or --force to override.');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
static::$create_files[] = array(
|
|
'path' => $filepath,
|
|
'contents' => $contents,
|
|
'type' => $type,
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function build()
|
|
{
|
|
foreach (static::$create_folders as $folder)
|
|
{
|
|
is_dir($folder) or mkdir($folder, 0755, TRUE);
|
|
}
|
|
|
|
$result = true;
|
|
|
|
foreach (static::$create_files as $file)
|
|
{
|
|
\Cli::write("\tCreating {$file['type']}: {$file['path']}", 'green');
|
|
|
|
if ( ! $handle = @fopen($file['path'], 'w+'))
|
|
{
|
|
throw new Exception('Cannot open file: '. $file['path']);
|
|
}
|
|
|
|
$result = @fwrite($handle, $file['contents']);
|
|
|
|
// Write $somecontent to our opened file.
|
|
if ($result === false)
|
|
{
|
|
throw new Exception('Cannot write to file: '. $file['path']);
|
|
}
|
|
|
|
@fclose($handle);
|
|
|
|
@chmod($file['path'], 0666);
|
|
}
|
|
|
|
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['indexes']))
|
|
{
|
|
foreach ($arg['indexes'] as $idx)
|
|
{
|
|
if ($idx['primary'])
|
|
{
|
|
$pk = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// keep track of the primary keys added
|
|
$pk_counter = 0;
|
|
|
|
// add a PK if none are present
|
|
if ( ! $pk and ! \Cli::option('no-standardisation'))
|
|
{
|
|
// define the default key column
|
|
$normalized['id'] = array('name' => 'id', 'data_type' => 'int', 'unsigned' => true, 'null' => false, 'auto_increment' => true, 'constraint' => '11');
|
|
|
|
// and it's primary index
|
|
$normalized['id']['indexes'] = array('PRIMARY' => array(
|
|
'name' => 'PRIMARY', 'column' => 'id', 'order' => strval(++$pk_counter), 'type' => 'BTREE', 'primary' => true, 'unique' => true, 'null' => false, 'ascending' => true,
|
|
));
|
|
}
|
|
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()) use($pk_counter) {
|
|
|
|
// 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;
|
|
|
|
// need to add an index?
|
|
if (isset($options['key']))
|
|
{
|
|
// add a primary key
|
|
if ($options['key'] == 'PRI')
|
|
{
|
|
$field['indexes'] = array('PRIMARY' => array(
|
|
'name' => 'PRIMARY', 'column' => $name, 'order' => strval(++$pk_counter), 'type' => 'BTREE', 'primary' => true, 'unique' => true, 'null' => false, 'ascending' => true,
|
|
));
|
|
}
|
|
|
|
unset($options['key']);
|
|
}
|
|
|
|
// 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;
|
|
|
|
if ($module = \Cli::option('module'))
|
|
{
|
|
if ( ! ($base_path = \Module::exists($module)) )
|
|
{
|
|
throw new Exception('Module ' . $module . ' was not found within any of the defined module paths');
|
|
}
|
|
}
|
|
|
|
foreach(new \GlobIterator($base_path .'migrations/*_*.php') as $file)
|
|
{
|
|
$migrations[] = $file->getPathname();
|
|
}
|
|
if ( ! empty($migrations))
|
|
{
|
|
sort($migrations);
|
|
list($last) = explode('_', basename(end($migrations)));
|
|
}
|
|
else
|
|
{
|
|
$last = 0;
|
|
}
|
|
|
|
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'))
|
|
{
|
|
$contents = file_get_contents($app_path);
|
|
}
|
|
elseif (is_file($core_path = COREPATH.'config'.DS.'migrations.php'))
|
|
{
|
|
$contents = file_get_contents($core_path);
|
|
}
|
|
else
|
|
{
|
|
throw new \Exception('Config file core/config/migrations.php');
|
|
exit;
|
|
}
|
|
|
|
$contents = preg_replace("#('version'[ \t]+=>)[ \t]+([0-9]+),#i", "$1 $version,", $contents);
|
|
|
|
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));
|
|
if ( ! empty($nav_item) and $type === 'View')
|
|
{
|
|
$filepath = $filepath.DS.strtolower($nav_item);
|
|
$class_name = $class_name.'_'.ucwords($nav_item);
|
|
}
|
|
$output = <<<TEST
|
|
<?php
|
|
|
|
class Test_{$type}_{$class_name} extends TestCase
|
|
{
|
|
}
|
|
TEST;
|
|
|
|
static::create($filepath.'.php', $output, 'test');
|
|
}
|
|
}
|
|
|
|
/* End of file oil/classes/generate.php */
|