Browse Source

working on console app.

tags/2.0.0-beta
Qiang Xue 13 years ago
parent
commit
5f3e24d81f
  1. 107
      framework/base/Application.php
  2. 49
      framework/base/Controller.php
  3. 166
      framework/base/Module.php
  4. 196
      framework/base/SecurityManager.php
  5. 242
      framework/console/Application.php
  6. 251
      framework/console/Command.php
  7. 138
      framework/console/CommandRunner.php
  8. 129
      framework/console/commands/AppController.php
  9. 0
      framework/console/commands/HelpController.php
  10. 223
      framework/console/commands/MessageController.php
  11. 561
      framework/console/commands/MigrateController.php
  12. 148
      framework/console/commands/ShellController.php
  13. 5
      framework/yiic
  14. 25
      framework/yiic.php

107
framework/base/Application.php

@ -73,7 +73,7 @@ use yii\base\Exception;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Application extends Module abstract class Application extends Module
{ {
/** /**
* @var string the application name. Defaults to 'My Application'. * @var string the application name. Defaults to 'My Application'.
@ -95,7 +95,12 @@ class Application extends Module
* to ensure errors and exceptions can be handled nicely. * to ensure errors and exceptions can be handled nicely.
*/ */
public $preload = array('errorHandler'); public $preload = array('errorHandler');
/**
* @var Controller the currently active controller instance
*/
public $controller;
// todo
public $localeDataPath = '@yii/i18n/data'; public $localeDataPath = '@yii/i18n/data';
private $_runtimePath; private $_runtimePath;
@ -118,9 +123,8 @@ class Application extends Module
} }
/** /**
* Initializes the module. * Initializes the application by loading components declared in [[preload]].
* This method is called after the module is created and initialized with property values * If you override this method, make sure the parent implementation is invoked.
* given in configuration.
*/ */
public function init() public function init()
{ {
@ -129,16 +133,10 @@ class Application extends Module
/** /**
* Runs the application. * Runs the application.
* This method loads static application components. Derived classes usually overrides this * This is the main entrance of an application. Derived classes must implement this method.
* method to do more application-specific tasks. * @return integer the exit status (0 means normal, non-zero values mean abnormal)
* Remember to call the parent implementation so that static application components are loaded.
*/ */
public function run() abstract public function run();
{
$this->beforeRequest();
$this->processRequest();
$this->afterRequest();
}
/** /**
* Terminates the application. * Terminates the application.
@ -167,20 +165,33 @@ class Application extends Module
} }
/** /**
* Processes the request. * Raises the [[afterRequest]] event right AFTER the application processes the request.
* This is the place where the actual request processing work is done.
* Derived classes should override this method.
*/ */
public function processRequest() public function afterRequest()
{ {
$this->trigger('afterRequest');
} }
/** /**
* Raises the [[afterRequest]] event right AFTER the application processes the request. * Processes the request.
* The request is represented in terms of a controller route and action parameters.
* @param string $route the route of the request. It may contain module ID, controller ID, and/or action ID.
* @param array $params parameters to be bound to the controller action.
* @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal)
* @throws Exception if the route cannot be resolved into a controller
*/ */
public function afterRequest() public function processRequest($route, $params = array())
{ {
$this->trigger('afterRequest'); $result = $this->parseRoute($route);
if ($result === null) {
throw new Exception(\Yii::t('yii', 'Unable to resolve the request.'));
}
list($controller, $action) = $result;
$oldController = $this->controller;
$this->controller = $controller;
$status = $controller->run($action, $params);
$this->controller = $oldController;
return $status;
} }
/** /**
@ -189,12 +200,10 @@ class Application extends Module
*/ */
public function getRuntimePath() public function getRuntimePath()
{ {
if ($this->_runtimePath !== null) { if ($this->_runtimePath === null) {
return $this->_runtimePath;
} else {
$this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime'); $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
return $this->_runtimePath;
} }
return $this->_runtimePath;
} }
/** /**
@ -205,7 +214,7 @@ class Application extends Module
public function setRuntimePath($path) public function setRuntimePath($path)
{ {
if (!is_dir($path) || !is_writable($path)) { if (!is_dir($path) || !is_writable($path)) {
throw new \yii\base\Exception("Application runtime path \"$path\" is invalid. Please make sure it is a directory writable by the Web server process."); throw new Exception("Application runtime path \"$path\" is invalid. Please make sure it is a directory writable by the Web server process.");
} }
$this->_runtimePath = $path; $this->_runtimePath = $path;
} }
@ -226,7 +235,7 @@ class Application extends Module
* By default, [[language]] and [[sourceLanguage]] are the same. * By default, [[language]] and [[sourceLanguage]] are the same.
* Do not set this property unless your application needs to support multiple languages. * Do not set this property unless your application needs to support multiple languages.
* @param string $language the user language (e.g. 'en_US', 'zh_CN'). * @param string $language the user language (e.g. 'en_US', 'zh_CN').
* If it is null, the {@link sourceLanguage} will be used. * If it is null, the [[sourceLanguage]] will be used.
*/ */
public function setLanguage($language) public function setLanguage($language)
{ {
@ -256,40 +265,6 @@ class Application extends Module
} }
/** /**
* Returns the localized version of a specified file.
*
* The searching is based on the specified language code. In particular,
* a file with the same name will be looked for under the subdirectory
* named as the locale ID. For example, given the file "path/to/view.php"
* and locale ID "zh_cn", the localized file will be looked for as
* "path/to/zh_cn/view.php". If the file is not found, the original file
* will be returned.
*
* For consistency, it is recommended that the locale ID is given
* in lower case and in the format of LanguageID_RegionID (e.g. "en_us").
*
* @param string $srcFile the original file
* @param string $srcLanguage the language that the original file is in. If null, the application {@link sourceLanguage source language} is used.
* @param string $language the desired language that the file should be localized to. If null, the {@link getLanguage application language} will be used.
* @return string the matching localized file. The original file is returned if no localized version is found
* or if source language is the same as the desired language.
*/
public function findLocalizedFile($srcFile, $srcLanguage = null, $language = null)
{
if ($srcLanguage === null) {
$srcLanguage = $this->sourceLanguage;
}
if ($language === null) {
$language = $this->getLanguage();
}
if ($language === $srcLanguage) {
return $srcFile;
}
$desiredFile = dirname($srcFile) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($srcFile);
return is_file($desiredFile) ? $desiredFile : $srcFile;
}
/**
* Returns the locale instance. * Returns the locale instance.
* @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used. * @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used.
* @return CLocale the locale instance * @return CLocale the locale instance
@ -346,15 +321,6 @@ class Application extends Module
} }
/** /**
* Returns the state persister component.
* @return CStatePersister the state persister application component.
*/
public function getStatePersister()
{
return $this->getComponent('statePersister');
}
/**
* Returns the cache component. * Returns the cache component.
* @return \yii\caching\Cache the cache application component. Null if the component is not enabled. * @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
*/ */
@ -420,9 +386,6 @@ class Application extends Module
'securityManager' => array( 'securityManager' => array(
'class' => 'yii\base\SecurityManager', 'class' => 'yii\base\SecurityManager',
), ),
'statePersister' => array(
'class' => 'yii\base\StatePersister',
),
)); ));
} }
} }

49
framework/base/Controller.php

