1
0

Added new (clean) yii boilerplate

This commit is contained in:
2014-05-13 12:40:42 +02:00
parent 1d6d975a16
commit 99d29b432b
1983 changed files with 653465 additions and 17 deletions

View File

@@ -0,0 +1,175 @@
<?php
/**
* ControllerCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* ControllerCommand generates a controller class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.cli.commands.shell
* @since 1.0
*/
class ControllerCommand extends CConsoleCommand
{
/**
* @var string the directory that contains templates for the model command.
* Defaults to null, meaning using 'framework/cli/views/shell/controller'.
* If you set this path and some views are missing in the directory,
* the default views will be used.
*/
public $templatePath;
public function getHelp()
{
return <<<EOD
USAGE
controller <controller-ID> [action-ID] ...
DESCRIPTION
This command generates a controller and views associated with
the specified actions.
PARAMETERS
* controller-ID: required, controller ID, e.g., 'post'.
If the controller should be located under a subdirectory,
please specify the controller ID as 'path/to/ControllerID',
e.g., 'admin/user'.
If the controller belongs to a module, please specify
the controller ID as 'ModuleID/ControllerID' or
'ModuleID/path/to/Controller' (assuming the controller is
under a subdirectory of that module).
* action-ID: optional, action ID. You may supply one or several
action IDs. A default 'index' action will always be generated.
EXAMPLES
* Generates the 'post' controller:
controller post
* Generates the 'post' controller with additional actions 'contact'
and 'about':
controller post contact about
* Generates the 'post' controller which should be located under
the 'admin' subdirectory of the base controller path:
controller admin/post
* Generates the 'post' controller which should belong to
the 'admin' module:
controller admin/post
NOTE: in the last two examples, the commands are the same, but
the generated controller file is located under different directories.
Yii is able to detect whether 'admin' refers to a module or a subdirectory.
EOD;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: controller name is required.\n";
echo $this->getHelp();
return 1;
}
$module=Yii::app();
$controllerID=$args[0];
if(($pos=strrpos($controllerID,'/'))===false)
{
$controllerClass=ucfirst($controllerID).'Controller';
$controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
$controllerID[0]=strtolower($controllerID[0]);
}
else
{
$last=substr($controllerID,$pos+1);
$last[0]=strtolower($last[0]);
$pos2=strpos($controllerID,'/');
$first=substr($controllerID,0,$pos2);
$middle=$pos===$pos2?'':substr($controllerID,$pos2+1,$pos-$pos2);
$controllerClass=ucfirst($last).'Controller';
$controllerFile=($middle===''?'':$middle.'/').$controllerClass.'.php';
$controllerID=$middle===''?$last:$middle.'/'.$last;
if(($m=Yii::app()->getModule($first))!==null)
$module=$m;
else
{
$controllerFile=$first.'/'.$controllerClass.'.php';
$controllerID=$first.'/'.$controllerID;
}
$controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerFile);
}
$args[]='index';
$actions=array_unique(array_splice($args,1));
$templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/controller':$this->templatePath;
$list=array(
basename($controllerFile)=>array(
'source'=>$templatePath.DIRECTORY_SEPARATOR.'controller.php',
'target'=>$controllerFile,
'callback'=>array($this,'generateController'),
'params'=>array($controllerClass, $actions),
),
);
$viewPath=$module->viewPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerID);
foreach($actions as $name)
{
$list[$name.'.php']=array(
'source'=>$templatePath.DIRECTORY_SEPARATOR.'view.php',
'target'=>$viewPath.DIRECTORY_SEPARATOR.$name.'.php',
'callback'=>array($this,'generateAction'),
'params'=>array('controller'=>$controllerClass, 'action'=>$name),
);
}
$this->copyFiles($list);
if($module instanceof CWebModule)
$moduleID=$module->id.'/';
else
$moduleID='';
echo <<<EOD
Controller '{$controllerID}' has been created in the following file:
$controllerFile
You may access it in the browser using the following URL:
http://hostname/path/to/index.php?r={$moduleID}{$controllerID}
EOD;
}
public function generateController($source,$params)
{
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/controller/'.basename($source);
return $this->renderFile($source,array('className'=>$params[0],'actions'=>$params[1]),true);
}
public function generateAction($source,$params)
{
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/controller/'.basename($source);
return $this->renderFile($source,$params,true);
}
}

