diff options
Diffstat (limited to 'framework/cli/commands/shell')
| -rw-r--r-- | framework/cli/commands/shell/ControllerCommand.php | 176 | ||||
| -rw-r--r-- | framework/cli/commands/shell/CrudCommand.php | 327 | ||||
| -rw-r--r-- | framework/cli/commands/shell/FormCommand.php | 123 | ||||
| -rw-r--r-- | framework/cli/commands/shell/HelpCommand.php | 78 | ||||
| -rw-r--r-- | framework/cli/commands/shell/ModelCommand.php | 488 | ||||
| -rw-r--r-- | framework/cli/commands/shell/ModuleCommand.php | 92 |
6 files changed, 1284 insertions, 0 deletions
diff --git a/framework/cli/commands/shell/ControllerCommand.php b/framework/cli/commands/shell/ControllerCommand.php new file mode 100644 index 0000000..f7447b6 --- /dev/null +++ b/framework/cli/commands/shell/ControllerCommand.php @@ -0,0 +1,176 @@ +<?php +/** + * ControllerCommand class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: ControllerCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $ + */ + +/** + * ControllerCommand generates a controller class. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: ControllerCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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 command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + { + echo "Error: controller name is required.\n"; + echo $this->getHelp(); + return; + } + + $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); + } +}
\ No newline at end of file diff --git a/framework/cli/commands/shell/CrudCommand.php b/framework/cli/commands/shell/CrudCommand.php new file mode 100644 index 0000000..5932dea --- /dev/null +++ b/framework/cli/commands/shell/CrudCommand.php @@ -0,0 +1,327 @@ +<?php +/** + * CrudCommand class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: CrudCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $ + */ + +/** + * CrudCommand generates code implementing CRUD operations. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CrudCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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 command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + { + echo "Error: data model class is required.\n"; + echo $this->getHelp(); + return; + } + $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()))); + else if(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}')"; + else if(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}')"; + else if(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))))); + } +} diff --git a/framework/cli/commands/shell/FormCommand.php b/framework/cli/commands/shell/FormCommand.php new file mode 100644 index 0000000..c3f064f --- /dev/null +++ b/framework/cli/commands/shell/FormCommand.php @@ -0,0 +1,123 @@ +<?php +/** + * FormCommand class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: FormCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $ + */ + +/** + * FormCommand generates a form view based on a specified model. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: FormCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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 command line parameters specific for this command + */ + 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; + } + $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))),'-'); + } +}
\ No newline at end of file diff --git a/framework/cli/commands/shell/HelpCommand.php b/framework/cli/commands/shell/HelpCommand.php new file mode 100644 index 0000000..8007373 --- /dev/null +++ b/framework/cli/commands/shell/HelpCommand.php @@ -0,0 +1,78 @@ +<?php +/** + * HelpCommand class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: HelpCommand.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + */ + +/** + * HelpCommand displays help information for commands under yiic shell. + * + * @property string $help The command description. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: HelpCommand.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.cli.commands.shell + * @since 1.0 + */ +class HelpCommand extends CConsoleCommand +{ + /** + * Execute the action. + * @param array command line parameters specific for this command + */ + 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(); + } + + /** + * 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; + } +}
\ No newline at end of file diff --git a/framework/cli/commands/shell/ModelCommand.php b/framework/cli/commands/shell/ModelCommand.php new file mode 100644 index 0000000..cfd8c21 --- /dev/null +++ b/framework/cli/commands/shell/ModelCommand.php @@ -0,0 +1,488 @@ +<?php +/** + * ModelCommand class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: ModelCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $ + */ + +/** + * ModelCommand generates a model class. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: ModelCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $ + * @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 to inspect + * @return boolean true if table matches description of helpter 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 foriegn 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 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 the database schema + * @param string 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)); + else if(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 the name of the table to hold the relation + * @param string the foreign key name + * @param boolean whether the relation would contain multiple objects + */ + 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 command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + { + echo "Error: model class name is required.\n"; + echo $this->getHelp(); + return; + } + $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; + } + + $db->active=true; + $this->_schema=$db->schema; + + if(!preg_match('/^[\w\.\-\*]*(.*?)$/',$className,$matches)) + { + echo "Error: model class name is invalid.\n"; + return; + } + + 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; + else if($column->type==='double') + $numerical[]=$column->name; + else if($column->type==='string' && $column->size>0) + $length[$column->size][]=$column->name; + else if(!$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; + } +}
\ No newline at end of file diff --git a/framework/cli/commands/shell/ModuleCommand.php b/framework/cli/commands/shell/ModuleCommand.php new file mode 100644 index 0000000..51a2158 --- /dev/null +++ b/framework/cli/commands/shell/ModuleCommand.php @@ -0,0 +1,92 @@ +<?php +/** + * ModuleCommand class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 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 command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + { + echo "Error: module ID is required.\n"; + echo $this->getHelp(); + return; + } + + $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); + } +}
\ No newline at end of file |