@ -27,7 +27,7 @@ namespace yii\base;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
abstract class Controller extends Component implements Initable class Controller extends Component implements Initable
{ {
/** /**
* @var string ID of this controller * @var string ID of this controller
@ -98,36 +98,33 @@ abstract class Controller extends Component implements Initable
} }
/** /**
* Creates an action with the specified ID and runs it. * Runs the controller with the specified action and parameters.
* If the action does not exist, [[missingAction()]] will be invoked. * @param Action|string $action the action to be executed. This can be either an action object
* @param string $actionID action ID * or the ID of the action.
* @param array $params the parameters to be passed to the action.
* If null, the result of [[getActionParams()]] will be used as action parameters.
* Note that the parameters must be name-value pairs with the names corresponding to
* the parameter names as declared by the action.
* @return integer the exit status of the action. 0 means normal, other values mean abnormal. * @return integer the exit status of the action. 0 means normal, other values mean abnormal.
* @see createAction
* @see runAction
* @see missingAction * @see missingAction
* @see createAction
*/ */
public function run($actionID) public function run($action, $params = null)
{ {
if (($action = $this->createAction($actionID)) !== null) { if (is_string($action)) {
return $this->runAction($action); if (($a = $this->createAction($action)) !== null) {
} else { $action = $a;
$this->missingAction($actionID); } else {
return 1; $this->missingAction($action);
return 1;
}
} }
}
/**
* Runs the action.
* @param Action $action action to run
* @return integer the exit status of the action. 0 means normal, other values mean abnormal.
*/
public function runAction($action)
{
$priorAction = $this->action; $priorAction = $this->action;
$this->action = $action; $this->action = $action;
$exitStatus = 1; $exitStatus = 1;
if ($this->authorize($action)) { if ($this->authorize($action)) {
$params = $action->normalizeParams($this->getActionParams()); $params = $action->normalizeParams($params === null ? $this->getActionParams() : $params);
if ($params !== false) { if ($params !== false) {
if ($this->beforeAction($action)) { if ($this->beforeAction($action)) {
$exitStatus = (int)call_user_func_array(array($action, 'run'), $params); $exitStatus = (int)call_user_func_array(array($action, 'run'), $params);
@ -223,17 +220,21 @@ abstract class Controller extends Component implements Initable
* @param string $route the route of the new controller action. This can be an action ID, or a complete route * @param string $route the route of the new controller action. This can be an action ID, or a complete route
* with module ID (optional in the current module), controller ID and action ID. If the former, * with module ID (optional in the current module), controller ID and action ID. If the former,
* the action is assumed to be located within the current controller. * the action is assumed to be located within the current controller.
* @param array $params the parameters to be passed to the action.
* If null, the result of [[getActionParams()]] will be used as action parameters.
* Note that the parameters must be name-value pairs with the names corresponding to
* the parameter names as declared by the action.
* @param boolean $exit whether to end the application after this call. Defaults to true. * @param boolean $exit whether to end the application after this call. Defaults to true.
*/ */
public function forward($route, $exit = true) public function forward($route, $params = array(), $exit = true)
{ {
if (strpos($route, '/') === false) { if (strpos($route, '/') === false) {
$status = $this->run($route); $status = $this->run($route, $params);
} else { } else {
if ($route[0] !== '/' && !$this->module instanceof Application) { if ($route[0] !== '/' && !$this->module instanceof Application) {
$route = '/' . $this->module->getUniqueId() . '/' . $route; $route = '/' . $this->module->getUniqueId() . '/' . $route;
} }
$status = \Yii::$application->runController($route); $status = \Yii::$application->dispatch($route, $params);
} }
if ($exit) { if ($exit) {
\Yii::$application->end($status); \Yii::$application->end($status);

166
framework/base/Module.php

@ -43,10 +43,54 @@ abstract class Module extends Component implements Initable
* @var Module the parent module of this module. Null if this module does not have a parent. * @var Module the parent module of this module. Null if this module does not have a parent.
*/ */
public $module; public $module;
/**
private $_basePath; * @var array mapping from controller ID to controller configurations.
private $_modules = array(); * Each name-value pair specifies the configuration of a single controller.
private $_components = array(); * A controller configuration can be either a string or an array.
* If the former, the string should be the class name or path alias of the controller.
* If the latter, the array must contain a 'class' element which specifies
* the controller's class name or path alias, and the rest of the name-value pairs
* in the array are used to initialize the corresponding controller properties. For example,
*
* ~~~
* array(
* 'account' => '@application/controllers/UserController',
* 'article' => array(
* 'class' => '@application/controllers/PostController',
* 'pageTitle' => 'something new',
* ),
* )
* ~~~
*/
public $controllers = array();
/**
* @return string the default route of this module. Defaults to 'default'.
* The route may consist of child module ID, controller ID, and/or action ID.
* For example, `help`, `post/create`, `admin/post/create`.
* If action ID is not given, it will take the default value as specified in
* [[Controller::defaultAction]].
*/
public $defaultRoute = 'default';
/**
* @var string the root directory of the module.
* @see getBasePath
* @see setBasePath
*/
protected $_basePath;
/**
* @var string the directory containing controller classes in the module.
* @see getControllerPath
* @see setControllerPath
*/
protected $_controllerPath;
/**
* @var array child modules of this module
*/
protected $_modules = array();
/**
* @var array application components of this module
*/
protected $_components = array();
/** /**
* Constructor. * Constructor.
@ -118,7 +162,8 @@ abstract class Module extends Component implements Initable
/** /**
* Returns the root directory of the module. * Returns the root directory of the module.
* @return string the root directory of the module. Defaults to the directory containing the module class file. * It defaults to the directory containing the module class file.
* @return string the root directory of the module.
*/ */
public function getBasePath() public function getBasePath()
{ {
@ -132,7 +177,7 @@ abstract class Module extends Component implements Initable
/** /**
* Sets the root directory of the module. * Sets the root directory of the module.
* This method can only be invoked at the beginning of the constructor. * This method can only be invoked at the beginning of the constructor.
* @param string $path the root directory of the module. * @param string $path the root directory of the module. This can be either a directory name or a path alias.
* @throws Exception if the directory does not exist. * @throws Exception if the directory does not exist.
*/ */
public function setBasePath($path) public function setBasePath($path)
@ -146,6 +191,36 @@ abstract class Module extends Component implements Initable
} }
/** /**
* Returns the directory that contains the controller classes.
* Defaults to "[[basePath]]/controllers".
* @return string the directory that contains the controller classes.
*/
public function getControllerPath()
{
if ($this->_controllerPath !== null) {
return $this->_controllerPath;
} else {
return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
}
}
/**
* Sets the directory that contains the controller classes.
* @param string $path the directory that contains the controller classes.
* This can be either a directory name or a path alias.
* @throws Exception if the directory is invalid
*/
public function setControllerPath($path)
{
$p = \Yii::getAlias($path);
if ($p === false || !is_dir($p)) {
throw new Exception('Invalid controller path: ' . $path);
} else {
$this->_controllerPath = realpath($p);
}
}
/**
* Imports the specified path aliases. * Imports the specified path aliases.
* This method is provided so that you can import a set of path aliases when configuring a module. * This method is provided so that you can import a set of path aliases when configuring a module.
* The path aliases will be imported by calling [[\Yii::import()]]. * The path aliases will be imported by calling [[\Yii::import()]].
@ -404,4 +479,83 @@ abstract class Module extends Component implements Initable
$this->getComponent($id); $this->getComponent($id);
} }
} }
/**
* Parses a given route into controller and action ID.
* The parsing process follows the following algorithm:
*
* 1. Get the first segment in route
* 2. If the segment matches
* - an ID in [[controllers]], create a controller instance using the corresponding configuration,
* and return the controller with the rest part of the route;
* - a controller class under [[controllerPath]], create the controller instance, and return it
* with the rest part of the route;
* - an ID in [[modules]], let the corresponding module to parse the rest part of the route.
*
* @param string $route the route which may consist module ID, controller ID and/or action ID (e.g. `post/create`)
* @return array|null the controller instance and action ID. Null if the route cannot be resolved.
*/
public function parseRoute($route)
{
if (($route = trim($route, '/')) === '') {
$route = $this->defaultRoute;
}
if (($pos = strpos($route, '/')) !== false) {
$id = substr($route, 0, $pos);
$route = (string)substr($route, $pos + 1);
} else {
$id = $route;
$route = '';
}
$controller = $this->createController($id);
if ($controller !== null) {
return array($controller, $route);
}
if (($module = $this->getModule($id)) !== null) {
return $module->parseRoute($route);
}
return null;
}
/**
* Creates a controller instance according to the specified controller ID.
* For security reasons, the controller ID must start with a lower-case letter
* and consist of word characters only.
*
* This method will first match the controller ID in [[controllers]]. If found,
* it will create an instance of controller using the corresponding configuration.
* If not found, it will look for a controller class named `XyzController` under
* the [[controllerPath]] directory, where `xyz` is the controller ID with the first
* letter in upper-case.
* @param string $id the controller ID
* @return Controller|null the created controller instance. Null if the controller ID is invalid.
*/
public function createController($id)
{
// Controller IDs must start with a lower-case letter and consist of word characters only
if (!preg_match('/^[a-z][a-zA-Z0-9_]*$/', $id)) {
return null;
}
if (isset($this->controllers[$id])) {
return \Yii::createObject($this->controllers[$id], $id, $this);
}
$className = ucfirst($id) . 'Controller';
$classFile = $this->getControllerPath() . DIRECTORY_SEPARATOR . $className . '.php';
if (is_file($classFile)) {
if (!class_exists($className, false)) {
require($classFile);
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
return $className::newInstance(array(), $id, $this);
}
}
return null;
}
} }

196
framework/base/SecurityManager.php

@ -1,21 +1,22 @@
<?php <?php
/** /**
* This file contains classes implementing security manager feature. * SecurityManager class file.
* *
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\base;
/** /**
* CSecurityManager provides private keys, hashing and encryption functions. * SecurityManager provides private keys, hashing and encryption functions.
* *
* CSecurityManager is used by Yii components and applications for security-related purpose. * SecurityManager is used by Yii components and applications for security-related purpose.
* For example, it is used in cookie validation feature to prevent cookie data * For example, it is used in cookie validation feature to prevent cookie data
* from being tampered. * from being tampered.
* *
* CSecurityManager is mainly used to protect data from being tampered and viewed. * SecurityManager is mainly used to protect data from being tampered and viewed.
* It can generate HMAC and encrypt the data. The private key used to generate HMAC * It can generate HMAC and encrypt the data. The private key used to generate HMAC
* is set by {@link setValidationKey ValidationKey}. The key used to encrypt data is * is set by {@link setValidationKey ValidationKey}. The key used to encrypt data is
* specified by {@link setEncryptionKey EncryptionKey}. If the above keys are not * specified by {@link setEncryptionKey EncryptionKey}. If the above keys are not
@ -30,7 +31,7 @@
* respectively, which uses 3DES encryption algorithm. Note, the PHP Mcrypt * respectively, which uses 3DES encryption algorithm. Note, the PHP Mcrypt
* extension must be installed and loaded. * extension must be installed and loaded.
* *
* CSecurityManager is a core application component that can be accessed via * SecurityManager is a core application component that can be accessed via
* {@link CApplication::getSecurityManager()}. * {@link CApplication::getSecurityManager()}.
* *
* @property string $validationKey The private key used to generate HMAC. * @property string $validationKey The private key used to generate HMAC.
@ -44,10 +45,10 @@
* @package system.base * @package system.base
* @since 1.0 * @since 1.0
*/ */
class CSecurityManager extends CApplicationComponent class SecurityManager extends ApplicationComponent
{ {
const STATE_VALIDATION_KEY='Yii.CSecurityManager.validationkey'; const STATE_VALIDATION_KEY = 'Yii.SecurityManager.validationkey';
const STATE_ENCRYPTION_KEY='Yii.CSecurityManager.encryptionkey'; const STATE_ENCRYPTION_KEY = 'Yii.SecurityManager.encryptionkey';
/** /**
* @var string the name of the hashing algorithm to be used by {@link computeHMAC}. * @var string the name of the hashing algorithm to be used by {@link computeHMAC}.
@ -57,7 +58,7 @@ class CSecurityManager extends CApplicationComponent
* Defaults to 'sha1', meaning using SHA1 hash algorithm. * Defaults to 'sha1', meaning using SHA1 hash algorithm.
* @since 1.1.3 * @since 1.1.3
*/ */
public $hashAlgorithm='sha1'; public $hashAlgorithm = 'sha1';
/** /**
* @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}. * @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}.
* This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}. * This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}.
@ -68,7 +69,7 @@ class CSecurityManager extends CApplicationComponent
* Defaults to 'des', meaning using DES crypt algorithm. * Defaults to 'des', meaning using DES crypt algorithm.
* @since 1.1.3 * @since 1.1.3
*/ */
public $cryptAlgorithm='des'; public $cryptAlgorithm = 'des';
private $_validationKey; private $_validationKey;
private $_encryptionKey; private $_encryptionKey;
@ -77,7 +78,7 @@ class CSecurityManager extends CApplicationComponent
public function init() public function init()
{ {
parent::init(); parent::init();
$this->_mbstring=extension_loaded('mbstring'); $this->_mbstring = extension_loaded('mbstring');
} }
/** /**
@ -85,7 +86,7 @@ class CSecurityManager extends CApplicationComponent
*/ */
protected function generateRandomKey() protected function generateRandomKey()
{ {
return sprintf('%08x%08x%08x%08x',mt_rand(),mt_rand(),mt_rand(),mt_rand()); return sprintf('%08x%08x%08x%08x', mt_rand(), mt_rand(), mt_rand(), mt_rand());
} }
/** /**
@ -94,17 +95,17 @@ class CSecurityManager extends CApplicationComponent
*/ */
public function getValidationKey() public function getValidationKey()
{ {
if($this->_validationKey!==null) if ($this->_validationKey !== null) {
return $this->_validationKey; return $this->_validationKey;
else }
{ else {
if(($key=Yii::app()->getGlobalState(self::STATE_VALIDATION_KEY))!==null) if (($key = Yii::app()->getGlobalState(self::STATE_VALIDATION_KEY)) !== null) {
$this->setValidationKey($key); $this->setValidationKey($key);
else }
{ else {
$key=$this->generateRandomKey(); $key = $this->generateRandomKey();
$this->setValidationKey($key); $this->setValidationKey($key);
Yii::app()->setGlobalState(self::STATE_VALIDATION_KEY,$key); Yii::app()->setGlobalState(self::STATE_VALIDATION_KEY, $key);
} }
return $this->_validationKey; return $this->_validationKey;
} }
@ -116,10 +117,12 @@ class CSecurityManager extends CApplicationComponent
*/ */
public function setValidationKey($value) public function setValidationKey($value)
{ {
if(!empty($value)) if (!empty($value)) {
$this->_validationKey=$value; $this->_validationKey = $value;
else }
throw new CException(Yii::t('yii','CSecurityManager.validationKey cannot be empty.')); else {
throw new CException(Yii::t('yii', 'SecurityManager.validationKey cannot be empty.'));
}
} }
/** /**
@ -128,17 +131,17 @@ class CSecurityManager extends CApplicationComponent
*/ */
public function getEncryptionKey() public function getEncryptionKey()
{ {
if($this->_encryptionKey!==null) if ($this->_encryptionKey !== null) {
return $this->_encryptionKey; return $this->_encryptionKey;
else }
{ else {
if(($key=Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY))!==null) if (($key = Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY)) !== null) {
$this->setEncryptionKey($key); $this->setEncryptionKey($key);
else }
{ else {
$key=$this->generateRandomKey(); $key = $this->generateRandomKey();
$this->setEncryptionKey($key); $this->setEncryptionKey($key);
Yii::app()->setGlobalState(self::STATE_ENCRYPTION_KEY,$key); Yii::app()->setGlobalState(self::STATE_ENCRYPTION_KEY, $key);
} }
return $this->_encryptionKey; return $this->_encryptionKey;
} }
@ -150,10 +153,12 @@ class CSecurityManager extends CApplicationComponent
*/ */
public function setEncryptionKey($value) public function setEncryptionKey($value)
{ {
if(!empty($value)) if (!empty($value)) {
$this->_encryptionKey=$value; $this->_encryptionKey = $value;
else }
throw new CException(Yii::t('yii','CSecurityManager.encryptionKey cannot be empty.')); else {
throw new CException(Yii::t('yii', 'SecurityManager.encryptionKey cannot be empty.'));
}
} }
/** /**
@ -173,7 +178,7 @@ class CSecurityManager extends CApplicationComponent
*/ */
public function setValidation($value) public function setValidation($value)
{ {
$this->hashAlgorithm=$value; $this->hashAlgorithm = $value;
} }
/** /**
@ -183,14 +188,14 @@ class CSecurityManager extends CApplicationComponent
* @return string the encrypted data * @return string the encrypted data
* @throws CException if PHP Mcrypt extension is not loaded * @throws CException if PHP Mcrypt extension is not loaded
*/ */
public function encrypt($data,$key=null) public function encrypt($data, $key = null)
{ {
$module=$this->openCryptModule(); $module = $this->openCryptModule();
$key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module)); $key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 0, mcrypt_enc_get_key_size($module));
srand(); srand();
$iv=mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
mcrypt_generic_init($module,$key,$iv); mcrypt_generic_init($module, $key, $iv);
$encrypted=$iv.mcrypt_generic($module,$data); $encrypted = $iv . mcrypt_generic($module, $data);
mcrypt_generic_deinit($module); mcrypt_generic_deinit($module);
mcrypt_module_close($module); mcrypt_module_close($module);
return $encrypted; return $encrypted;
@ -203,17 +208,17 @@ class CSecurityManager extends CApplicationComponent
* @return string the decrypted data * @return string the decrypted data
* @throws CException if PHP Mcrypt extension is not loaded * @throws CException if PHP Mcrypt extension is not loaded
*/ */
public function decrypt($data,$key=null) public function decrypt($data, $key = null)
{ {
$module=$this->openCryptModule(); $module = $this->openCryptModule();
$key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module)); $key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 0, mcrypt_enc_get_key_size($module));
$ivSize=mcrypt_enc_get_iv_size($module); $ivSize = mcrypt_enc_get_iv_size($module);
$iv=$this->substr($data,0,$ivSize); $iv = $this->substr($data, 0, $ivSize);
mcrypt_generic_init($module,$key,$iv); mcrypt_generic_init($module, $key, $iv);
$decrypted=mdecrypt_generic($module,$this->substr($data,$ivSize,$this->strlen($data))); $decrypted = mdecrypt_generic($module, $this->substr($data, $ivSize, $this->strlen($data)));
mcrypt_generic_deinit($module); mcrypt_generic_deinit($module);
mcrypt_module_close($module); mcrypt_module_close($module);
return rtrim($decrypted,"\0"); return rtrim($decrypted, "\0");
} }
/** /**
@ -223,20 +228,23 @@ class CSecurityManager extends CApplicationComponent
*/ */
protected function openCryptModule() protected function openCryptModule()
{ {
if(extension_loaded('mcrypt')) if (extension_loaded('mcrypt')) {
{ if (is_array($this->cryptAlgorithm)) {
if(is_array($this->cryptAlgorithm)) $module = @call_user_func_array('mcrypt_module_open', $this->cryptAlgorithm);
$module=@call_user_func_array('mcrypt_module_open',$this->cryptAlgorithm); }
else else {
$module=@mcrypt_module_open($this->cryptAlgorithm,'', MCRYPT_MODE_CBC,''); $module = @mcrypt_module_open($this->cryptAlgorithm, '', MCRYPT_MODE_CBC, '');
}
if($module===false) if ($module === false) {
throw new CException(Yii::t('yii','Failed to initialize the mcrypt module.')); throw new CException(Yii::t('yii', 'Failed to initialize the mcrypt module.'));
}
return $module; return $module;
} }
else else {
throw new CException(Yii::t('yii','CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.')); throw new CException(Yii::t('yii', 'SecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'));
}
} }
/** /**
@ -245,9 +253,9 @@ class CSecurityManager extends CApplicationComponent
* @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
* @return string data prefixed with HMAC * @return string data prefixed with HMAC
*/ */
public function hashData($data,$key=null) public function hashData($data, $key = null)
{ {
return $this->computeHMAC($data,$key).$data; return $this->computeHMAC($data, $key) . $data;
} }
/** /**
@ -258,17 +266,17 @@ class CSecurityManager extends CApplicationComponent
* @return string the real data with HMAC stripped off. False if the data * @return string the real data with HMAC stripped off. False if the data
* is tampered. * is tampered.
*/ */
public function validateData($data,$key=null) public function validateData($data, $key = null)
{ {
$len=$this->strlen($this->computeHMAC('test')); $len = $this->strlen($this->computeHMAC('test'));
if($this->strlen($data)>=$len) if ($this->strlen($data) >= $len) {
{ $hmac = $this->substr($data, 0, $len);
$hmac=$this->substr($data,0,$len); $data2 = $this->substr($data, $len, $this->strlen($data));
$data2=$this->substr($data,$len,$this->strlen($data)); return $hmac === $this->computeHMAC($data2, $key) ? $data2 : false;
return $hmac===$this->computeHMAC($data2,$key)?$data2:false;
} }
else else {
return false; return false;
}
} }
/** /**
@ -277,29 +285,31 @@ class CSecurityManager extends CApplicationComponent
* @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
* @return string the HMAC for the data * @return string the HMAC for the data
*/ */
protected function computeHMAC($data,$key=null) protected function computeHMAC($data, $key = null)
{ {
if($key===null) if ($key === null) {
$key=$this->getValidationKey(); $key = $this->getValidationKey();
}
if(function_exists('hash_hmac')) if (function_exists('hash_hmac')) {
return hash_hmac($this->hashAlgorithm, $data, $key); return hash_hmac($this->hashAlgorithm, $data, $key);
}
if(!strcasecmp($this->hashAlgorithm,'sha1')) if (!strcasecmp($this->hashAlgorithm, 'sha1')) {
{ $pack = 'H40';
$pack='H40'; $func = 'sha1';
$func='sha1'; }
else {
$pack = 'H32';
$func = 'md5';
}
if ($this->strlen($key) > 64) {
$key = pack($pack, $func($key));
} }
else if ($this->strlen($key) < 64) {
{ $key = str_pad($key, 64, chr(0));
$pack='H32';
$func='md5';
} }
if($this->strlen($key) > 64) $key = $this->substr($key, 0, 64);
$key=pack($pack, $func($key));
if($this->strlen($key) < 64)
$key=str_pad($key, 64, chr(0));
$key=$this->substr($key,0,64);
return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $data))); return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $data)));
} }
@ -311,7 +321,7 @@ class CSecurityManager extends CApplicationComponent
*/ */
private function strlen($string) private function strlen($string)
{ {
return $this->_mbstring ? mb_strlen($string,'8bit') : strlen($string); return $this->_mbstring ? mb_strlen($string, '8bit') : strlen($string);
} }
/** /**
@ -322,8 +332,8 @@ class CSecurityManager extends CApplicationComponent
* @param int $length the desired portion length * @param int $length the desired portion length
* @return string the extracted part of string, or FALSE on failure or an empty string. * @return string the extracted part of string, or FALSE on failure or an empty string.
*/ */
private function substr($string,$start,$length) private function substr($string, $start, $length)
{ {
return $this->_mbstring ? mb_substr($string,$start,$length,'8bit') : substr($string,$start,$length); return $this->_mbstring ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length);
} }
} }

242
framework/console/Application.php

@ -2,7 +2,6 @@
/** /**
* Console Application class file. * Console Application class file.
* *
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
@ -10,33 +9,37 @@
namespace yii\console; namespace yii\console;
use yii\base\Exception;
// fcgi doesn't have STDIN defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
/** /**
* \yii\console\Application represents a console application. * Application represents a console application.
* *
* \yii\console\Application extends {@link \yii\base\Application} by providing functionalities * Application extends from [[yii\base\Application]] by providing functionalities that are
* specific to console requests. In particular, it deals with console requests * specific to console requests. In particular, it deals with console requests
* through a command-based approach: * through a command-based approach:
* <ul>
* <li>A console application consists of one or several possible user commands;</li>
* <li>Each user command is implemented as a class extending {@link \yii\console\Command};</li>
* <li>User specifies which command to run on the command line;</li>
* <li>The command processes the user request with the specified parameters.</li>
* </ul>
* *
* The command classes reside in the directory {@link getCommandPath commandPath}. * - A console application consists of one or several possible user commands;
* The name of the class follows the pattern: &lt;command-name&gt;Command, and its * - Each user command is implemented as a class extending [[Command]];
* file name is the same the class name. For example, the 'ShellCommand' class defines * - User specifies which command to run on the command line;
* a 'shell' command and the class file name is 'ShellCommand.php'. * - The command processes the user request with the specified parameters.
*
* The command classes reside in the directory specified by [[commandPath]].
* The name of the class should be of the form `<command-name>Command` (e.g. `HelpCommand`).
* *
* To run the console application, enter the following on the command line: * To run the console application, enter the following on the command line:
* <pre>
* php path/to/entry_script.php <command name> [param 1] [param 2] ...
* </pre>
* *
* You may use the following to see help instructions about a command: * ~~~
* <pre> * yiic <command-name> [param 1] [param 2] ...
* php path/to/entry_script.php help <command name> * ~~~
* </pre> *
* You may use the following line to see help instructions about a command:
*
* ~~~
* yiic help <command-name>
* ~~~
* *
* @property string $commandPath The directory that contains the command classes. Defaults to 'protected/commands'. * @property string $commandPath The directory that contains the command classes. Defaults to 'protected/commands'.
* @property CommandRunner $commandRunner The command runner. * @property CommandRunner $commandRunner The command runner.
@ -47,135 +50,148 @@ namespace yii\console;
class Application extends \yii\base\Application class Application extends \yii\base\Application
{ {
/** /**
* @var array mapping from command name to command configurations. * @var string the default route of this application. Defaults to 'help',
* Each command configuration can be either a string or an array. * meaning the `help` command.
* If the former, the string should be the file path of the command class.
* If the latter, the array must contain a 'class' element which specifies
* the command's class name or {@link \YiiBase::getPathOfAlias class path alias}.
* The rest name-value pairs in the array are used to initialize
* the corresponding command properties. For example,
* <pre>
* array(
* 'email'=>array(
* 'class'=>'path.to.Mailer',
* 'interval'=>3600,
* ),
* 'log'=>'path/to/LoggerCommand.php',
* )
* </pre>
*/ */
public $commandMap=array(); public $defaultRoute = 'help';
private $_commandPath;
/** /**
* @var \yii\console\CommandRunner * @var boolean whether to enable the commands provided by the core framework.
* Defaults to true.
*/ */
private $_runner; public $enableCoreCommands = true;
/**
* Initializes the application by creating the command runner.
*/
public function init() public function init()
{ {
if(!isset($_SERVER['argv'])) parent::init();
{ if ($this->enableCoreCommands) {
die('This script must be run from the command line.'); foreach ($this->coreCommands() as $id => $command) {
if (!isset($this->controllers[$id])) {
$this->controllers[$id] = $command;
}
}
}
// ensure we have the 'help' command so that we can list the available commands
if ($this->defaultRoute === 'help' && !isset($this->controllers['help'])) {
$this->controllers['help'] = 'yii\console\commands\HelpController';
} }
$this->_runner=$this->createCommandRunner();
$this->_runner->commands=$this->commandMap;
$this->_runner->addCommands($this->getCommandPath());
} }
/** /**
* Processes the user request. * Runs the application.
* This method creates a console command runner to handle the particular user command. * This is the main entrance of an application.
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
*/ */
public function processRequest() public function run()
{ {
$this->_runner->run($_SERVER['argv']); if (!isset($_SERVER['argv'])) {
die('This script must be run from the command line.');
}
list($route, $params) = $this->resolveRequest($_SERVER['argv']);
$this->beforeRequest();
$status = $this->processRequest($route, $params);
$this->afterRequest();
return $status;
} }
/** /**
* Creates the command runner instance. * Resolves the request.
* @return CommandRunner the command runner * @param array $args the arguments passed via the command line
* @return array the controller route and the parameters for the controller action
*/ */
protected function createCommandRunner() protected function resolveRequest($args)
{ {
return new CommandRunner(); array_shift($args); // the 1st argument is the yiic script name
}
/** if (isset($args[0])) {
* Displays the captured PHP error. $route = $args[0];
* This method displays the error in console mode when there is array_shift($args);
* no active error handler. } else {
* @param integer $code error code $route = '';
* @param string $message error message }
* @param string $file error file
* @param string $line error line $params = array();
*/ foreach ($args as $arg) {
public function displayError($code,$message,$file,$line) if (preg_match('/^--(\w+)(=(.*))?$/', $arg, $matches)) {
{ $name = $matches[1];
echo "PHP Error[$code]: $message\n"; $params[$name] = isset($matches[3]) ? $matches[3] : true;
echo " in file $file at line $line\n"; } else {
$trace=debug_backtrace(); $params['args'][] = $arg;
// skip the first 4 stacks as they do not tell the error position }
if(count($trace)>4)
$trace=array_slice($trace,4);
foreach($trace as $i=>$t)
{
if(!isset($t['file']))
$t['file']='unknown';
if(!isset($t['line']))
$t['line']=0;
if(!isset($t['function']))
$t['function']='unknown';
echo "#$i {$t['file']}({$t['line']}): ";
if(isset($t['object']) && is_object($t['object']))
echo get_class($t['object']).'->';
echo "{$t['function']}()\n";
} }
return array($route, $params);
} }
/** /**
* Displays the uncaught PHP exception. * Searches for commands under the specified directory.
* This method displays the exception in console mode when there is * @param string $path the directory containing the command class files.
* no active error handler. * @return array list of commands (command name=>command class file)
* @param Exception $exception the uncaught exception
*/ */
public function displayException($exception) public function findCommands($path)
{ {
echo $exception; if (($dir = @opendir($path)) === false)
return array();
$commands = array();
while (($name = readdir($dir)) !== false) {
$file = $path . DIRECTORY_SEPARATOR . $name;
if (!strcasecmp(substr($name, -11), 'Command.php') && is_file($file))
$commands[strtolower(substr($name, 0, -11))] = $file;
}
closedir($dir);
return $commands;
} }
/** /**
* @return string the directory that contains the command classes. Defaults to 'protected/commands'. * Adds commands from the specified command path.
* If a command already exists, the new one will be ignored.
* @param string $path the alias of the directory containing the command class files.
*/ */
public function getCommandPath() public function addCommands($path)
{ {
$applicationCommandPath = $this->getBasePath().DIRECTORY_SEPARATOR.'commands'; if (($commands = $this->findCommands($path)) !== array()) {
if($this->_commandPath===null && file_exists($applicationCommandPath)) foreach ($commands as $name => $file) {
$this->setCommandPath($applicationCommandPath); if (!isset($this->commands[$name]))
return $this->_commandPath; $this->commands[$name] = $file;
}
}
} }
/** /**
* @param string $value the directory that contains the command classes. * @param string $name command name (case-insensitive)
* @throws CException if the directory is invalid * @return CConsoleCommand the command object. Null if the name is invalid.
*/ */
public function setCommandPath($value) public function createCommand($name)
{ {
if(($this->_commandPath=realpath($value))===false || !is_dir($this->_commandPath)) $name = strtolower($name);
throw new \yii\base\Exception(Yii::t('yii','The command path "{path}" is not a valid directory.', if (isset($this->commands[$name])) {
array('{path}'=>$value))); if (is_string($this->commands[$name])) // class file path or alias
{
if (strpos($this->commands[$name], '/') !== false || strpos($this->commands[$name], '\\') !== false) {
$className = substr(basename($this->commands[$name]), 0, -4);
if (!class_exists($className, false))
require_once($this->commands[$name]);
}
else // an alias
$className = Yii::import($this->commands[$name]);
return new $className($name, $this);
}
else // an array configuration
return Yii::createComponent($this->commands[$name], $name, $this);
}
else if ($name === 'help')
return new CHelpCommand('help', $this);
else
return null;
} }
/** public function coreCommands()
* Returns the command runner.
* @return CConsoleCommandRunner the command runner.
*/
public function getCommandRunner()
{ {
return $this->_runner; return array(
'message' => 'yii\console\commands\MessageController',
'help' => 'yii\console\commands\HelpController',
'migrate' => 'yii\console\commands\MigrateController',
'shell' => 'yii\console\commands\ShellController',
'app' => 'yii\console\commands\AppController',
);
} }
} }

251
framework/console/Command.php

@ -2,9 +2,8 @@
/** /**
* Command class file. * Command class file.
* *
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
@ -45,37 +44,9 @@ namespace yii\console;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
abstract class Command extends \yii\base\Component class Command extends \yii\base\Controller
{ {
/** /**
* @var string the name of the default action. Defaults to 'index'.
*/
public $defaultAction='index';
private $_name;
private $_runner;
/**
* Constructor.
* @param string $name name of the command
* @param CConsoleCommandRunner $runner the command runner
*/
public function __construct($name,$runner)
{
$this->_name=$name;
$this->_runner=$runner;
}
/**
* Initializes the command object.
* This method is invoked after a command object is created and initialized with configurations.
* You may override this method to further customize the command before it executes.
*/
public function init()
{
}
/**
* Executes the command. * Executes the command.
* The default implementation will parse the input parameters and * The default implementation will parse the input parameters and
* dispatch the command request to an appropriate action with the corresponding * dispatch the command request to an appropriate action with the corresponding
@ -84,115 +55,86 @@ abstract class Command extends \yii\base\Component
*/ */
public function run($args) public function run($args)
{ {
list($action, $options, $args)=$this->resolveRequest($args); list($action, $options, $args) = $this->resolveRequest($args);
$methodName='action'.$action; $methodName = 'action' . $action;
if(!preg_match('/^\w+$/',$action) || !method_exists($this,$methodName)) if (!preg_match('/^\w+$/', $action) || !method_exists($this, $methodName))
$this->usageError("Unknown action: ".$action); {
$this->usageError("Unknown action: " . $action);
}
$method=new \ReflectionMethod($this,$methodName); $method = new \ReflectionMethod($this, $methodName);
$params=array(); $params = array();
// named and unnamed options // named and unnamed options
foreach($method->getParameters() as $param) foreach ($method->getParameters() as $param) {
{ $name = $param->getName();
$name=$param->getName(); if (isset($options[$name])) {
if(isset($options[$name])) if ($param->isArray())
{ $params[] = is_array($options[$name]) ? $options[$name] : array($options[$name]);
if($param->isArray()) else if (!is_array($options[$name]))
$params[]=is_array($options[$name]) ? $options[$name] : array($options[$name]); $params[] = $options[$name];
else if(!is_array($options[$name]))
$params[]=$options[$name];
else else
$this->usageError("Option --$name requires a scalar. Array is given."); $this->usageError("Option --$name requires a scalar. Array is given.");
} else if($name==='args') } else if ($name === 'args')
$params[]=$args; $params[] = $args;
else if($param->isDefaultValueAvailable()) else if ($param->isDefaultValueAvailable())
$params[]=$param->getDefaultValue(); $params[] = $param->getDefaultValue();
else else
$this->usageError("Missing required option --$name."); $this->usageError("Missing required option --$name.");
unset($options[$name]); unset($options[$name]);
} }
// try global options // try global options
if(!empty($options)) if (!empty($options)) {
{ $class = new \ReflectionClass(get_class($this));
$class=new \ReflectionClass(get_class($this)); foreach ($options as $name => $value) {
foreach($options as $name=>$value) if ($class->hasProperty($name)) {
{ $property = $class->getProperty($name);
if($class->hasProperty($name)) if ($property->isPublic() && !$property->isStatic()) {
{ $this->$name = $value;
$property=$class->getProperty($name);
if($property->isPublic() && !$property->isStatic())
{
$this->$name=$value;
unset($options[$name]); unset($options[$name]);
} }
} }
} }
} }
if(!empty($options)) if (!empty($options))
$this->usageError("Unknown options: ".implode(', ',array_keys($options))); $this->usageError("Unknown options: " . implode(', ', array_keys($options)));
if($this->beforeAction($action,$params)) if ($this->beforeAction($action, $params)) {
{ $method->invokeArgs($this, $params);
$method->invokeArgs($this,$params); $this->afterAction($action, $params);
$this->afterAction($action,$params);
} }
} }
/** /**
* This method is invoked right before an action is to be executed.
* You may override this method to do last-minute preparation for the action.
* @param string $action the action name
* @param array $params the parameters to be passed to the action method.
* @return boolean whether the action should be executed.
*/
protected function beforeAction($action,$params)
{
return true;
}
/**
* This method is invoked right after an action finishes execution.
* You may override this method to do some postprocessing for the action.
* @param string $action the action name
* @param array $params the parameters to be passed to the action method.
*/
protected function afterAction($action,$params)
{
}
/**
* Parses the command line arguments and determines which action to perform. * Parses the command line arguments and determines which action to perform.
* @param array $args command line arguments * @param array $args command line arguments
* @return array the action name, named options (name=>value), and unnamed options * @return array the action name, named options (name=>value), and unnamed options
*/ */
protected function resolveRequest($args) protected function resolveRequest($args)
{ {
$options=array(); // named parameters $options = array(); // named parameters
$params=array(); // unnamed parameters $params = array(); // unnamed parameters
foreach($args as $arg) foreach ($args as $arg) {
{ if (preg_match('/^--(\w+)(=(.*))?$/', $arg, $matches)) // an option
if(preg_match('/^--(\w+)(=(.*))?$/',$arg,$matches)) // an option
{ {
$name=$matches[1]; $name = $matches[1];
$value=isset($matches[3]) ? $matches[3] : true; $value = isset($matches[3]) ? $matches[3] : true;
if(isset($options[$name])) if (isset($options[$name])) {
{ if (!is_array($options[$name]))
if(!is_array($options[$name])) $options[$name] = array($options[$name]);
$options[$name]=array($options[$name]); $options[$name][] = $value;
$options[$name][]=$value;
} else } else
$options[$name]=$value; $options[$name] = $value;
} else if(isset($action)) } else if (isset($action))
$params[]=$arg; $params[] = $arg;
else else
$action=$arg; $action = $arg;
} }
if(!isset($action)) if (!isset($action))
$action=$this->defaultAction; $action = $this->defaultAction;
return array($action,$options,$params); return array($action, $options, $params);
} }
/** /**
@ -204,29 +146,21 @@ abstract class Command extends \yii\base\Component
} }
/** /**
* @return \yii\console\CommandRunner the command runner instance
*/
public function getCommandRunner()
{
return $this->_runner;
}
/**
* Provides the command description. * Provides the command description.
* This method may be overridden to return the actual command description. * This method may be overridden to return the actual command description.
* @return string the command description. Defaults to 'Usage: php entry-script.php command-name'. * @return string the command description. Defaults to 'Usage: php entry-script.php command-name'.
*/ */
public function getHelp() public function getHelp()
{ {
$help='Usage: '.$this->getCommandRunner()->getScriptName().' '.$this->getName(); $help = 'Usage: ' . $this->getCommandRunner()->getScriptName() . ' ' . $this->getName();
$options=$this->getOptionHelp(); $options = $this->getOptionHelp();
if(empty($options)) if (empty($options))
return $help; return $help;
if(count($options)===1) if (count($options) === 1)
return $help.' '.$options[0]; return $help . ' ' . $options[0];
$help.=" <action>\nActions:\n"; $help .= " <action>\nActions:\n";
foreach($options as $option) foreach ($options as $option)
$help.=' '.$option."\n"; $help .= ' ' . $option . "\n";
return $help; return $help;
} }
@ -239,31 +173,28 @@ abstract class Command extends \yii\base\Component
*/ */
public function getOptionHelp() public function getOptionHelp()
{ {
$options=array(); $options = array();
$class=new \ReflectionClass(get_class($this)); $class = new \ReflectionClass(get_class($this));
foreach($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
{ $name = $method->getName();
$name=$method->getName(); if (!strncasecmp($name, 'action', 6) && strlen($name) > 6) {
if(!strncasecmp($name,'action',6) && strlen($name)>6) $name = substr($name, 6);
{ $name[0] = strtolower($name[0]);
$name=substr($name,6); $help = $name;
$name[0]=strtolower($name[0]);
$help=$name;
foreach($method->getParameters() as $param) foreach ($method->getParameters() as $param) {
{ $optional = $param->isDefaultValueAvailable();
$optional=$param->isDefaultValueAvailable(); $defaultValue = $optional ? $param->getDefaultValue() : null;
$defaultValue=$optional ? $param->getDefaultValue() : null; $name = $param->getName();
$name=$param->getName(); if ($optional)
if($optional) $help .= " [--$name=$defaultValue]";
$help.=" [--$name=$defaultValue]";
else else
$help.=" --$name=value"; $help .= " --$name=value";
} }
$options[]=$help; $options[] = $help;
} }
} }
return $options; return $options;
} }
/** /**
@ -273,7 +204,7 @@ abstract class Command extends \yii\base\Component
*/ */
public function usageError($message) public function usageError($message)
{ {
echo "Error: $message\n\n".$this->getHelp()."\n"; echo "Error: $message\n\n" . $this->getHelp() . "\n";
exit(1); exit(1);
} }
@ -284,14 +215,13 @@ abstract class Command extends \yii\base\Component
* @param boolean $_return_ whether to return the rendering result instead of displaying it * @param boolean $_return_ whether to return the rendering result instead of displaying it
* @return mixed the rendering result if required. Null otherwise. * @return mixed the rendering result if required. Null otherwise.
*/ */
public function renderFile($_viewFile_,$_data_=null,$_return_=false) public function renderFile($_viewFile_, $_data_ = null, $_return_ = false)
{ {
if(is_array($_data_)) if (is_array($_data_))
extract($_data_,EXTR_PREFIX_SAME,'data'); extract($_data_, EXTR_PREFIX_SAME, 'data');
else else
$data=$_data_; $data = $_data_;
if($_return_) if ($_return_) {
{
ob_start(); ob_start();
ob_implicit_flush(false); ob_implicit_flush(false);
require($_viewFile_); require($_viewFile_);
@ -310,29 +240,28 @@ abstract class Command extends \yii\base\Component
*/ */
public function prompt($message, $default = null) public function prompt($message, $default = null)
{ {
if($default !== null) { if ($default !== null) {
$message .= " [$default] "; $message .= " [$default] ";
} }
else { else {
$message .= ' '; $message .= ' ';
} }
if(extension_loaded('readline')) if (extension_loaded('readline')) {
{
$input = readline($message); $input = readline($message);
if($input) { if ($input) {
readline_add_history($input); readline_add_history($input);
} }
} else { } else {
echo $message; echo $message;
$input = fgets(STDIN); $input = fgets(STDIN);
} }
if($input === false) { if ($input === false) {
return false; return false;
} }
else { else {
$input = trim($input); $input = trim($input);
return ($input==='' && $default!==null) ? $default : $input; return ($input === '' && $default !== null) ? $default : $input;
} }
} }
@ -344,7 +273,7 @@ abstract class Command extends \yii\base\Component
*/ */
public function confirm($message) public function confirm($message)
{ {
echo $message.' [yes|no] '; echo $message . ' [yes|no] ';
return !strncasecmp(trim(fgets(STDIN)),'y',1); return !strncasecmp(trim(fgets(STDIN)), 'y', 1);
} }
} }

138
framework/console/CommandRunner.php

@ -1,138 +0,0 @@
<?php
/**
* CConsoleCommandRunner class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console;
/**
* CConsoleCommandRunner manages commands and executes the requested command.
*
* @property string $scriptName The entry script name.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CommandRunner extends \yii\base\Component
{
/**
* @var array list of all available commands (command name=>command configuration).
* Each command configuration can be either a string or an array.
* If the former, the string should be the class name or
* {@link YiiBase::getPathOfAlias class path alias} of the command.
* If the latter, the array must contain a 'class' element which specifies
* the command's class name or {@link YiiBase::getPathOfAlias class path alias}.
* The rest name-value pairs in the array are used to initialize
* the corresponding command properties. For example,
* <pre>
* array(
* 'email'=>array(
* 'class'=>'path.to.Mailer',
* 'interval'=>3600,
* ),
* 'log'=>'path.to.LoggerCommand',
* )
* </pre>
*/
public $commands=array();
private $_scriptName;
/**
* Executes the requested command.
* @param array $args list of user supplied parameters (including the entry script name and the command name).
*/
public function run($args)
{
$this->_scriptName=$args[0];
array_shift($args);
if(isset($args[0]))
{
$name=$args[0];
array_shift($args);
} else
$name='help';
if(($command=$this->createCommand($name))===null)
$command=$this->createCommand('help');
$command->init();
$command->run($args);
}
/**
* @return string the entry script name
*/
public function getScriptName()
{
return $this->_scriptName;
}
/**
* Searches for commands under the specified directory.
* @param string $path the directory containing the command class files.
* @return array list of commands (command name=>command class file)
*/
public function findCommands($path)
{
if(($dir=@opendir($path))===false)
return array();
$commands=array();
while(($name=readdir($dir))!==false)
{
$file=$path.DIRECTORY_SEPARATOR.$name;
if(!strcasecmp(substr($name,-11),'Command.php') && is_file($file))
$commands[strtolower(substr($name,0,-11))]=$file;
}
closedir($dir);
return $commands;
}
/**
* Adds commands from the specified command path.
* If a command already exists, the new one will be ignored.
* @param string $path the alias of the directory containing the command class files.
*/
public function addCommands($path)
{
if(($commands=$this->findCommands($path))!==array())
{
foreach($commands as $name=>$file)
{
if(!isset($this->commands[$name]))
$this->commands[$name]=$file;
}
}
}
/**
* @param string $name command name (case-insensitive)
* @return \yii\console\Command the command object. Null if the name is invalid.
*/
public function createCommand($name)
{
$name=strtolower($name);
if(isset($this->commands[$name]))
{
if(is_string($this->commands[$name])) // class file path or alias
{
if(strpos($this->commands[$name],'/')!==false || strpos($this->commands[$name],'\\')!==false)
{
$className=substr(basename($this->commands[$name]),0,-4);
if(!class_exists($className,false))
require_once($this->commands[$name]);
} else // an alias
$className=\Yii::import($this->commands[$name]);
return new $className($name,$this);
} else // an array configuration
return \Yii::create($this->commands[$name],$name,$this);
} else if($name==='help')
return new HelpCommand('help',$this);
else
return null;
}
}

129
framework/console/commands/AppController.php

@ -0,0 +1,129 @@
<?php
/**
* WebAppCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @version $Id$
*/
/**
* WebAppCommand creates an Yii Web application at the specified location.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.cli.commands
* @since 1.0
*/
class WebAppCommand extends CConsoleCommand
{
private $_rootPath;
public function getHelp()
{
return <<<EOD
USAGE
yiic webapp <app-path>
DESCRIPTION
This command generates an Yii Web Application at the specified location.
PARAMETERS
* app-path: required, the directory where the new application will be created.
If the directory does not exist, it will be created. After the application
is created, please make sure the directory can be accessed by Web users.
EOD;
}
/**
* Execute the action.
* @param array command line parameters specific for this command
*/
public function run($args)
{
if(!isset($args[0]))
$this->usageError('the Web application location is not specified.');
$path=strtr($args[0],'/\\',DIRECTORY_SEPARATOR);
if(strpos($path,DIRECTORY_SEPARATOR)===false)
$path='.'.DIRECTORY_SEPARATOR.$path;
$dir=rtrim(realpath(dirname($path)),'\\/');
if($dir===false || !is_dir($dir))
$this->usageError("The directory '$path' is not valid. Please make sure the parent directory exists.");
if(basename($path)==='.')
$this->_rootPath=$path=$dir;
else
$this->_rootPath=$path=$dir.DIRECTORY_SEPARATOR.basename($path);
if($this->confirm("Create a Web application under '$path'?"))
{
$sourceDir=realpath(dirname(__FILE__).'/../views/webapp');
if($sourceDir===false)
die("\nUnable to locate the source directory.\n");
$list=$this->buildFileList($sourceDir,$path);
$list['index.php']['callback']=array($this,'generateIndex');
$list['index-test.php']['callback']=array($this,'generateIndex');
$list['protected/tests/bootstrap.php']['callback']=array($this,'generateTestBoostrap');
$list['protected/yiic.php']['callback']=array($this,'generateYiic');
$this->copyFiles($list);
@chmod($path.'/assets',0777);
@chmod($path.'/protected/runtime',0777);
@chmod($path.'/protected/data',0777);
@chmod($path.'/protected/data/testdrive.db',0777);
@chmod($path.'/protected/yiic',0755);
echo "\nYour application has been created successfully under {$path}.\n";
}
}
public function generateIndex($source,$params)
{
$content=file_get_contents($source);
$yii=realpath(dirname(__FILE__).'/../../yii.php');
$yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'index.php');
$yii=str_replace('\\','\\\\',$yii);
return preg_replace('/\$yii\s*=(.*?);/',"\$yii=$yii;",$content);
}
public function generateTestBoostrap($source,$params)
{
$content=file_get_contents($source);
$yii=realpath(dirname(__FILE__).'/../../yiit.php');
$yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'bootstrap.php');
$yii=str_replace('\\','\\\\',$yii);
return preg_replace('/\$yiit\s*=(.*?);/',"\$yiit=$yii;",$content);
}
public function generateYiic($source,$params)
{
$content=file_get_contents($source);
$yiic=realpath(dirname(__FILE__).'/../../yiic.php');
$yiic=$this->getRelativePath($yiic,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'yiic.php');
$yiic=str_replace('\\','\\\\',$yiic);
return preg_replace('/\$yiic\s*=(.*?);/',"\$yiic=$yiic;",$content);
}
protected function getRelativePath($path1,$path2)
{
$segs1=explode(DIRECTORY_SEPARATOR,$path1);
$segs2=explode(DIRECTORY_SEPARATOR,$path2);
$n1=count($segs1);
$n2=count($segs2);
for($i=0;$i<$n1 && $i<$n2;++$i)
{
if($segs1[$i]!==$segs2[$i])
break;
}
if($i===0)
return "'".$path1."'";
$up='';
for($j=$i;$j<$n2-1;++$j)
$up.='/..';
for(;$i<$n1-1;++$i)
$up.='/'.$segs1[$i];
return 'dirname(__FILE__).\''.$up.'/'.basename($path1).'\'';
}
}

0
framework/console/HelpCommand.php → framework/console/commands/HelpController.php

223
framework/console/commands/MessageController.php

@ -0,0 +1,223 @@
<?php
/**
* MessageCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* MessageCommand extracts messages to be translated from source files.
* The extracted messages are saved as PHP message source files
* under the specified directory.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.cli.commands
* @since 1.0
*/
class MessageCommand extends CConsoleCommand
{
public function getHelp()
{
return <<<EOD
USAGE
yiic message <config-file>
DESCRIPTION
This command searches for messages to be translated in the specified
source files and compiles them into PHP arrays as message source.
PARAMETERS
* config-file: required, the path of the configuration file. You can find
an example in framework/messages/config.php.
The file can be placed anywhere and must be a valid PHP script which
returns an array of name-value pairs. Each name-value pair represents
a configuration option.
The following options are available:
- sourcePath: string, root directory of all source files.
- messagePath: string, root directory containing message translations.
- languages: array, list of language codes that the extracted messages
should be translated to. For example, array('zh_cn','en_au').
- fileTypes: array, a list of file extensions (e.g. 'php', 'xml').
Only the files whose extension name can be found in this list
will be processed. If empty, all files will be processed.
- exclude: array, a list of directory and file exclusions. Each
exclusion can be either a name or a path. If a file or directory name
or path matches the exclusion, it will not be copied. For example,
an exclusion of '.svn' will exclude all files and directories whose
name is '.svn'. And an exclusion of '/a/b' will exclude file or
directory 'sourcePath/a/b'.
- translator: the name of the function for translating messages.
Defaults to 'Yii::t'. This is used as a mark to find messages to be
translated.
- overwrite: if message file must be overwritten with the merged messages.
- removeOld: if message no longer needs translation it will be removed,
instead of being enclosed between a pair of '@@' marks.
- sort: sort messages by key when merging, regardless of their translation
state (new, obsolete, translated.)
EOD;
}
/**
* Execute the action.
* @param array command line parameters specific for this command
*/
public function run($args)
{
if(!isset($args[0]))
$this->usageError('the configuration file is not specified.');
if(!is_file($args[0]))
$this->usageError("the configuration file {$args[0]} does not exist.");
$config=require_once($args[0]);
$translator='Yii::t';
extract($config);
if(!isset($sourcePath,$messagePath,$languages))
$this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".');
if(!is_dir($sourcePath))
$this->usageError("The source path $sourcePath is not a valid directory.");
if(!is_dir($messagePath))
$this->usageError("The message path $messagePath is not a valid directory.");
if(empty($languages))
$this->usageError("Languages cannot be empty.");
if(!isset($overwrite))
$overwrite = false;
if(!isset($removeOld))
$removeOld = false;
if(!isset($sort))
$sort = false;
$options=array();
if(isset($fileTypes))
$options['fileTypes']=$fileTypes;
if(isset($exclude))
$options['exclude']=$exclude;
$files=CFileHelper::findFiles(realpath($sourcePath),$options);
$messages=array();
foreach($files as $file)
$messages=array_merge_recursive($messages,$this->extractMessages($file,$translator));
foreach($languages as $language)
{
$dir=$messagePath.DIRECTORY_SEPARATOR.$language;
if(!is_dir($dir))
@mkdir($dir);
foreach($messages as $category=>$msgs)
{
$msgs=array_values(array_unique($msgs));
$this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort);
}
}
}
protected function extractMessages($fileName,$translator)
{
echo "Extracting messages from $fileName...\n";
$subject=file_get_contents($fileName);
$n=preg_match_all('/\b'.$translator.'\s*\(\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*,\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*[,\)]/s',$subject,$matches,PREG_SET_ORDER);
$messages=array();
for($i=0;$i<$n;++$i)
{
if(($pos=strpos($matches[$i][1],'.'))!==false)
$category=substr($matches[$i][1],$pos+1,-1);
else
$category=substr($matches[$i][1],1,-1);
$message=$matches[$i][2];
$messages[$category][]=eval("return $message;"); // use eval to eliminate quote escape
}
return $messages;
}
protected function generateMessageFile($messages,$fileName,$overwrite,$removeOld,$sort)
{
echo "Saving messages to $fileName...";
if(is_file($fileName))
{
$translated=require($fileName);
sort($messages);
ksort($translated);
if(array_keys($translated)==$messages)
{
echo "nothing new...skipped.\n";
return;
}
$merged=array();
$untranslated=array();
foreach($messages as $message)
{
if(!empty($translated[$message]))
$merged[$message]=$translated[$message];
else
$untranslated[]=$message;
}
ksort($merged);
sort($untranslated);
$todo=array();
foreach($untranslated as $message)
$todo[$message]='';
ksort($translated);
foreach($translated as $message=>$translation)
{
if(!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld)
{
if(substr($translation,0,2)==='@@' && substr($translation,-2)==='@@')
$todo[$message]=$translation;
else
$todo[$message]='@@'.$translation.'@@';
}
}
$merged=array_merge($todo,$merged);
if($sort)
ksort($merged);
if($overwrite === false)
$fileName.='.merged';
echo "translation merged.\n";
}
else
{
$merged=array();
foreach($messages as $message)
$merged[$message]='';
ksort($merged);
echo "saved.\n";
}
$array=str_replace("\r",'',var_export($merged,true));
$content=<<<EOD
<?php
/**
* Message translations.
*
* This file is automatically generated by 'yiic message' command.
* It contains the localizable messages extracted from source code.
* You may modify this file by translating the extracted messages.
*
* Each array element represents the translation (value) of a message (key).
* If the value is empty, the message is considered as not translated.
* Messages that no longer need translation will have their translations
* enclosed between a pair of '@@' marks.
*
* Message string can be used with plural forms format. Check i18n section
* of the guide for details.
*
* NOTE, this file must be saved in UTF-8 encoding.
*
* @version \$Id: \$
*/
return $array;
EOD;
file_put_contents($fileName, $content);
}
}