View File

@@ -0,0 +1,326 @@
<?php
/**
* CrudCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CrudCommand generates code implementing CRUD operations.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.cli.commands.shell
* @since 1.0
*/
class CrudCommand extends CConsoleCommand
{
/**
* @var string the directory that contains templates for crud commands.
* Defaults to null, meaning using 'framework/cli/views/shell/crud'.
* If you set this path and some views are missing in the directory,
* the default views will be used.
*/
public $templatePath;
/**
* @var string the directory that contains functional test classes.
* Defaults to null, meaning using 'protected/tests/functional'.
* If this is false, it means functional test file should NOT be generated.
*/
public $functionalTestPath;
/**
* @var array list of actions to be created. Each action must be associated with a template file with the same name.
*/
public $actions=array('create','update','index','view','admin','_form','_view','_search');
public function getHelp()
{
return <<<EOD
USAGE
crud <model-class> [controller-ID] ...
DESCRIPTION
This command generates a controller and views that accomplish
CRUD operations for the specified data model.
PARAMETERS
* model-class: required, the name of the data model class. This can
also be specified as a path alias (e.g. application.models.Post).
If the model class belongs to a module, it should be specified
as 'ModuleID.models.ClassName'.
* controller-ID: optional, the controller ID (e.g. 'post').
If this is not specified, the model class name will be used
as the controller ID. In this case, if the model belongs to
a module, the controller will also be created under the same
module.
If the controller should be located under a subdirectory,
please specify the controller ID as 'path/to/ControllerID'
(e.g. 'admin/user').
If the controller belongs to a module (different from the module
that the model belongs to), please specify the controller ID
as 'ModuleID/ControllerID' or 'ModuleID/path/to/Controller'.
EXAMPLES
* Generates CRUD for the Post model:
crud Post
* Generates CRUD for the Post model which belongs to module 'admin':
crud admin.models.Post
* Generates CRUD for the Post model. The generated controller should
belong to module 'admin', but not the model class:
crud Post admin/post
EOD;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: data model class is required.\n";
echo $this->getHelp();
return 1;
}
$module=Yii::app();
$modelClass=$args[0];
if(($pos=strpos($modelClass,'.'))===false)
$modelClass='application.models.'.$modelClass;
else
{
$id=substr($modelClass,0,$pos);
if(($m=Yii::app()->getModule($id))!==null)
$module=$m;
}
$modelClass=Yii::import($modelClass);
if(isset($args[1]))
{
$controllerID=$args[1];
if(($pos=strrpos($controllerID,'/'))===false)
{
$controllerClass=ucfirst($controllerID).'Controller';
$controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
$controllerID[0]=strtolower($controllerID[0]);
}
else
{
$last=substr($controllerID,$pos+1);
$last[0]=strtolower($last);
$pos2=strpos($controllerID,'/');
$first=substr($controllerID,0,$pos2);
$middle=$pos===$pos2?'':substr($controllerID,$pos2+1,$pos-$pos2);
$controllerClass=ucfirst($last).'Controller';
$controllerFile=($middle===''?'':$middle.'/').$controllerClass.'.php';
$controllerID=$middle===''?$last:$middle.'/'.$last;
if(($m=Yii::app()->getModule($first))!==null)
$module=$m;
else
{
$controllerFile=$first.'/'.$controllerFile;
$controllerID=$first.'/'.$controllerID;
}
$controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerFile);
}
}
else
{
$controllerID=$modelClass;
$controllerClass=ucfirst($controllerID).'Controller';
$controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
$controllerID[0]=strtolower($controllerID[0]);
}
$templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/crud':$this->templatePath;
$functionalTestPath=$this->functionalTestPath===null?Yii::getPathOfAlias('application.tests.functional'):$this->functionalTestPath;
$viewPath=$module->viewPath.DIRECTORY_SEPARATOR.str_replace('.',DIRECTORY_SEPARATOR,$controllerID);
$fixtureName=$this->pluralize($modelClass);
$fixtureName[0]=strtolower($fixtureName);
$list=array(
basename($controllerFile)=>array(
'source'=>$templatePath.'/controller.php',
'target'=>$controllerFile,
'callback'=>array($this,'generateController'),
'params'=>array($controllerClass,$modelClass),
),
);
if($functionalTestPath!==false)
{
$list[$modelClass.'Test.php']=array(
'source'=>$templatePath.'/test.php',
'target'=>$functionalTestPath.DIRECTORY_SEPARATOR.$modelClass.'Test.php',
'callback'=>array($this,'generateTest'),
'params'=>array($controllerID,$fixtureName,$modelClass),
);
}
foreach($this->actions as $action)
{
$list[$action.'.php']=array(
'source'=>$templatePath.'/'.$action.'.php',
'target'=>$viewPath.'/'.$action.'.php',
'callback'=>array($this,'generateView'),
'params'=>$modelClass,
);
}
$this->copyFiles($list);
if($module instanceof CWebModule)
$moduleID=$module->id.'/';
else
$moduleID='';
echo "\nCrud '{$controllerID}' has been successfully created. You may access it via:\n";
echo "http://hostname/path/to/index.php?r={$moduleID}{$controllerID}\n";
}
public function generateController($source,$params)
{
list($controllerClass,$modelClass)=$params;
$model=CActiveRecord::model($modelClass);
$id=$model->tableSchema->primaryKey;
if($id===null)
throw new ShellException(Yii::t('yii','Error: Table "{table}" does not have a primary key.',array('{table}'=>$model->tableName())));
elseif(is_array($id))
throw new ShellException(Yii::t('yii','Error: Table "{table}" has a composite primary key which is not supported by crud command.',array('{table}'=>$model->tableName())));
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
return $this->renderFile($source,array(
'ID'=>$id,
'controllerClass'=>$controllerClass,
'modelClass'=>$modelClass,
),true);
}
public function generateView($source,$modelClass)
{
$model=CActiveRecord::model($modelClass);
$table=$model->getTableSchema();
$columns=$table->columns;
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
return $this->renderFile($source,array(
'ID'=>$table->primaryKey,
'modelClass'=>$modelClass,
'columns'=>$columns),true);
}
public function generateTest($source,$params)
{
list($controllerID,$fixtureName,$modelClass)=$params;
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
return $this->renderFile($source, array(
'controllerID'=>$controllerID,
'fixtureName'=>$fixtureName,
'modelClass'=>$modelClass,
),true);
}
public function generateInputLabel($modelClass,$column)
{
return "CHtml::activeLabelEx(\$model,'{$column->name}')";
}
public function generateInputField($modelClass,$column)
{
if($column->type==='boolean')
return "CHtml::activeCheckBox(\$model,'{$column->name}')";
elseif(stripos($column->dbType,'text')!==false)
return "CHtml::activeTextArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
else
{
if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
$inputField='activePasswordField';
else
$inputField='activeTextField';
if($column->type!=='string' || $column->size===null)
return "CHtml::{$inputField}(\$model,'{$column->name}')";
else
{
if(($size=$maxLength=$column->size)>60)
$size=60;
return "CHtml::{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
}
}
}
public function generateActiveLabel($modelClass,$column)
{
return "\$form->labelEx(\$model,'{$column->name}')";
}
public function generateActiveField($modelClass,$column)
{
if($column->type==='boolean')
return "\$form->checkBox(\$model,'{$column->name}')";
elseif(stripos($column->dbType,'text')!==false)
return "\$form->textArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
else
{
if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
$inputField='passwordField';
else
$inputField='textField';
if($column->type!=='string' || $column->size===null)
return "\$form->{$inputField}(\$model,'{$column->name}')";
else
{
if(($size=$maxLength=$column->size)>60)
$size=60;
return "\$form->{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
}
}
}
public function guessNameColumn($columns)
{
foreach($columns as $column)
{
if(!strcasecmp($column->name,'name'))
return $column->name;
}
foreach($columns as $column)
{
if(!strcasecmp($column->name,'title'))
return $column->name;
}
foreach($columns as $column)
{
if($column->isPrimaryKey)
return $column->name;
}
return 'id';
}
public function class2id($className)
{
return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $className))),'-');
}
public function class2name($className,$pluralize=false)
{
if($pluralize)
$className=$this->pluralize($className);
return ucwords(trim(strtolower(str_replace(array('-','_'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $className)))));
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* FormCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* FormCommand generates a form view based on a specified model.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.cli.commands.shell
* @since 1.0
*/
class FormCommand extends CConsoleCommand
{
/**
* @var string the directory that contains templates for the form command.
* Defaults to null, meaning using 'framework/cli/views/shell/form'.
* If you set this path and some views are missing in the directory,
* the default views will be used.
*/
public $templatePath;
public function getHelp()
{
return <<<EOD
USAGE
form <model-class> <view-name> [scenario]
DESCRIPTION
This command generates a form view that can be used to collect inputs
for the specified model.
PARAMETERS
* model-class: required, model class. This can be either the name of
the model class (e.g. 'ContactForm') or the path alias of the model
class file (e.g. 'application.models.ContactForm'). The former can
be used only if the class can be autoloaded.
* view-name: required, the name of the view to be generated. This should
be the path alias of the view script (e.g. 'application.views.site.contact').
* scenario: optional, the name of the scenario in which the model is used
(e.g. 'update', 'login'). This determines which model attributes the
generated form view will be used to collect user inputs for. If this
is not provided, the scenario will be assumed to be '' (empty string).
EXAMPLES
* Generates the view script for the 'ContactForm' model:
form ContactForm application.views.site.contact
EOD;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0],$args[1]))
{
echo "Error: both model class and view name are required.\n";
echo $this->getHelp();
return 1;
}
$scenario=isset($args[2]) ? $args[2] : '';
$modelClass=Yii::import($args[0],true);
$model=new $modelClass($scenario);
$attributes=$model->getSafeAttributeNames();
$templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/form':$this->templatePath;
$viewPath=Yii::getPathOfAlias($args[1]);
$viewName=basename($viewPath);
$viewPath.='.php';
$params=array(
'modelClass'=>$modelClass,
'viewName'=>$viewName,
'attributes'=>$attributes,
);
$list=array(
basename($viewPath)=>array(
'source'=>$templatePath.'/form.php',
'target'=>$viewPath,
'callback'=>array($this,'generateForm'),
'params'=>$params,
),
);
$this->copyFiles($list);
$actionFile=$templatePath.'/action.php';
if(!is_file($actionFile)) // fall back to default ones
$actionFile=YII_PATH.'/cli/views/shell/form/action.php';
echo "The following form view has been successfully created:\n";
echo "\t$viewPath\n\n";
echo "You may use the following code in your controller action:\n\n";
echo $this->renderFile($actionFile,$params,true);
echo "\n";
}
public function generateForm($source,$params)
{
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/form/'.basename($source);
return $this->renderFile($source,$params,true);
}
public function class2id($className)
{
if(strrpos($className,'Form')===strlen($className)-4)
$className=substr($className,0,strlen($className)-4);
return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $className))),'-');
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* HelpCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* HelpCommand displays help information for commands under yiic shell.
*
* @property string $help The command description.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.cli.commands.shell
* @since 1.0
*/
class HelpCommand extends CConsoleCommand
{
/**
* Execute the action.
* @param array $args command line parameters specific for this command
* @return integer non zero application exit code for help
*/
public function run($args)
{
$runner=$this->getCommandRunner();
$commands=$runner->commands;
if(isset($args[0]))
$name=strtolower($args[0]);
if(!isset($args[0]) || !isset($commands[$name]))
{
echo <<<EOD
At the prompt, you may enter a PHP statement or one of the following commands:
EOD;
$commandNames=array_keys($commands);
sort($commandNames);
echo ' - '.implode("\n - ",$commandNames);
echo <<<EOD
Type 'help <command-name>' for details about a command.
To expand the above command list, place your command class files
under 'protected/commands/shell', or a directory specified
by the 'YIIC_SHELL_COMMAND_PATH' environment variable. The command class
must extend from CConsoleCommand.
EOD;
}
else
echo $runner->createCommand($name)->getHelp();
return 1;
}
/**
* Provides the command description.
* @return string the command description.
*/
public function getHelp()
{
return <<<EOD
USAGE
help [command-name]
DESCRIPTION
Display the help information for the specified command.
If the command name is not given, all commands will be listed.
PARAMETERS
* command-name: optional, the name of the command to show help information.
EOD;
}
}

View File

@@ -0,0 +1,488 @@
<?php
/**
* ModelCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* ModelCommand generates a model class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.cli.commands.shell
* @since 1.0
*/
class ModelCommand extends CConsoleCommand
{
/**
* @var string the directory that contains templates for the model command.
* Defaults to null, meaning using 'framework/cli/views/shell/model'.
* If you set this path and some views are missing in the directory,
* the default views will be used.
*/
public $templatePath;
/**
* @var string the directory that contains test fixtures.
* Defaults to null, meaning using 'protected/tests/fixtures'.
* If this is false, it means fixture file should NOT be generated.
*/
public $fixturePath;
/**
* @var string the directory that contains unit test classes.
* Defaults to null, meaning using 'protected/tests/unit'.
* If this is false, it means unit test file should NOT be generated.
*/
public $unitTestPath;
private $_schema;
private $_relations; // where we keep table relations
private $_tables;
private $_classes;
public function getHelp()
{
return <<<EOD
USAGE
model <class-name> [table-name]
DESCRIPTION
This command generates a model class with the specified class name.
PARAMETERS
* class-name: required, model class name. By default, the generated
model class file will be placed under the directory aliased as
'application.models'. To override this default, specify the class
name in terms of a path alias, e.g., 'application.somewhere.ClassName'.
If the model class belongs to a module, it should be specified
as 'ModuleID.models.ClassName'.
If the class name ends with '*', then a model class will be generated
for EVERY table in the database.
If the class name contains a regular expression deliminated by slashes,
then a model class will be generated for those tables whose name
matches the regular expression. If the regular expression contains
sub-patterns, the first sub-pattern will be used to generate the model
class name.
* table-name: optional, the associated database table name. If not given,
it is assumed to be the model class name.
Note, when the class name ends with '*', this parameter will be
ignored.
EXAMPLES
* Generates the Post model:
model Post
* Generates the Post model which is associated with table 'posts':
model Post posts
* Generates the Post model which should belong to module 'admin':
model admin.models.Post
* Generates a model class for every table in the current database:
model *
* Same as above, but the model class files should be generated
under 'protected/models2':
model application.models2.*
* Generates a model class for every table whose name is prefixed
with 'tbl_' in the current database. The model class will not
contain the table prefix.
model /^tbl_(.*)$/
* Same as above, but the model class files should be generated
under 'protected/models2':
model application.models2./^tbl_(.*)$/
EOD;
}
/**
* Checks if the given table is a "many to many" helper table.
* Their PK has 2 fields, and both of those fields are also FK to other separate tables.
* @param CDbTableSchema $table table to inspect
* @return boolean true if table matches description of helper table.
*/
protected function isRelationTable($table)
{
$pk=$table->primaryKey;
return (count($pk) === 2 // we want 2 columns
&& isset($table->foreignKeys[$pk[0]]) // pk column 1 is also a foreign key
&& isset($table->foreignKeys[$pk[1]]) // pk column 2 is also a foreign key
&& $table->foreignKeys[$pk[0]][0] !== $table->foreignKeys[$pk[1]][0]); // and the foreign keys point different tables
}
/**
* Generate code to put in ActiveRecord class's relations() function.
* @return array indexed by table names, each entry contains array of php code to go in appropriate ActiveRecord class.
* Empty array is returned if database couldn't be connected.
*/
protected function generateRelations()
{
$this->_relations=array();
$this->_classes=array();
foreach($this->_schema->getTables() as $table)
{
$tableName=$table->name;
if ($this->isRelationTable($table))
{
$pks=$table->primaryKey;
$fks=$table->foreignKeys;
$table0=$fks[$pks[1]][0];
$table1=$fks[$pks[0]][0];
$className0=$this->getClassName($table0);
$className1=$this->getClassName($table1);
$unprefixedTableName=$this->removePrefix($tableName,true);
$relationName=$this->generateRelationName($table0, $table1, true);
$this->_relations[$className0][$relationName]="array(self::MANY_MANY, '$className1', '$unprefixedTableName($pks[0], $pks[1])')";
$relationName=$this->generateRelationName($table1, $table0, true);
$this->_relations[$className1][$relationName]="array(self::MANY_MANY, '$className0', '$unprefixedTableName($pks[0], $pks[1])')";
}
else
{
$this->_classes[$tableName]=$className=$this->getClassName($tableName);
foreach ($table->foreignKeys as $fkName => $fkEntry)
{
// Put table and key name in variables for easier reading
$refTable=$fkEntry[0]; // Table name that current fk references to
$refKey=$fkEntry[1]; // Key in that table being referenced
$refClassName=$this->getClassName($refTable);
// Add relation for this table
$relationName=$this->generateRelationName($tableName, $fkName, false);
$this->_relations[$className][$relationName]="array(self::BELONGS_TO, '$refClassName', '$fkName')";
// Add relation for the referenced table
$relationType=$table->primaryKey === $fkName ? 'HAS_ONE' : 'HAS_MANY';
$relationName=$this->generateRelationName($refTable, $this->removePrefix($tableName), $relationType==='HAS_MANY');
$this->_relations[$refClassName][$relationName]="array(self::$relationType, '$className', '$fkName')";
}
}
}
}
protected function getClassName($tableName)
{
return isset($this->_tables[$tableName]) ? $this->_tables[$tableName] : $this->generateClassName($tableName);
}
/**
* Generates model class name based on a table name
* @param string $tableName the table name
* @return string the generated model class name
*/
protected function generateClassName($tableName)
{
return str_replace(' ','',
ucwords(
trim(
strtolower(
str_replace(array('-','_'),' ',
preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $tableName))))));
}
/**
* Generates the mapping table between table names and class names.
* @param CDbSchema $schema the database schema
* @param string $pattern a regular expression that may be used to filter table names
*/
protected function generateClassNames($schema,$pattern=null)
{
$this->_tables=array();
foreach($schema->getTableNames() as $name)
{
if($pattern===null)
$this->_tables[$name]=$this->generateClassName($this->removePrefix($name));
elseif(preg_match($pattern,$name,$matches))
{
if(count($matches)>1 && !empty($matches[1]))
$className=$this->generateClassName($matches[1]);
else
$className=$this->generateClassName($matches[0]);
$this->_tables[$name]=empty($className) ? $name : $className;
}
}
}
/**
* Generate a name for use as a relation name (inside relations() function in a model).
* @param string $tableName the name of the table to hold the relation
* @param string $fkName the foreign key name
* @param boolean $multiple whether the relation would contain multiple objects
* @return string the generated relation name
*/
protected function generateRelationName($tableName, $fkName, $multiple)
{
if(strcasecmp(substr($fkName,-2),'id')===0 && strcasecmp($fkName,'id'))
$relationName=rtrim(substr($fkName, 0, -2),'_');
else
$relationName=$fkName;
$relationName[0]=strtolower($relationName);
$rawName=$relationName;
if($multiple)
$relationName=$this->pluralize($relationName);
$table=$this->_schema->getTable($tableName);
$i=0;
while(isset($table->columns[$relationName]))
$relationName=$rawName.($i++);
return $relationName;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: model class name is required.\n";
echo $this->getHelp();
return 1;
}
$className=$args[0];
if(($db=Yii::app()->getDb())===null)
{
echo "Error: an active 'db' connection is required.\n";
echo "If you already added 'db' component in application configuration,\n";
echo "please quit and re-enter the yiic shell.\n";
return 1;
}
$db->active=true;
$this->_schema=$db->schema;
if(!preg_match('/^[\w\.\-\*]*(.*?)$/',$className,$matches))
{
echo "Error: model class name is invalid.\n";
return 1;
}
if(empty($matches[1])) // without regular expression
{
$this->generateClassNames($this->_schema);
if(($pos=strrpos($className,'.'))===false)
$basePath=Yii::getPathOfAlias('application.models');
else
{
$basePath=Yii::getPathOfAlias(substr($className,0,$pos));
$className=substr($className,$pos+1);
}
if($className==='*') // generate all models
$this->generateRelations();
else
{
$tableName=isset($args[1])?$args[1]:$className;
$tableName=$this->addPrefix($tableName);
$this->_tables[$tableName]=$className;
$this->generateRelations();
$this->_classes=array($tableName=>$className);
}
}
else // with regular expression
{
$pattern=$matches[1];
$pos=strrpos($className,$pattern);
if($pos>0) // only regexp is given
$basePath=Yii::getPathOfAlias(rtrim(substr($className,0,$pos),'.'));
else
$basePath=Yii::getPathOfAlias('application.models');
$this->generateClassNames($this->_schema,$pattern);
$classes=$this->_tables;
$this->generateRelations();
$this->_classes=$classes;
}
if(count($this->_classes)>1)
{
$entries=array();
$count=0;
foreach($this->_classes as $tableName=>$className)
$entries[]=++$count.". $className ($tableName)";
echo "The following model classes (tables) match your criteria:\n";
echo implode("\n",$entries)."\n\n";
if(!$this->confirm("Do you want to generate the above classes?"))
return;
}
$templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/model':$this->templatePath;
$fixturePath=$this->fixturePath===null?Yii::getPathOfAlias('application.tests.fixtures'):$this->fixturePath;
$unitTestPath=$this->unitTestPath===null?Yii::getPathOfAlias('application.tests.unit'):$this->unitTestPath;
$list=array();
$files=array();
foreach ($this->_classes as $tableName=>$className)
{
$files[$className]=$classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
$list['models/'.$className.'.php']=array(
'source'=>$templatePath.DIRECTORY_SEPARATOR.'model.php',
'target'=>$classFile,
'callback'=>array($this,'generateModel'),
'params'=>array($className,$tableName),
);
if($fixturePath!==false)
{
$list['fixtures/'.$tableName.'.php']=array(
'source'=>$templatePath.DIRECTORY_SEPARATOR.'fixture.php',
'target'=>$fixturePath.DIRECTORY_SEPARATOR.$tableName.'.php',
'callback'=>array($this,'generateFixture'),
'params'=>$this->_schema->getTable($tableName),
);
}
if($unitTestPath!==false)
{
$fixtureName=$this->pluralize($className);
$fixtureName[0]=strtolower($fixtureName);
$list['unit/'.$className.'Test.php']=array(
'source'=>$templatePath.DIRECTORY_SEPARATOR.'test.php',
'target'=>$unitTestPath.DIRECTORY_SEPARATOR.$className.'Test.php',
'callback'=>array($this,'generateTest'),
'params'=>array($className,$fixtureName),
);
}
}
$this->copyFiles($list);
foreach($files as $className=>$file)
{
if(!class_exists($className,false))
include_once($file);
}
$classes=implode(", ", $this->_classes);
echo <<<EOD
The following model classes are successfully generated:
$classes
If you have a 'db' database connection, you can test these models now with:
\$model={$className}::model()->find();
print_r(\$model);
EOD;
}
public function generateModel($source,$params)
{
list($className,$tableName)=$params;
$rules=array();
$labels=array();
$relations=array();
if(($table=$this->_schema->getTable($tableName))!==null)
{
$required=array();
$integers=array();
$numerical=array();
$length=array();
$safe=array();
foreach($table->columns as $column)
{
$label=ucwords(trim(strtolower(str_replace(array('-','_'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $column->name)))));
$label=preg_replace('/\s+/',' ',$label);
if(strcasecmp(substr($label,-3),' id')===0)
$label=substr($label,0,-3);
$labels[$column->name]=$label;
if($column->isPrimaryKey && $table->sequenceName!==null)
continue;
$r=!$column->allowNull && $column->defaultValue===null;
if($r)
$required[]=$column->name;
if($column->type==='integer')
$integers[]=$column->name;
elseif($column->type==='double')
$numerical[]=$column->name;
elseif($column->type==='string' && $column->size>0)
$length[$column->size][]=$column->name;
elseif(!$column->isPrimaryKey && !$r)
$safe[]=$column->name;
}
if($required!==array())
$rules[]="array('".implode(', ',$required)."', 'required')";
if($integers!==array())
$rules[]="array('".implode(', ',$integers)."', 'numerical', 'integerOnly'=>true)";
if($numerical!==array())
$rules[]="array('".implode(', ',$numerical)."', 'numerical')";
if($length!==array())
{
foreach($length as $len=>$cols)
$rules[]="array('".implode(', ',$cols)."', 'length', 'max'=>$len)";
}
if($safe!==array())
$rules[]="array('".implode(', ',$safe)."', 'safe')";
if(isset($this->_relations[$className]) && is_array($this->_relations[$className]))
$relations=$this->_relations[$className];
}
else
echo "Warning: the table '$tableName' does not exist in the database.\n";
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/model/'.basename($source);
return $this->renderFile($source,array(
'className'=>$className,
'tableName'=>$this->removePrefix($tableName,true),
'columns'=>isset($table) ? $table->columns : array(),
'rules'=>$rules,
'labels'=>$labels,
'relations'=>$relations,
),true);
}
public function generateFixture($source,$table)
{
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/model/'.basename($source);
return $this->renderFile($source, array(
'table'=>$table,
),true);
}
public function generateTest($source,$params)
{
list($className,$fixtureName)=$params;
if(!is_file($source)) // fall back to default ones
$source=YII_PATH.'/cli/views/shell/model/'.basename($source);
return $this->renderFile($source, array(
'className'=>$className,
'fixtureName'=>$fixtureName,
),true);
}
protected function removePrefix($tableName,$addBrackets=false)
{
$tablePrefix=Yii::app()->getDb()->tablePrefix;
if($tablePrefix!='' && !strncmp($tableName,$tablePrefix,strlen($tablePrefix)))
{
$tableName=substr($tableName,strlen($tablePrefix));
if($addBrackets)
$tableName='{{'.$tableName.'}}';
}
return $tableName;
}
protected function addPrefix($tableName)
{
$tablePrefix=Yii::app()->getDb()->tablePrefix;
if($tablePrefix!='' && strncmp($tableName,$tablePrefix,strlen($tablePrefix)))
$tableName=$tablePrefix.$tableName;
return $tableName;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* ModuleCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @version $Id: ModuleCommand.php 433 2008-12-30 22:59:17Z qiang.xue $
*/
/**
* ModuleCommand generates a controller class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: ModuleCommand.php 433 2008-12-30 22:59:17Z qiang.xue $
* @package system.cli.commands.shell
*/
class ModuleCommand extends CConsoleCommand
{
/**
* @var string the directory that contains templates for the module command.
* Defaults to null, meaning using 'framework/cli/views/shell/module'.
* If you set this path and some views are missing in the directory,
* the default views will be used.
*/
public $templatePath;
public function getHelp()
{
return <<<EOD
USAGE
module <module-ID>
DESCRIPTION
This command generates an application module.
PARAMETERS
* module-ID: required, module ID. It is case-sensitive.
EOD;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: module ID is required.\n";
echo $this->getHelp();
return 1;
}
$moduleID=$args[0];
$moduleClass=ucfirst($moduleID).'Module';
$modulePath=Yii::app()->getModulePath().DIRECTORY_SEPARATOR.$moduleID;
$sourceDir=$this->templatePath===null?YII_PATH.'/cli/views/shell/module':$this->templatePath;
$list=$this->buildFileList($sourceDir,$modulePath);
$list['module.php']['target']=$modulePath.DIRECTORY_SEPARATOR.$moduleClass.'.php';
$list['module.php']['callback']=array($this,'generateModuleClass');
$list['module.php']['params']=array(
'moduleClass'=>$moduleClass,
'moduleID'=>$moduleID,
);
$list[$moduleClass.'.php']=$list['module.php'];
unset($list['module.php']);
$this->copyFiles($list);
echo <<<EOD
Module '{$moduleID}' has been created under the following folder:
$modulePath
You may access it in the browser using the following URL:
http://hostname/path/to/index.php?r=$moduleID
Note, the module needs to be installed first by adding '{$moduleID}'
to the 'modules' property in the application configuration.
EOD;
}
public function generateModuleClass($source,$params)
{
return $this->renderFile($source,$params,true);
}
}