561
framework/console/commands/MigrateController.php

@ -0,0 +1,561 @@
<?php
/**
* MigrateCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* MigrateCommand manages the database migrations.
*
* The implementation of this command and other supporting classes referenced
* the yii-dbmigrations extension ((https://github.com/pieterclaerhout/yii-dbmigrations),
* authored by Pieter Claerhout.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.cli.commands
* @since 1.1.6
*/
class MigrateCommand extends CConsoleCommand
{
const BASE_MIGRATION='m000000_000000_base';
/**
* @var string the directory that stores the migrations. This must be specified
* in terms of a path alias, and the corresponding directory must exist.
* Defaults to 'application.migrations' (meaning 'protected/migrations').
*/
public $migrationPath='application.migrations';
/**
* @var string the name of the table for keeping applied migration information.
* This table will be automatically created if not exists. Defaults to 'tbl_migration'.
* The table structure is: (version varchar(255) primary key, apply_time integer)
*/
public $migrationTable='tbl_migration';
/**
* @var string the application component ID that specifies the database connection for
* storing migration information. Defaults to 'db'.
*/
public $connectionID='db';
/**
* @var string the path of the template file for generating new migrations. This
* must be specified in terms of a path alias (e.g. application.migrations.template).
* If not set, an internal template will be used.
*/
public $templateFile;
/**
* @var string the default command action. It defaults to 'up'.
*/
public $defaultAction='up';
/**
* @var boolean whether to execute the migration in an interactive mode. Defaults to true.
* Set this to false when performing migration in a cron job or background process.
*/
public $interactive=true;
public function beforeAction($action,$params)
{
$path=Yii::getPathOfAlias($this->migrationPath);
if($path===false || !is_dir($path))
die('Error: The migration directory does not exist: '.$this->migrationPath."\n");
$this->migrationPath=$path;
$yiiVersion=Yii::getVersion();
echo "\nYii Migration Tool v1.0 (based on Yii v{$yiiVersion})\n\n";
return true;
}
public function actionUp($args)
{
if(($migrations=$this->getNewMigrations())===array())
{
echo "No new migration found. Your system is up-to-date.\n";
return;
}
$total=count($migrations);
$step=isset($args[0]) ? (int)$args[0] : 0;
if($step>0)
$migrations=array_slice($migrations,0,$step);
$n=count($migrations);
if($n===$total)
echo "Total $n new ".($n===1 ? 'migration':'migrations')." to be applied:\n";
else
echo "Total $n out of $total new ".($total===1 ? 'migration':'migrations')." to be applied:\n";
foreach($migrations as $migration)
echo " $migration\n";
echo "\n";
if($this->confirm('Apply the above '.($n===1 ? 'migration':'migrations')."?"))
{
foreach($migrations as $migration)
{
if($this->migrateUp($migration)===false)
{
echo "\nMigration failed. All later migrations are canceled.\n";
return;
}
}
echo "\nMigrated up successfully.\n";
}
}
public function actionDown($args)
{
$step=isset($args[0]) ? (int)$args[0] : 1;
if($step<1)
die("Error: The step parameter must be greater than 0.\n");
if(($migrations=$this->getMigrationHistory($step))===array())
{
echo "No migration has been done before.\n";
return;
}
$migrations=array_keys($migrations);
$n=count($migrations);
echo "Total $n ".($n===1 ? 'migration':'migrations')." to be reverted:\n";
foreach($migrations as $migration)
echo " $migration\n";
echo "\n";
if($this->confirm('Revert the above '.($n===1 ? 'migration':'migrations')."?"))
{
foreach($migrations as $migration)
{
if($this->migrateDown($migration)===false)
{
echo "\nMigration failed. All later migrations are canceled.\n";
return;
}
}
echo "\nMigrated down successfully.\n";
}
}
public function actionRedo($args)
{
$step=isset($args[0]) ? (int)$args[0] : 1;
if($step<1)
die("Error: The step parameter must be greater than 0.\n");
if(($migrations=$this->getMigrationHistory($step))===array())
{
echo "No migration has been done before.\n";
return;
}
$migrations=array_keys($migrations);
$n=count($migrations);
echo "Total $n ".($n===1 ? 'migration':'migrations')." to be redone:\n";
foreach($migrations as $migration)
echo " $migration\n";
echo "\n";
if($this->confirm('Redo the above '.($n===1 ? 'migration':'migrations')."?"))
{
foreach($migrations as $migration)
{
if($this->migrateDown($migration)===false)
{
echo "\nMigration failed. All later migrations are canceled.\n";
return;
}
}
foreach(array_reverse($migrations) as $migration)
{
if($this->migrateUp($migration)===false)
{
echo "\nMigration failed. All later migrations are canceled.\n";
return;
}
}
echo "\nMigration redone successfully.\n";
}
}
public function actionTo($args)
{
if(isset($args[0]))
$version=$args[0];
else
$this->usageError('Please specify which version to migrate to.');
$originalVersion=$version;
if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches))
$version='m'.$matches[1];
else
die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n");
// try migrate up
$migrations=$this->getNewMigrations();
foreach($migrations as $i=>$migration)
{
if(strpos($migration,$version.'_')===0)
{
$this->actionUp(array($i+1));
return;
}
}
// try migrate down
$migrations=array_keys($this->getMigrationHistory(-1));
foreach($migrations as $i=>$migration)
{
if(strpos($migration,$version.'_')===0)
{
if($i===0)
echo "Already at '$originalVersion'. Nothing needs to be done.\n";
else
$this->actionDown(array($i));
return;
}
}
die("Error: Unable to find the version '$originalVersion'.\n");
}
public function actionMark($args)
{
if(isset($args[0]))
$version=$args[0];
else
$this->usageError('Please specify which version to mark to.');
$originalVersion=$version;
if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches))
$version='m'.$matches[1];
else
die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n");
$db=$this->getDbConnection();
// try mark up
$migrations=$this->getNewMigrations();
foreach($migrations as $i=>$migration)
{
if(strpos($migration,$version.'_')===0)
{
if($this->confirm("Set migration history at $originalVersion?"))
{
$command=$db->createCommand();
for($j=0;$j<=$i;++$j)
{
$command->insert($this->migrationTable, array(
'version'=>$migrations[$j],
'apply_time'=>time(),
));
}
echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
}
return;
}
}
// try mark down
$migrations=array_keys($this->getMigrationHistory(-1));
foreach($migrations as $i=>$migration)
{
if(strpos($migration,$version.'_')===0)
{
if($i===0)
echo "Already at '$originalVersion'. Nothing needs to be done.\n";
else
{
if($this->confirm("Set migration history at $originalVersion?"))
{
$command=$db->createCommand();
for($j=0;$j<$i;++$j)
$command->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$migrations[$j]));
echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
}
}
return;
}
}
die("Error: Unable to find the version '$originalVersion'.\n");
}
public function actionHistory($args)
{
$limit=isset($args[0]) ? (int)$args[0] : -1;
$migrations=$this->getMigrationHistory($limit);
if($migrations===array())
echo "No migration has been done before.\n";
else
{
$n=count($migrations);
if($limit>0)
echo "Showing the last $n applied ".($n===1 ? 'migration' : 'migrations').":\n";
else
echo "Total $n ".($n===1 ? 'migration has' : 'migrations have')." been applied before:\n";
foreach($migrations as $version=>$time)
echo " (".date('Y-m-d H:i:s',$time).') '.$version."\n";
}
}
public function actionNew($args)
{
$limit=isset($args[0]) ? (int)$args[0] : -1;
$migrations=$this->getNewMigrations();
if($migrations===array())
echo "No new migrations found. Your system is up-to-date.\n";
else
{
$n=count($migrations);
if($limit>0 && $n>$limit)
{
$migrations=array_slice($migrations,0,$limit);
echo "Showing $limit out of $n new ".($n===1 ? 'migration' : 'migrations').":\n";
}
else
echo "Found $n new ".($n===1 ? 'migration' : 'migrations').":\n";
foreach($migrations as $migration)
echo " ".$migration."\n";
}
}
public function actionCreate($args)
{
if(isset($args[0]))
$name=$args[0];
else
$this->usageError('Please provide the name of the new migration.');
if(!preg_match('/^\w+$/',$name))
die("Error: The name of the migration must contain letters, digits and/or underscore characters only.\n");
$name='m'.gmdate('ymd_His').'_'.$name;
$content=strtr($this->getTemplate(), array('{ClassName}'=>$name));
$file=$this->migrationPath.DIRECTORY_SEPARATOR.$name.'.php';
if($this->confirm("Create new migration '$file'?"))
{
file_put_contents($file, $content);
echo "New migration created successfully.\n";
}
}
public function confirm($message)
{
if(!$this->interactive)
return true;
return parent::confirm($message);
}
protected function migrateUp($class)
{
if($class===self::BASE_MIGRATION)
return;
echo "*** applying $class\n";
$start=microtime(true);
$migration=$this->instantiateMigration($class);
if($migration->up()!==false)
{
$this->getDbConnection()->createCommand()->insert($this->migrationTable, array(
'version'=>$class,
'apply_time'=>time(),
));
$time=microtime(true)-$start;
echo "*** applied $class (time: ".sprintf("%.3f",$time)."s)\n\n";
}
else
{
$time=microtime(true)-$start;
echo "*** failed to apply $class (time: ".sprintf("%.3f",$time)."s)\n\n";
return false;
}
}
protected function migrateDown($class)
{
if($class===self::BASE_MIGRATION)
return;
echo "*** reverting $class\n";
$start=microtime(true);
$migration=$this->instantiateMigration($class);
if($migration->down()!==false)
{
$db=$this->getDbConnection();
$db->createCommand()->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$class));
$time=microtime(true)-$start;
echo "*** reverted $class (time: ".sprintf("%.3f",$time)."s)\n\n";
}
else
{
$time=microtime(true)-$start;
echo "*** failed to revert $class (time: ".sprintf("%.3f",$time)."s)\n\n";
return false;
}
}
protected function instantiateMigration($class)
{
$file=$this->migrationPath.DIRECTORY_SEPARATOR.$class.'.php';
require_once($file);
$migration=new $class;
$migration->setDbConnection($this->getDbConnection());
return $migration;
}
/**
* @var CDbConnection
*/
private $_db;
protected function getDbConnection()
{
if($this->_db!==null)
return $this->_db;
else if(($this->_db=Yii::app()->getComponent($this->connectionID)) instanceof CDbConnection)
return $this->_db;
else
die("Error: CMigrationCommand.connectionID '{$this->connectionID}' is invalid. Please make sure it refers to the ID of a CDbConnection application component.\n");
}
protected function getMigrationHistory($limit)
{
$db=$this->getDbConnection();
if($db->schema->getTable($this->migrationTable)===null)
{
$this->createMigrationHistoryTable();
}
return CHtml::listData($db->createCommand()
->select('version, apply_time')
->from($this->migrationTable)
->order('version DESC')
->limit($limit)
->queryAll(), 'version', 'apply_time');
}
protected function createMigrationHistoryTable()
{
$db=$this->getDbConnection();
echo 'Creating migration history table "'.$this->migrationTable.'"...';
$db->createCommand()->createTable($this->migrationTable,array(
'version'=>'string NOT NULL PRIMARY KEY',
'apply_time'=>'integer',
));
$db->createCommand()->insert($this->migrationTable,array(
'version'=>self::BASE_MIGRATION,
'apply_time'=>time(),
));
echo "done.\n";
}
protected function getNewMigrations()
{
$applied=array();
foreach($this->getMigrationHistory(-1) as $version=>$time)
$applied[substr($version,1,13)]=true;
$migrations=array();
$handle=opendir($this->migrationPath);
while(($file=readdir($handle))!==false)
{
if($file==='.' || $file==='..')
continue;
$path=$this->migrationPath.DIRECTORY_SEPARATOR.$file;
if(preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/',$file,$matches) && is_file($path) && !isset($applied[$matches[2]]))
$migrations[]=$matches[1];
}
closedir($handle);
sort($migrations);
return $migrations;
}
public function getHelp()
{
return <<<EOD
USAGE
yiic migrate [action] [parameter]
DESCRIPTION
This command provides support for database migrations. The optional
'action' parameter specifies which specific migration task to perform.
It can take these values: up, down, to, create, history, new, mark.
If the 'action' parameter is not given, it defaults to 'up'.
Each action takes different parameters. Their usage can be found in
the following examples.
EXAMPLES
* yiic migrate
Applies ALL new migrations. This is equivalent to 'yiic migrate up'.
* yiic migrate create create_user_table
Creates a new migration named 'create_user_table'.
* yiic migrate up 3
Applies the next 3 new migrations.
* yiic migrate down
Reverts the last applied migration.
* yiic migrate down 3
Reverts the last 3 applied migrations.
* yiic migrate to 101129_185401
Migrates up or down to version 101129_185401.
* yiic migrate mark 101129_185401
Modifies the migration history up or down to version 101129_185401.
No actual migration will be performed.
* yiic migrate history
Shows all previously applied migration information.
* yiic migrate history 10
Shows the last 10 applied migrations.
* yiic migrate new
Shows all new migrations.
* yiic migrate new 10
Shows the next 10 migrations that have not been applied.
EOD;
}
protected function getTemplate()
{
if($this->templateFile!==null)
return file_get_contents(Yii::getPathOfAlias($this->templateFile).'.php');
else
return <<<EOD
<?php
class {ClassName} extends CDbMigration
{
public function up()
{
}
public function down()
{
echo "{ClassName} does not support migration down.\\n";
return false;
}
/*
// Use safeUp/safeDown to do migration with transaction
public function safeUp()
{
}
public function safeDown()
{
}
*/
}
EOD;
}
}

148
framework/console/commands/ShellController.php

@ -0,0 +1,148 @@
<?php
/**
* ShellCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @version $Id$
*/
/**
* ShellCommand executes the specified Web application and provides a shell for interaction.
*
* @property string $help The help information for the shell command.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.cli.commands
* @since 1.0
*/
class ShellCommand extends CConsoleCommand
{
/**
* @return string the help information for the shell command
*/
public function getHelp()
{
return <<<EOD
USAGE
yiic shell [entry-script | config-file]
DESCRIPTION
This command allows you to interact with a Web application
on the command line. It also provides tools to automatically
generate new controllers, views and data models.
It is recommended that you execute this command under
the directory that contains the entry script file of
the Web application.
PARAMETERS
* entry-script | config-file: optional, the path to
the entry script file or the configuration file for
the Web application. If not given, it is assumed to be
the 'index.php' file under the current directory.
EOD;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
*/
public function run($args)
{
if(!isset($args[0]))
$args[0]='index.php';
$entryScript=isset($args[0]) ? $args[0] : 'index.php';
if(($entryScript=realpath($args[0]))===false || !is_file($entryScript))
$this->usageError("{$args[0]} does not exist or is not an entry script file.");
// fake the web server setting
$cwd=getcwd();
chdir(dirname($entryScript));
$_SERVER['SCRIPT_NAME']='/'.basename($entryScript);
$_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME'];
$_SERVER['SCRIPT_FILENAME']=$entryScript;
$_SERVER['HTTP_HOST']='localhost';
$_SERVER['SERVER_NAME']='localhost';
$_SERVER['SERVER_PORT']=80;
// reset context to run the web application
restore_error_handler();
restore_exception_handler();
Yii::setApplication(null);
Yii::setPathOfAlias('application',null);
ob_start();
$config=require($entryScript);
ob_end_clean();
// oops, the entry script turns out to be a config file
if(is_array($config))
{
chdir($cwd);
$_SERVER['SCRIPT_NAME']='/index.php';
$_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME'];
$_SERVER['SCRIPT_FILENAME']=$cwd.DIRECTORY_SEPARATOR.'index.php';
Yii::createWebApplication($config);
}
restore_error_handler();
restore_exception_handler();
$yiiVersion=Yii::getVersion();
echo <<<EOD
Yii Interactive Tool v1.1 (based on Yii v{$yiiVersion})
Please type 'help' for help. Type 'exit' to quit.
EOD;
$this->runShell();
}
protected function runShell()
{
// disable E_NOTICE so that the shell is more friendly
error_reporting(E_ALL ^ E_NOTICE);
$_runner_=new CConsoleCommandRunner;
$_runner_->addCommands(dirname(__FILE__).'/shell');
$_runner_->addCommands(Yii::getPathOfAlias('application.commands.shell'));
if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false)
$_runner_->addCommands($_path_);
$_commands_=$_runner_->commands;
$log=Yii::app()->log;
while(($_line_=$this->prompt("\n>>"))!==false)
{
$_line_=trim($_line_);
if($_line_==='exit')
return;
try
{
$_args_=preg_split('/[\s,]+/',rtrim($_line_,';'),-1,PREG_SPLIT_NO_EMPTY);
if(isset($_args_[0]) && isset($_commands_[$_args_[0]]))
{
$_command_=$_runner_->createCommand($_args_[0]);
array_shift($_args_);
$_command_->init();
$_command_->run($_args_);
}
else
echo eval($_line_.';');
}
catch(Exception $e)
{
if($e instanceof ShellException)
echo $e->getMessage();
else
echo $e;
}
}
}
}
class ShellException extends CException
{
}

5
framework/yiic

@ -5,10 +5,9 @@
* *
* This is the bootstrap script for running yiic on Unix/Linux. * This is the bootstrap script for running yiic on Unix/Linux.
* *
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2012 Yii Software LLC * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
require_once(__DIR__.'/yiic.php'); require_once(__DIR__ . '/yiic.php');

25
framework/yiic.php

@ -7,23 +7,12 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
// fcgi doesn't have STDIN defined by default require(__DIR__ . '/yii.php');
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
require(__DIR__.'/yii.php'); $config = array(
'controllerPath' => '@yii/console/commands',
);
$id = 'yiic';
$basePath = __DIR__ . '/console';
if(isset($config)) yii\console\Application::newInstance($config, $id, $basePath)->run();
{
$app=new \yii\console\Application($config);
$app->commandRunner->addCommands(YII_PATH.'/cli/commands');
$env=@getenv('YII_CONSOLE_COMMANDS');
if(!empty($env))
$app->commandRunner->addCommands($env);
} else
{
$app=new \yii\console\Application(array(
'basePath'=>__DIR__.'/cli',
));
}
$app->run();
Loading…
Cancel
Save