Commit c5816d23 by Carsten Brandt

Merge branch 'master' of git.yiisoft.com:yii2

* 'master' of git.yiisoft.com:yii2: Added Command::batchInsert() exception cleanup. error handling cleanup. console app cleanup. MVC cleanup MVC WIP MVC WIP MVC WIP cleanup. MVC WIP refactored logging. MVC WIP MVC WIP patched controller and action creation todo updates. Conflicts: framework/console/View.php
parents f1b62a47 a3963851
...@@ -8,9 +8,8 @@ ...@@ -8,9 +8,8 @@
*/ */
use yii\base\Exception; use yii\base\Exception;
use yii\logging\Logger;
use yii\base\InvalidCallException;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\logging\Logger;
/** /**
* Gets the application start timestamp. * Gets the application start timestamp.
...@@ -30,6 +29,11 @@ defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 0); ...@@ -30,6 +29,11 @@ defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 0);
* This constant defines the framework installation directory. * This constant defines the framework installation directory.
*/ */
defined('YII_PATH') or define('YII_PATH', __DIR__); defined('YII_PATH') or define('YII_PATH', __DIR__);
/**
* This constant defines whether error handling should be enabled. Defaults to true.
*/
defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true);
/** /**
* YiiBase is the core helper class for the Yii framework. * YiiBase is the core helper class for the Yii framework.
...@@ -121,8 +125,8 @@ class YiiBase ...@@ -121,8 +125,8 @@ class YiiBase
* *
* To import a class or a directory, one can use either path alias or class name (can be namespaced): * To import a class or a directory, one can use either path alias or class name (can be namespaced):
* *
* - `@app/components/GoogleMap`: importing the `GoogleMap` class with a path alias; * - `@application/components/GoogleMap`: importing the `GoogleMap` class with a path alias;
* - `@app/components/*`: importing the whole `components` directory with a path alias; * - `@application/components/*`: importing the whole `components` directory with a path alias;
* - `GoogleMap`: importing the `GoogleMap` class with a class name. [[autoload()]] will be used * - `GoogleMap`: importing the `GoogleMap` class with a class name. [[autoload()]] will be used
* when this class is used for the first time. * when this class is used for the first time.
* *
...@@ -189,14 +193,14 @@ class YiiBase ...@@ -189,14 +193,14 @@ class YiiBase
* *
* Note, this method does not ensure the existence of the resulting path. * Note, this method does not ensure the existence of the resulting path.
* @param string $alias alias * @param string $alias alias
* @param boolean $throwException whether to throw exception if the alias is invalid.
* @return string|boolean path corresponding to the alias, false if the root alias is not previously registered. * @return string|boolean path corresponding to the alias, false if the root alias is not previously registered.
* @throws Exception if the alias is invalid and $throwException is true.
* @see setAlias * @see setAlias
*/ */
public static function getAlias($alias, $throwException = false) public static function getAlias($alias)
{ {
if (isset(self::$aliases[$alias])) { if (!is_string($alias)) {
return false;
} elseif (isset(self::$aliases[$alias])) {
return self::$aliases[$alias]; return self::$aliases[$alias];
} elseif ($alias === '' || $alias[0] !== '@') { // not an alias } elseif ($alias === '' || $alias[0] !== '@') { // not an alias
return $alias; return $alias;
...@@ -206,11 +210,7 @@ class YiiBase ...@@ -206,11 +210,7 @@ class YiiBase
return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos); return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos);
} }
} }
if ($throwException) { return false;
throw new Exception("Invalid path alias: $alias");
} else {
return false;
}
} }
/** /**
...@@ -322,12 +322,12 @@ class YiiBase ...@@ -322,12 +322,12 @@ class YiiBase
* the class. For example, * the class. For example,
* *
* - `\app\components\GoogleMap`: fully-qualified namespaced class. * - `\app\components\GoogleMap`: fully-qualified namespaced class.
* - `@app/components/GoogleMap`: an alias * - `@application/components/GoogleMap`: an alias
* *
* Below are some usage examples: * Below are some usage examples:
* *
* ~~~ * ~~~
* $object = \Yii::createObject('@app/components/GoogleMap'); * $object = \Yii::createObject('@application/components/GoogleMap');
* $object = \Yii::createObject(array( * $object = \Yii::createObject(array(
* 'class' => '\app\components\GoogleMap', * 'class' => '\app\components\GoogleMap',
* 'apiKey' => 'xyz', * 'apiKey' => 'xyz',
...@@ -361,7 +361,7 @@ class YiiBase ...@@ -361,7 +361,7 @@ class YiiBase
$class = $config['class']; $class = $config['class'];
unset($config['class']); unset($config['class']);
} else { } else {
throw new InvalidCallException('Object configuration must be an array containing a "class" element.'); throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
} }
if (!class_exists($class, false)) { if (!class_exists($class, false)) {
......
...@@ -9,8 +9,6 @@ ...@@ -9,8 +9,6 @@
namespace yii\base; namespace yii\base;
use yii\util\ReflectionHelper;
/** /**
* Action is the base class for all controller action classes. * Action is the base class for all controller action classes.
* *
...@@ -21,6 +19,14 @@ use yii\util\ReflectionHelper; ...@@ -21,6 +19,14 @@ use yii\util\ReflectionHelper;
* will be invoked by the controller when the action is requested. * will be invoked by the controller when the action is requested.
* The `run()` method can have parameters which will be filled up * The `run()` method can have parameters which will be filled up
* with user input values automatically according to their names. * with user input values automatically according to their names.
* For example, if the `run()` method is declared as follows:
*
* ~~~
* public function run($id, $type = 'book') { ... }
* ~~~
*
* And the parameters provided for the action are: `array('id' => 1)`.
* Then the `run()` method will be invoked as `run(1)` automatically.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
...@@ -37,6 +43,7 @@ class Action extends Component ...@@ -37,6 +43,7 @@ class Action extends Component
public $controller; public $controller;
/** /**
* Constructor.
* @param string $id the ID of this action * @param string $id the ID of this action
* @param Controller $controller the controller that owns this action * @param Controller $controller the controller that owns this action
* @param array $config name-value pairs that will be used to initialize the object properties * @param array $config name-value pairs that will be used to initialize the object properties
...@@ -51,20 +58,45 @@ class Action extends Component ...@@ -51,20 +58,45 @@ class Action extends Component
/** /**
* Runs this action with the specified parameters. * Runs this action with the specified parameters.
* This method is mainly invoked by the controller. * This method is mainly invoked by the controller.
* @param array $params action parameters * @param array $params the parameters to be bound to the action's run() method.
* @return integer the exit status (0 means normal, non-zero means abnormal). * @return integer the exit status (0 means normal, non-zero means abnormal).
* @throws InvalidConfigException if the action class does not have a run() method
*/ */
public function runWithParams($params) public function runWithParams($params)
{ {
try { if (!method_exists($this, 'run')) {
$ps = ReflectionHelper::extractMethodParams($this, 'run', $params); throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
} catch (Exception $e) {
$this->controller->invalidActionParams($this, $e);
return 1;
} }
if ($params !== $ps) { $method = new \ReflectionMethod($this, 'run');
$this->controller->extraActionParams($this, $ps, $params); $args = $this->bindActionParams($method, $params);
return (int)$method->invokeArgs($this, $args);
}
/**
* Binds the given parameters to the action method.
* The returned array contains the parameters that need to be passed to the action method.
* This method calls [[Controller::validateActionParams()]] to check if any exception
* should be raised if there are missing or unknown parameters.
* @param \ReflectionMethod $method the action method reflection object
* @param array $params the supplied parameters
* @return array the parameters that can be passed to the action method
*/
protected function bindActionParams($method, $params)
{
$args = array();
$missing = array();
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$args[] = $params[$name];
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
} }
return (int)call_user_func_array(array($this, 'run'), $ps); $this->controller->validateActionParams($this, $missing, $params);
return $args;
} }
} }
...@@ -12,8 +12,7 @@ namespace yii\base; ...@@ -12,8 +12,7 @@ namespace yii\base;
/** /**
* ActionEvent represents the event parameter used for an action event. * ActionEvent represents the event parameter used for an action event.
* *
* By setting the [[isValid]] property, one may control whether to continue the life cycle of * By setting the [[isValid]] property, one may control whether to continue running the action.
* the action currently being executed.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
...@@ -25,7 +24,7 @@ class ActionEvent extends Event ...@@ -25,7 +24,7 @@ class ActionEvent extends Event
*/ */
public $action; public $action;
/** /**
* @var boolean whether the action is in valid state and its life cycle should proceed. * @var boolean whether to continue running the action.
*/ */
public $isValid = true; public $isValid = true;
...@@ -34,7 +33,7 @@ class ActionEvent extends Event ...@@ -34,7 +33,7 @@ class ActionEvent extends Event
* @param Action $action the action associated with this action event. * @param Action $action the action associated with this action event.
* @param array $config name-value pairs that will be used to initialize the object properties * @param array $config name-value pairs that will be used to initialize the object properties
*/ */
public function __construct(Action $action, $config = array()) public function __construct($action, $config = array())
{ {
$this->action = $action; $this->action = $action;
parent::__construct($config); parent::__construct($config);
......
<?php
/**
* ActionFilter class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* ActionFilter is the base class for all action filters.
*
* A filter can be applied to a controller action at different stages of its life cycle. In particular,
* it responds to the following events that are raised when an action is being executed:
*
* 1. authorize
* 2. beforeAction
* 3. beforeRender
* 4. afterRender
* 5. afterAction
*
* Derived classes may respond to these events by overriding the corresponding methods in this class.
* For example, to create an access control filter, one may override the [[authorize()]] method.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActionFilter extends Behavior
{
/**
* @var Controller the owner of this behavior. For action filters, this should be a controller object.
*/
public $owner;
/**
* @var array IDs of actions that this filter applies to.
* If this property is empty or not set, it means this filter applies to all actions.
* Note that if an action appears in [[except]], the filter will not apply to this action, even
* if the action also appears in [[only]].
* @see exception
*/
public $only;
/**
* @var array IDs of actions that this filter does NOT apply to.
*/
public $except;
public function init()
{
$this->owner->on('authorize', array($this, 'handleEvent'));
$this->owner->on('beforeAction', array($this, 'handleEvent'));
$this->owner->on('beforeRender', array($this, 'handleEvent'));
$this->owner->getEventHandlers('afterRender')->insertAt(0, array($this, 'handleEvent'));
$this->owner->getEventHandlers('afterAction')->insertAt(0, array($this, 'handleEvent'));
}
public function authorize($event)
{
}
public function beforeAction($event)
{
}
public function beforeRender($event)
{
}
public function afterRender($event)
{
}
public function afterAction($event)
{
}
public function handleEvent($event)
{
if ($this->applyTo($event->action)) {
$this->{$event->name}($event);
}
}
public function applyTo(Action $action)
{
return (empty($this->only) || in_array($action->id, $this->only, false) !== false)
&& (empty($this->except) || in_array($action->id, $this->except, false) === false);
}
}
\ No newline at end of file
...@@ -52,90 +52,46 @@ class ErrorHandler extends Component ...@@ -52,90 +52,46 @@ class ErrorHandler extends Component
* @var \Exception the exception that is being handled currently * @var \Exception the exception that is being handled currently
*/ */
public $exception; public $exception;
/**
* @var boolean whether to log errors also using error_log(). Defaults to true.
* Note that errors captured by the error handler are always logged by [[\Yii::error()]].
*/
public $logErrors = true;
public function init()
{
set_exception_handler(array($this, 'handleException'));
set_error_handler(array($this, 'handleError'), error_reporting());
}
/**
* Handles PHP execution errors such as warnings, notices.
*
* This method is used as a PHP error handler. It will simply raise an `ErrorException`.
*
* @param integer $code the level of the error raised
* @param string $message the error message
* @param string $file the filename that the error was raised in
* @param integer $line the line number the error was raised at
* @throws \ErrorException the error exception
*/
public function handleError($code, $message, $file, $line)
{
if(error_reporting()!==0) {
throw new \ErrorException($message, 0, $code, $file, $line);
}
}
/** /**
* @param \Exception $exception * @param \Exception $exception
*/ */
public function handleException($exception) public function handle($exception)
{ {
// disable error capturing to avoid recursive errors while handling exceptions
restore_error_handler();
restore_exception_handler();
$this->exception = $exception; $this->exception = $exception;
$this->logException($exception);
if ($this->discardExistingOutput) { if ($this->discardExistingOutput) {
$this->clearOutput(); $this->clearOutput();
} }
try { $this->render($exception);
$this->render($exception);
} catch (\Exception $e) {
// use the most primitive way to display exception thrown in the error view
$this->renderAsText($e);
}
try {
\Yii::$application->end(1);
} catch (Exception $e2) {
// use the most primitive way to log error occurred in end()
$msg = get_class($e2) . ': ' . $e2->getMessage() . ' (' . $e2->getFile() . ':' . $e2->getLine() . ")\n";
$msg .= $e2->getTraceAsString() . "\n";
$msg .= "Previous error:\n";
$msg .= $e2->getTraceAsString() . "\n";
$msg .= '$_SERVER=' . var_export($_SERVER, true);
error_log($msg);
exit(1);
}
} }
protected function render($exception) protected function render($exception)
{ {
if ($this->errorAction !== null) { if ($this->errorAction !== null) {
\Yii::$application->runController($this->errorAction); \Yii::$application->runAction($this->errorAction);
} elseif (\Yii::$application instanceof \yii\web\Application) { } elseif (\Yii::$application instanceof \yii\web\Application) {
if (!headers_sent()) { if (!headers_sent()) {
$errorCode = $exception instanceof HttpException ? $exception->statusCode : 500; $errorCode = $exception instanceof HttpException ? $exception->statusCode : 500;
header("HTTP/1.0 $errorCode " . get_class($exception)); header("HTTP/1.0 $errorCode " . get_class($exception));
} }
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
$this->renderAsText($exception); \Yii::$application->renderException($exception);
} else { } else {
$this->renderAsHtml($exception); $view = new View($this);
if (!YII_DEBUG || $exception instanceof Exception && $exception->causedByUser) {
$viewName = $this->errorView;
} else {
$viewName = $this->exceptionView;
}
echo $view->render($viewName, array(
'exception' => $exception,
));
} }
} else { } else {
$this->renderAsText($exception); \Yii::$application->renderException($exception);
} }
} }
...@@ -286,22 +242,6 @@ class ErrorHandler extends Component ...@@ -286,22 +242,6 @@ class ErrorHandler extends Component
return htmlspecialchars($text, ENT_QUOTES, \Yii::$application->charset); return htmlspecialchars($text, ENT_QUOTES, \Yii::$application->charset);
} }
public function logException($exception)
{
$category = get_class($exception);
if ($exception instanceof HttpException) {
/** @var $exception HttpException */
$category .= '\\' . $exception->statusCode;
} elseif ($exception instanceof \ErrorException) {
/** @var $exception \ErrorException */
$category .= '\\' . $exception->getSeverity();
}
\Yii::error((string)$exception, $category);
if ($this->logErrors) {
error_log($exception);
}
}
public function clearOutput() public function clearOutput()
{ {
// the following manual level counting is to deal with zlib.output_compression set to On // the following manual level counting is to deal with zlib.output_compression set to On
...@@ -313,22 +253,14 @@ class ErrorHandler extends Component ...@@ -313,22 +253,14 @@ class ErrorHandler extends Component
/** /**
* @param \Exception $exception * @param \Exception $exception
*/ */
public function renderAsText($exception) public function renderAsHtml($exception)
{ {
if (YII_DEBUG) { $view = new View($this);
echo $exception; if (!YII_DEBUG || $exception instanceof Exception && $exception->causedByUser) {
$viewName = $this->errorView;
} else { } else {
echo get_class($exception) . ': ' . $exception->getMessage(); $viewName = $this->exceptionView;
} }
}
/**
* @param \Exception $exception
*/
public function renderAsHtml($exception)
{
$view = new View;
$view->context = $this;
$name = !YII_DEBUG || $exception instanceof HttpException ? $this->errorView : $this->exceptionView; $name = !YII_DEBUG || $exception instanceof HttpException ? $this->errorView : $this->exceptionView;
echo $view->render($name, array( echo $view->render($name, array(
'exception' => $exception, 'exception' => $exception,
......
...@@ -17,5 +17,17 @@ namespace yii\base; ...@@ -17,5 +17,17 @@ namespace yii\base;
*/ */
class Exception extends \Exception class Exception extends \Exception
{ {
/**
* @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
*/
public $causedByUser = false;
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Exception');
}
} }
...@@ -25,6 +25,10 @@ class HttpException extends Exception ...@@ -25,6 +25,10 @@ class HttpException extends Exception
* @var integer HTTP status code, such as 403, 404, 500, etc. * @var integer HTTP status code, such as 403, 404, 500, etc.
*/ */
public $statusCode; public $statusCode;
/**
* @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
*/
public $causedByUser = true;
/** /**
* Constructor. * Constructor.
......
...@@ -9,13 +9,11 @@ ...@@ -9,13 +9,11 @@
namespace yii\base; namespace yii\base;
use yii\util\ReflectionHelper;
/** /**
* InlineAction represents an action that is defined as a controller method. * InlineAction represents an action that is defined as a controller method.
* *
* The name of the controller method should be in the format of `actionXyz` * The name of the controller method is available via [[actionMethod]] which
* where `Xyz` stands for the action ID (e.g. `actionIndex`). * is set by the [[controller]] who creates this action.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
...@@ -23,6 +21,23 @@ use yii\util\ReflectionHelper; ...@@ -23,6 +21,23 @@ use yii\util\ReflectionHelper;
class InlineAction extends Action class InlineAction extends Action
{ {
/** /**
* @var string the controller method that this inline action is associated with
*/
public $actionMethod;
/**
* @param string $id the ID of this action
* @param Controller $controller the controller that owns this action
* @param string $actionMethod the controller method that this inline action is associated with
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($id, $controller, $actionMethod, $config = array())
{
$this->actionMethod = $actionMethod;
parent::__construct($id, $controller, $config);
}
/**
* Runs this action with the specified parameters. * Runs this action with the specified parameters.
* This method is mainly invoked by the controller. * This method is mainly invoked by the controller.
* @param array $params action parameters * @param array $params action parameters
...@@ -30,16 +45,8 @@ class InlineAction extends Action ...@@ -30,16 +45,8 @@ class InlineAction extends Action
*/ */
public function runWithParams($params) public function runWithParams($params)
{ {
try { $method = new \ReflectionMethod($this->controller, $this->actionMethod);
$method = 'action' . $this->id; $args = $this->bindActionParams($method, $params);
$ps = ReflectionHelper::extractMethodParams($this->controller, $method, $params); return (int)$method->invokeArgs($this->controller, $args);
} catch (Exception $e) {
$this->controller->invalidActionParams($this, $e);
return 1;
}
if ($params !== $ps) {
$this->controller->extraActionParams($this, $ps, $params);
}
return (int)call_user_func_array(array($this->controller, $method), $ps);
} }
} }
...@@ -17,5 +17,12 @@ namespace yii\base; ...@@ -17,5 +17,12 @@ namespace yii\base;
*/ */
class InvalidCallException extends \Exception class InvalidCallException extends \Exception
{ {
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Call');
}
} }
...@@ -17,5 +17,12 @@ namespace yii\base; ...@@ -17,5 +17,12 @@ namespace yii\base;
*/ */
class InvalidConfigException extends \Exception class InvalidConfigException extends \Exception
{ {
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Configuration');
}
} }
...@@ -17,5 +17,17 @@ namespace yii\base; ...@@ -17,5 +17,17 @@ namespace yii\base;
*/ */
class InvalidRequestException extends \Exception class InvalidRequestException extends \Exception
{ {
/**
* @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
*/
public $causedByUser = true;
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Request');
}
} }
<?php
/**
* InvalidRouteException class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* InvalidRouteException represents an exception caused by an invalid route.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InvalidRouteException extends \Exception
{
/**
* @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
*/
public $causedByUser = true;
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Route');
}
}
...@@ -17,5 +17,12 @@ namespace yii\base; ...@@ -17,5 +17,12 @@ namespace yii\base;
*/ */
class NotSupportedException extends \Exception class NotSupportedException extends \Exception
{ {
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Not Supported');
}
} }
...@@ -9,55 +9,114 @@ ...@@ -9,55 +9,114 @@
namespace yii\base; namespace yii\base;
use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\util\FileHelper;
/** /**
* Theme represents an application theme. * Theme represents an application theme.
* *
* A theme is directory consisting of view and layout files which are meant to replace their
* non-themed counterparts.
*
* Theme uses [[pathMap]] to achieve the file replacement. A view or layout file will be replaced
* with its themed version if part of its path matches one of the keys in [[pathMap]].
* Then the matched part will be replaced with the corresponding array value.
*
* For example, if [[pathMap]] is `array('/www/views' => '/www/themes/basic')`,
* then the themed version for a view file `/www/views/site/index.php` will be
* `/www/themes/basic/site/index.php`.
*
* @property string $baseUrl the base URL for this theme. This is mainly used by [[getUrl()]].
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Theme extends Component class Theme extends Component
{ {
/**
* @var string the root path of this theme.
* @see pathMap
*/
public $basePath; public $basePath;
public $baseUrl; /**
* @var array the mapping between view directories and their corresponding themed versions.
* If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]].
* This property is used by [[apply()]] when a view is trying to apply the theme.
*/
public $pathMap;
private $_baseUrl;
/**
* Initializes the theme.
* @throws InvalidConfigException if [[basePath]] is not set.
*/
public function init() public function init()
{ {
if ($this->basePath !== null) { parent::init();
$this->basePath = \Yii::getAlias($this->basePath, true); if (empty($this->pathMap)) {
} else { if ($this->basePath !== null) {
throw new InvalidConfigException("Theme.basePath must be set."); $this->basePath = FileHelper::ensureDirectory($this->basePath);
$this->pathMap = array(Yii::$application->getBasePath() => $this->basePath);
} else {
throw new InvalidConfigException("Theme::basePath must be set.");
}
} }
if ($this->baseUrl !== null) { $paths = array();
$this->baseUrl = \Yii::getAlias($this->baseUrl, true); foreach ($this->pathMap as $from => $to) {
} else { $paths[FileHelper::normalizePath($from) . DIRECTORY_SEPARATOR] = FileHelper::normalizePath($to) . DIRECTORY_SEPARATOR;
throw new InvalidConfigException("Theme.baseUrl must be set.");
} }
$this->pathMap = $paths;
}
/**
* Returns the base URL for this theme.
* The method [[getUrl()]] will prefix this to the given URL.
* @return string the base URL for this theme.
*/
public function getBaseUrl()
{
return $this->_baseUrl;
}
/**
* Sets the base URL for this theme.
* @param string $value the base URL for this theme.
*/
public function setBaseUrl($value)
{
$this->_baseUrl = rtrim(Yii::getAlias($value), '/');
} }
/** /**
* @param Application|Module|Controller|Object $context * Converts a file to a themed file if possible.
* @return string * If there is no corresponding themed file, the original file will be returned.
* @param string $path the file to be themed
* @return string the themed file, or the original file if the themed version is not available.
*/ */
public function getViewPath($context = null) public function apply($path)
{ {
$viewPath = $this->basePath . DIRECTORY_SEPARATOR . 'views'; $path = FileHelper::normalizePath($path);
if ($context === null || $context instanceof Application) { foreach ($this->pathMap as $from => $to) {
return $viewPath; if (strpos($path, $from) === 0) {
} elseif ($context instanceof Controller || $context instanceof Module) { $n = strlen($from);
return $viewPath . DIRECTORY_SEPARATOR . $context->getUniqueId(); $file = $to . substr($path, $n);
} else { if (is_file($file)) {
return $viewPath . DIRECTORY_SEPARATOR . str_replace('\\', '_', get_class($context)); return $file;
}
}
} }
return $path;
} }
/** /**
* @param Module $module * Converts a relative URL into an absolute URL using [[basePath]].
* @return string * @param string $url the relative URL to be converted.
* @return string the absolute URL
*/ */
public function getLayoutPath($module = null) public function getUrl($url)
{ {
return $this->getViewPath($module) . DIRECTORY_SEPARATOR . 'layouts'; return $this->baseUrl . '/' . ltrim($url, '/');
} }
} }
...@@ -17,5 +17,12 @@ namespace yii\base; ...@@ -17,5 +17,12 @@ namespace yii\base;
*/ */
class UnknownMethodException extends \Exception class UnknownMethodException extends \Exception
{ {
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Unknown Method');
}
} }
...@@ -17,5 +17,12 @@ namespace yii\base; ...@@ -17,5 +17,12 @@ namespace yii\base;
*/ */
class UnknownPropertyException extends \Exception class UnknownPropertyException extends \Exception
{ {
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Unknown Property');
}
} }
...@@ -49,7 +49,7 @@ class Widget extends Component ...@@ -49,7 +49,7 @@ class Widget extends Component
public function getId($autoGenerate = true) public function getId($autoGenerate = true)
{ {
if ($autoGenerate && $this->_id === null) { if ($autoGenerate && $this->_id === null) {
$this->_id = 'yw' . self::$_counter++; $this->_id = 'w' . self::$_counter++;
} }
return $this->_id; return $this->_id;
} }
...@@ -80,7 +80,7 @@ class Widget extends Component ...@@ -80,7 +80,7 @@ class Widget extends Component
* To determine which view file should be rendered, the method calls [[findViewFile()]] which * To determine which view file should be rendered, the method calls [[findViewFile()]] which
* will search in the directories as specified by [[basePath]]. * will search in the directories as specified by [[basePath]].
* *
* View name can be a path alias representing an absolute file path (e.g. `@app/views/layout/index`), * View name can be a path alias representing an absolute file path (e.g. `@application/views/layout/index`),
* or a path relative to [[basePath]]. The file suffix is optional and defaults to `.php` if not given * or a path relative to [[basePath]]. The file suffix is optional and defaults to `.php` if not given
* in the view name. * in the view name.
* *
...@@ -102,4 +102,16 @@ class Widget extends Component ...@@ -102,4 +102,16 @@ class Widget extends Component
{ {
return new View($this); return new View($this);
} }
/**
* Returns the directory containing the view files for this widget.
* The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
* @return string the directory containing the view files for this widget.
*/
public function getViewPath()
{
$className = get_class($this);
$class = new \ReflectionClass($className);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
} }
\ No newline at end of file
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
namespace yii\console; namespace yii\console;
use yii\base\Exception; use yii\base\Exception;
use yii\util\ReflectionHelper; use yii\base\InvalidRouteException;
/** /**
* Application represents a console application. * Application represents a console application.
...@@ -85,40 +85,36 @@ class Application extends \yii\base\Application ...@@ -85,40 +85,36 @@ class Application extends \yii\base\Application
* Processes the request. * Processes the request.
* The request is represented in terms of a controller route and action parameters. * The request is represented in terms of a controller route and action parameters.
* @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal) * @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 processRequest() public function processRequest()
{ {
/** @var $request Request */ /** @var $request Request */
$request = $this->getRequest(); $request = $this->getRequest();
if ($request->getIsConsoleRequest()) { if ($request->getIsConsoleRequest()) {
return $this->runController($request->route, $request->params); return $this->runAction($request->route, $request->params);
} else { } else {
die('This script must be run from the command line.'); echo "Error: this script must be run from the command line.";
return 1;
} }
} }
/** /**
* Runs a controller with the given route and parameters. * Runs a controller action specified by a route.
* @param string $route the route (e.g. `post/create`) * This method parses the specified route and creates the corresponding child module(s), controller and action
* @param array $params the parameters to be passed to the controller action * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
* @return integer the exit status (0 means normal, non-zero values mean abnormal) * If the route is empty, the method will use [[defaultRoute]].
* @throws Exception if the route cannot be resolved into a controller * @param string $route the route that specifies the action.
* @param array $params the parameters to be passed to the action
* @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
*/ */
public function runController($route, $params = array()) public function runAction($route, $params = array())
{ {
$result = $this->createController($route); try {
if ($result === false) { return parent::runAction($route, $params);
throw new Exception(\Yii::t('yii', 'Unable to resolve the request.')); } catch (InvalidRouteException $e) {
echo "Error: unknown command \"$route\".\n";
return 1;
} }
/** @var $controller \yii\console\Controller */
list($controller, $action) = $result;
$priorController = $this->controller;
$this->controller = $controller;
$params = ReflectionHelper::initObjectWithParams($controller, $params);
$status = $controller->run($action, $params);
$this->controller = $priorController;
return $status;
} }
/** /**
...@@ -152,4 +148,9 @@ class Application extends \yii\base\Application ...@@ -152,4 +148,9 @@ class Application extends \yii\base\Application
), ),
)); ));
} }
public function usageError($message)
{
}
} }
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
namespace yii\console; namespace yii\console;
use Yii;
use yii\base\Action; use yii\base\Action;
use yii\base\Exception; use yii\base\InvalidRequestException;
use yii\base\InvalidRouteException;
/** /**
* Controller is the base class of console command classes. * Controller is the base class of console command classes.
...@@ -30,72 +32,56 @@ use yii\base\Exception; ...@@ -30,72 +32,56 @@ use yii\base\Exception;
class Controller extends \yii\base\Controller class Controller extends \yii\base\Controller
{ {
/** /**
* This method is invoked when the request parameters do not satisfy the requirement of the specified action. * @var boolean whether the call of [[confirm()]] requires a user input.
* The default implementation will throw an exception. * If false, [[confirm()]] will always return true no matter what user enters or not.
* @param Action $action the action being executed
* @param Exception $exception the exception about the invalid parameters
*/ */
public function invalidActionParams($action, $exception) public $interactive = true;
{
echo \Yii::t('yii', 'Error: {message}', array(
'{message}' => $exception->getMessage(),
));
\Yii::$application->end(1);
}
/** /**
* This method is invoked when extra parameters are provided to an action while it is executed. * Runs an action with the specified action ID and parameters.
* @param Action $action the action being executed * If the action ID is empty, the method will use [[defaultAction]].
* @param array $expected the expected action parameters (name => value) * @param string $id the ID of the action to be executed.
* @param array $actual the actual action parameters (name => value) * @param array $params the parameters (name-value pairs) to be passed to the action.
* @return integer the status of the action execution. 0 means normal, other values mean abnormal.
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
* @see createAction
*/ */
public function extraActionParams($action, $expected, $actual) public function runAction($id, $params = array())
{ {
unset($expected['args'], $actual['args']); if ($params !== array()) {
$class = new \ReflectionClass($this);
$keys = array_diff(array_keys($actual), array_keys($expected)); foreach ($params as $name => $value) {
if (!empty($keys)) { if ($class->hasProperty($name)) {
echo \Yii::t('yii', 'Error: Unknown parameter(s): {params}', array( $property = $class->getProperty($name);
'{params}' => implode(', ', $keys), if ($property->isPublic() && !$property->isStatic() && $property->getDeclaringClass()->getName() === get_class($this)) {
)) . "\n"; $this->$name = $value;
\Yii::$application->end(1); unset($params[$name]);
}
}
}
} }
return parent::runAction($id, $params);
} }
/** /**
* Reads input via the readline PHP extension if that's available, or fgets() if readline is not installed. * Validates the parameter being bound to actions.
* * This method is invoked when parameters are being bound to the currently requested action.
* @param string $message to echo out before waiting for user input * Child classes may override this method to throw exceptions when there are missing and/or unknown parameters.
* @param string $default the default string to be returned when user does not write anything. * @param Action $action the currently requested action
* Defaults to null, means that default string is disabled. * @param array $missingParams the names of the missing parameters
* @return mixed line read as a string, or false if input has been closed * @param array $unknownParams the unknown parameters (name=>value)
* @throws InvalidRequestException if there are missing or unknown parameters
*/ */
public function prompt($message, $default = null) public function validateActionParams($action, $missingParams, $unknownParams)
{ {
if($default !== null) { if (!empty($missingParams)) {
$message .= " [$default] "; throw new InvalidRequestException(Yii::t('yii', 'Missing required options: {params}', array(
} '{params}' => implode(', ', $missingParams),
else { )));
$message .= ' '; } elseif (!empty($unknownParams)) {
} throw new InvalidRequestException(Yii::t('yii', 'Unknown options: {params}', array(
'{params}' => implode(', ', $unknownParams),
if(extension_loaded('readline')) { )));
$input = readline($message);
if($input !== false) {
readline_add_history($input);
}
}
else {
echo $message;
$input = fgets(STDIN);
}
if($input === false) {
return false;
}
else {
$input = trim($input);
return ($input === '' && $default !== null) ? $default : $input;
} }
} }
...@@ -108,9 +94,23 @@ class Controller extends \yii\base\Controller ...@@ -108,9 +94,23 @@ class Controller extends \yii\base\Controller
*/ */
public function confirm($message, $default = false) public function confirm($message, $default = false)
{ {
echo $message . ' (yes|no) [' . ($default ? 'yes' : 'no') . ']:'; if ($this->interactive) {
echo $message . ' (yes|no) [' . ($default ? 'yes' : 'no') . ']:';
$input = trim(fgets(STDIN));
return empty($input) ? $default : !strncasecmp($input, 'y', 1);
} else {
return true;
}
}
public function usageError($message)
{
echo "\nError: $message\n";
Yii::$application->end(1);
}
$input = trim(fgets(STDIN)); public function globalOptions()
return empty($input) ? $default : !strncasecmp($input, 'y', 1); {
return array();
} }
} }
\ No newline at end of file
...@@ -165,8 +165,8 @@ class CreateController extends Controller ...@@ -165,8 +165,8 @@ class CreateController extends Controller
} }
/** /**
* @param string $path1 abosolute path * @param string $path1 absolute path
* @param string $path2 abosolute path * @param string $path2 absolute path
* *
* @return string relative path * @return string relative path
*/ */
......
...@@ -12,6 +12,7 @@ namespace yii\console\controllers; ...@@ -12,6 +12,7 @@ namespace yii\console\controllers;
use yii\base\Application; use yii\base\Application;
use yii\base\InlineAction; use yii\base\InlineAction;
use yii\console\Controller; use yii\console\Controller;
use yii\util\StringHelper;
/** /**
* This command provides help information about console commands. * This command provides help information about console commands.
...@@ -54,16 +55,16 @@ class HelpController extends Controller ...@@ -54,16 +55,16 @@ class HelpController extends Controller
} else { } else {
$result = \Yii::$application->createController($args[0]); $result = \Yii::$application->createController($args[0]);
if ($result === false) { if ($result === false) {
echo "Unknown command: " . $args[0] . "\n"; echo "Error: no help for unknown command \"{$args[0]}\".\n";
return 1; return 1;
} }
list($controller, $action) = $result; list($controller, $actionID) = $result;
if ($action === '') { if ($actionID === '') {
$status = $this->getControllerHelp($controller); $status = $this->getControllerHelp($controller);
} else { } else {
$status = $this->getActionHelp($controller, $action); $status = $this->getActionHelp($controller, $actionID);
} }
} }
return $status; return $status;
...@@ -87,13 +88,13 @@ class HelpController extends Controller ...@@ -87,13 +88,13 @@ class HelpController extends Controller
*/ */
public function getActions($controller) public function getActions($controller)
{ {
$actions = array_keys($controller->actionMap); $actions = array_keys($controller->actions());
$class = new \ReflectionClass($controller); $class = new \ReflectionClass($controller);
foreach ($class->getMethods() as $method) { foreach ($class->getMethods() as $method) {
/** @var $method \ReflectionMethod */ /** @var $method \ReflectionMethod */
$name = $method->getName(); $name = $method->getName();
if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0) { if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
$actions[] = lcfirst(substr($name, 6)); $actions[] = StringHelper::camel2id(substr($name, 6));
} }
} }
sort($actions); sort($actions);
...@@ -107,11 +108,7 @@ class HelpController extends Controller ...@@ -107,11 +108,7 @@ class HelpController extends Controller
*/ */
protected function getModuleCommands($module) protected function getModuleCommands($module)
{ {
if ($module instanceof Application) { $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';
$prefix = '';
} else {
$prefix = $module->getUniqueId() . '/';
}
$commands = array(); $commands = array();
foreach (array_keys($module->controllerMap) as $id) { foreach (array_keys($module->controllerMap) as $id) {
...@@ -145,12 +142,12 @@ class HelpController extends Controller ...@@ -145,12 +142,12 @@ class HelpController extends Controller
{ {
$commands = $this->getCommands(); $commands = $this->getCommands();
if ($commands !== array()) { if ($commands !== array()) {
echo "\n Usage: yiic <command-name> [...options...]\n\n"; echo "\nUsage: yiic <command-name> [...options...]\n\n";
echo "The following commands are available:\n"; echo "The following commands are available:\n\n";
foreach ($commands as $command) { foreach ($commands as $command) {
echo " - $command\n"; echo " * $command\n";
} }
echo "\nTo see individual command help, enter:\n"; echo "\nTo see the help of each command, enter:\n";
echo "\n yiic help <command-name>\n"; echo "\n yiic help <command-name>\n";
} else { } else {
echo "\nNo commands are found.\n"; echo "\nNo commands are found.\n";
...@@ -195,7 +192,7 @@ class HelpController extends Controller ...@@ -195,7 +192,7 @@ class HelpController extends Controller
$prefix = $controller->getUniqueId(); $prefix = $controller->getUniqueId();
foreach ($actions as $action) { foreach ($actions as $action) {
if ($controller->defaultAction === $action) { if ($controller->defaultAction === $action) {
echo " * $prefix/$action (default)\n"; echo " * $prefix (default)\n";
} else { } else {
echo " * $prefix/$action\n"; echo " * $prefix/$action\n";
} }
...@@ -216,7 +213,7 @@ class HelpController extends Controller ...@@ -216,7 +213,7 @@ class HelpController extends Controller
{ {
$action = $controller->createAction($actionID); $action = $controller->createAction($actionID);
if ($action === null) { if ($action === null) {
echo "Unknown sub-command: " . $controller->getUniqueId() . "/$actionID\n"; echo 'Error: no help for unknown sub-command "' . $controller->getUniqueId() . "/$actionID\".\n";
return 1; return 1;
} }
if ($action instanceof InlineAction) { if ($action instanceof InlineAction) {
...@@ -312,7 +309,7 @@ class HelpController extends Controller ...@@ -312,7 +309,7 @@ class HelpController extends Controller
{ {
$options = array(); $options = array();
foreach ($class->getProperties() as $property) { foreach ($class->getProperties() as $property) {
if (!$property->isPublic() || $property->isStatic() || $property->getDeclaringClass()->getName() === 'yii\base\Controller') { if (!$property->isPublic() || $property->isStatic() || $property->getDeclaringClass()->getName() !== get_class($controller)) {
continue; continue;
} }
$name = $property->getName(); $name = $property->getName();
......
<?php
/**
* ShellController class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console\controllers;
use yii\console\Controller;
/**
* This command 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>
* @since 2.0
*/
class ShellController extends Controller
{
/**
* @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::$application->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
{
}
\ No newline at end of file
...@@ -485,16 +485,41 @@ class Command extends \yii\base\Component ...@@ -485,16 +485,41 @@ class Command extends \yii\base\Component
* *
* @param string $table the table that new rows will be inserted into. * @param string $table the table that new rows will be inserted into.
* @param array $columns the column data (name=>value) to be inserted into the table. * @param array $columns the column data (name=>value) to be inserted into the table.
* @param array $params the parameters to be bound to the command
* @return Command the command object itself * @return Command the command object itself
*/ */
public function insert($table, $columns, $params = array()) public function insert($table, $columns)
{ {
$params = array();
$sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
return $this->setSql($sql)->bindValues($params); return $this->setSql($sql)->bindValues($params);
} }
/** /**
* Creates a batch INSERT command.
* For example,
*
* ~~~
* $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
* array('Tom', 30),
* array('Jane', 20),
* array('Linda', 25),
* ))->execute();
* ~~~
*
* Not that the values in each row must match the corresponding column names.
*
* @param string $table the table that new rows will be inserted into.
* @param array $columns the column names
* @param array $rows the rows to be batch inserted into the table
* @return Command the command object itself
*/
public function batchInsert($table, $columns, $rows)
{
$sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows);
return $this->setSql($sql);
}
/**
* Creates an UPDATE command. * Creates an UPDATE command.
* For example, * For example,
* *
......
...@@ -34,4 +34,12 @@ class Exception extends \yii\base\Exception ...@@ -34,4 +34,12 @@ class Exception extends \yii\base\Exception
$this->errorInfo = $errorInfo; $this->errorInfo = $errorInfo;
parent::__construct($message, $code); parent::__construct($message, $code);
} }
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Database Exception');
}
} }
\ No newline at end of file
...@@ -115,6 +115,33 @@ class QueryBuilder extends \yii\base\Object ...@@ -115,6 +115,33 @@ class QueryBuilder extends \yii\base\Object
} }
/** /**
* Generates a batch INSERT SQL statement.
* For example,
*
* ~~~
* $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
* array('Tom', 30),
* array('Jane', 20),
* array('Linda', 25),
* ))->execute();
* ~~~
*
* Not that the values in each row must match the corresponding column names.
*
* @param string $table the table that new rows will be inserted into.
* @param array $columns the column names
* @param array $rows the rows to be batch inserted into the table
* @param array $params the parameters to be bound to the command
* @return string the batch INSERT SQL statement
* @throws NotSupportedException if this is not supported by the underlying DBMS
*/
public function batchInsert($table, $columns, $rows, $params = array())
{
throw new NotSupportedException($this->db->getDriverName() . ' does not support batch insert.');
}
/**
* Creates an UPDATE SQL statement. * Creates an UPDATE SQL statement.
* For example, * For example,
* *
......
...@@ -129,4 +129,39 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -129,4 +129,39 @@ class QueryBuilder extends \yii\db\QueryBuilder
{ {
return 'SET FOREIGN_KEY_CHECKS=' . ($check ? 1 : 0); return 'SET FOREIGN_KEY_CHECKS=' . ($check ? 1 : 0);
} }
/**
* Generates a batch INSERT SQL statement.
* For example,
*
* ~~~
* $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
* array('Tom', 30),
* array('Jane', 20),
* array('Linda', 25),
* ))->execute();
* ~~~
*
* Not that the values in each row must match the corresponding column names.
*
* @param string $table the table that new rows will be inserted into.
* @param array $columns the column names
* @param array $rows the rows to be batch inserted into the table
* @return string the batch INSERT SQL statement
*/
public function batchInsert($table, $columns, $rows)
{
$values = array();
foreach ($rows as $row) {
$vs = array();
foreach ($row as $value) {
$vs[] = is_string($value) ? $this->db->quoteValue($value) : $value;
}
$values[] = $vs;
}
return 'INSERT INTO ' . $this->db->quoteTableName($table)
. ' (' . implode(', ', $columns) . ') VALUES ('
. implode(', ', $values) . ')';
}
} }
...@@ -89,16 +89,17 @@ class DbTarget extends Target ...@@ -89,16 +89,17 @@ class DbTarget extends Target
} }
/** /**
* Stores log [[messages]] to DB. * Stores log messages to DB.
* @param boolean $final whether this method is called at the end of the current application * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure
* of each message.
*/ */
public function exportMessages($final) public function export($messages)
{ {
$db = $this->getDb(); $db = $this->getDb();
$tableName = $db->quoteTableName($this->tableName); $tableName = $db->quoteTableName($this->tableName);
$sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)"; $sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)";
$command = $db->createCommand($sql); $command = $db->createCommand($sql);
foreach ($this->messages as $message) { foreach ($messages as $message) {
$command->bindValues(array( $command->bindValues(array(
':level' => $message[1], ':level' => $message[1],
':category' => $message[2], ':category' => $message[2],
......
...@@ -39,13 +39,14 @@ class EmailTarget extends Target ...@@ -39,13 +39,14 @@ class EmailTarget extends Target
public $headers = array(); public $headers = array();
/** /**
* Sends log [[messages]] to specified email addresses. * Sends log messages to specified email addresses.
* @param boolean $final whether this method is called at the end of the current application * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure
* of each message.
*/ */
public function exportMessages($final) public function export($messages)
{ {
$body = ''; $body = '';
foreach ($this->messages as $message) { foreach ($messages as $message) {
$body .= $this->formatMessage($message); $body .= $this->formatMessage($message);
} }
$body = wordwrap($body, 70); $body = wordwrap($body, 70);
......
...@@ -65,19 +65,28 @@ class FileTarget extends Target ...@@ -65,19 +65,28 @@ class FileTarget extends Target
} }
/** /**
* Sends log [[messages]] to specified email addresses. * Sends log messages to specified email addresses.
* @param boolean $final whether this method is called at the end of the current application * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure
* of each message.
*/ */
public function exportMessages($final) public function export($messages)
{ {
$text = '';
foreach ($messages as $message) {
$text .= $this->formatMessage($message);
}
$fp = @fopen($this->logFile, 'a');
@flock($fp, LOCK_EX);
if (@filesize($this->logFile) > $this->maxFileSize * 1024) { if (@filesize($this->logFile) > $this->maxFileSize * 1024) {
$this->rotateFiles(); $this->rotateFiles();
@flock($fp,LOCK_UN);
@fclose($fp);
@file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
} else {
@fwrite($fp, $text);
@flock($fp,LOCK_UN);
@fclose($fp);
} }
$messages = array();
foreach ($this->messages as $message) {
$messages[] = $this->formatMessage($message);
}
@file_put_contents($this->logFile, implode('', $messages), FILE_APPEND | LOCK_EX);
} }
/** /**
......
...@@ -8,16 +8,13 @@ ...@@ -8,16 +8,13 @@
*/ */
namespace yii\logging; namespace yii\logging;
use yii\base\InvalidConfigException;
use yii\base\Event;
use yii\base\Exception;
/** /**
* Logger records logged messages in memory. * Logger records logged messages in memory.
* *
* When [[flushInterval()]] is reached or when application terminates, it will * When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]]
* call [[flush()]] to send logged messages to different log targets, such as * to send logged messages to different log targets, such as file, email, Web.
* file, email, Web.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
...@@ -25,15 +22,6 @@ use yii\base\Exception; ...@@ -25,15 +22,6 @@ use yii\base\Exception;
class Logger extends \yii\base\Component class Logger extends \yii\base\Component
{ {
/** /**
* @event Event an event that is triggered when [[flush()]] is called.
*/
const EVENT_FLUSH = 'flush';
/**
* @event Event an event that is triggered when [[flush()]] is called at the end of application.
*/
const EVENT_FINAL_FLUSH = 'finalFlush';
/**
* Error message level. An error message is one that indicates the abnormal termination of the * Error message level. An error message is one that indicates the abnormal termination of the
* application and may require developer's handling. * application and may require developer's handling.
*/ */
...@@ -82,7 +70,7 @@ class Logger extends \yii\base\Component ...@@ -82,7 +70,7 @@ class Logger extends \yii\base\Component
* *
* ~~~ * ~~~
* array( * array(
* [0] => message (mixed) * [0] => message (mixed, can be a string or some complex data, such as an exception object)
* [1] => level (integer) * [1] => level (integer)
* [2] => category (string) * [2] => category (string)
* [3] => timestamp (float, obtained by microtime(true)) * [3] => timestamp (float, obtained by microtime(true))
...@@ -90,6 +78,10 @@ class Logger extends \yii\base\Component ...@@ -90,6 +78,10 @@ class Logger extends \yii\base\Component
* ~~~ * ~~~
*/ */
public $messages = array(); public $messages = array();
/**
* @var Router the log target router registered with this logger.
*/
public $router;
/** /**
* Initializes the logger by registering [[flush()]] as a shutdown function. * Initializes the logger by registering [[flush()]] as a shutdown function.
...@@ -138,7 +130,9 @@ class Logger extends \yii\base\Component ...@@ -138,7 +130,9 @@ class Logger extends \yii\base\Component
*/ */
public function flush($final = false) public function flush($final = false)
{ {
$this->trigger($final ? self::EVENT_FINAL_FLUSH : self::EVENT_FLUSH); if ($this->router) {
$this->router->dispatch($this->messages, $final);
}
$this->messages = array(); $this->messages = array();
} }
...@@ -149,7 +143,7 @@ class Logger extends \yii\base\Component ...@@ -149,7 +143,7 @@ class Logger extends \yii\base\Component
* of [[YiiBase]] class file. * of [[YiiBase]] class file.
* @return float the total elapsed time in seconds for current request. * @return float the total elapsed time in seconds for current request.
*/ */
public function getExecutionTime() public function getElapsedTime()
{ {
return microtime(true) - YII_BEGIN_TIME; return microtime(true) - YII_BEGIN_TIME;
} }
...@@ -218,7 +212,7 @@ class Logger extends \yii\base\Component ...@@ -218,7 +212,7 @@ class Logger extends \yii\base\Component
if (($last = array_pop($stack)) !== null && $last[0] === $token) { if (($last = array_pop($stack)) !== null && $last[0] === $token) {
$timings[] = array($token, $category, $timestamp - $last[3]); $timings[] = array($token, $category, $timestamp - $last[3]);
} else { } else {
throw new Exception("Unmatched profiling block: $token"); throw new InvalidConfigException("Unmatched profiling block: $token");
} }
} }
} }
...@@ -231,5 +225,4 @@ class Logger extends \yii\base\Component ...@@ -231,5 +225,4 @@ class Logger extends \yii\base\Component
return $timings; return $timings;
} }
} }
...@@ -81,26 +81,21 @@ class Router extends Component ...@@ -81,26 +81,21 @@ class Router extends Component
$this->targets[$name] = Yii::createObject($target); $this->targets[$name] = Yii::createObject($target);
} }
} }
Yii::getLogger()->router = $this;
Yii::getLogger()->on(Logger::EVENT_FLUSH, array($this, 'processMessages'));
Yii::getLogger()->on(Logger::EVENT_FINAL_FLUSH, array($this, 'processMessages'));
} }
/** /**
* Retrieves and processes log messages from the system logger. * Dispatches log messages to [[targets]].
* This method mainly serves the event handler to the [[Logger::EVENT_FLUSH]] event * This method is called by [[Logger]] when its [[Logger::flush()]] method is called.
* and the [[Logger::EVENT_FINAL_FLUSH]] event. * It will forward the messages to each log target registered in [[targets]].
* It will retrieve the available log messages from the [[Yii::getLogger()|system logger]] * @param array $messages the messages to be processed
* and invoke the registered [[targets|log targets]] to do the actual processing. * @param boolean $final whether this is the final call during a request cycle
* @param \yii\base\Event $event event parameter
*/ */
public function processMessages($event) public function dispatch($messages, $final = false)
{ {
$messages = Yii::getLogger()->messages;
$final = $event->name === Logger::EVENT_FINAL_FLUSH;
foreach ($this->targets as $target) { foreach ($this->targets as $target) {
if ($target->enabled) { if ($target->enabled) {
$target->processMessages($messages, $final); $target->collect($messages, $final);
} }
} }
} }
......
...@@ -50,15 +50,6 @@ abstract class Target extends \yii\base\Component ...@@ -50,15 +50,6 @@ abstract class Target extends \yii\base\Component
*/ */
public $except = array(); public $except = array();
/** /**
* @var boolean whether to prefix each log message with the current session ID. Defaults to false.
*/
public $prefixSession = false;
/**
* @var boolean whether to prefix each log message with the current user name and ID. Defaults to false.
* @see \yii\web\User
*/
public $prefixUser = false;
/**
* @var boolean whether to log a message containing the current user name and ID. Defaults to false. * @var boolean whether to log a message containing the current user name and ID. Defaults to false.
* @see \yii\web\User * @see \yii\web\User
*/ */
...@@ -77,19 +68,18 @@ abstract class Target extends \yii\base\Component ...@@ -77,19 +68,18 @@ abstract class Target extends \yii\base\Component
public $exportInterval = 1000; public $exportInterval = 1000;
/** /**
* @var array the messages that are retrieved from the logger so far by this log target. * @var array the messages that are retrieved from the logger so far by this log target.
* @see autoExport
*/ */
public $messages = array(); private $_messages = array();
private $_levels = 0; private $_levels = 0;
/** /**
* Exports log messages to a specific destination. * Exports log messages to a specific destination.
* Child classes must implement this method. Note that you may need * Child classes must implement this method.
* to clean up [[messages]] in this method to avoid re-exporting messages. * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure
* @param boolean $final whether this method is called at the end of the current application * of each message.
*/ */
abstract public function exportMessages($final); abstract public function export($messages);
/** /**
* Processes the given log messages. * Processes the given log messages.
...@@ -99,45 +89,16 @@ abstract class Target extends \yii\base\Component ...@@ -99,45 +89,16 @@ abstract class Target extends \yii\base\Component
* of each message. * of each message.
* @param boolean $final whether this method is called at the end of the current application * @param boolean $final whether this method is called at the end of the current application
*/ */
public function processMessages($messages, $final) public function collect($messages, $final)
{ {
$messages = $this->filterMessages($messages); $this->_messages = array($this->_messages, $this->filterMessages($messages));
$this->messages = array_merge($this->messages, $messages); $count = count($this->_messages);
$count = count($this->messages);
if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
$this->prepareExport($final); if (($context = $this->getContextMessage()) !== '') {
$this->exportMessages($final); $this->_messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME);
$this->messages = array();
}
}
/**
* Prepares the [[messages]] for exporting.
* This method will modify each message by prepending extra information
* if [[prefixSession]] and/or [[prefixUser]] are set true.
* It will also add an additional message showing context information if
* [[logUser]] and/or [[logVars]] are set.
* @param boolean $final whether this method is called at the end of the current application
*/
protected function prepareExport($final)
{
$prefix = array();
if ($this->prefixSession && ($id = session_id()) !== '') {
$prefix[] = "[$id]";
}
if ($this->prefixUser && ($user = \Yii::$application->getComponent('user', false)) !== null) {
$prefix[] = '[' . $user->getName() . ']';
$prefix[] = '[' . $user->getId() . ']';
}
if ($prefix !== array()) {
$prefix = implode(' ', $prefix);
foreach ($this->messages as $i => $message) {
$this->messages[$i][0] = $prefix . ' ' . $this->messages[$i][0];
} }
} $this->export($this->_messages);
if ($final && ($context = $this->getContextMessage()) !== '') { $this->_messages = array();
$this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME);
} }
} }
...@@ -164,7 +125,7 @@ abstract class Target extends \yii\base\Component ...@@ -164,7 +125,7 @@ abstract class Target extends \yii\base\Component
/** /**
* @return integer the message levels that this target is interested in. This is a bitmap of * @return integer the message levels that this target is interested in. This is a bitmap of
* level values. Defaults to 0, meaning all available levels. * level values. Defaults to 0, meaning all available levels.
*/ */
public function getLevels() public function getLevels()
{ {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
namespace yii\util; namespace yii\util;
use yii\base\Exception; use yii\base\Exception;
use yii\base\InvalidConfigException;
/** /**
* Filesystem helper * Filesystem helper
...@@ -37,7 +38,7 @@ class FileHelper ...@@ -37,7 +38,7 @@ class FileHelper
* If the given path does not refer to an existing directory, an exception will be thrown. * If the given path does not refer to an existing directory, an exception will be thrown.
* @param string $path the given path. This can also be a path alias. * @param string $path the given path. This can also be a path alias.
* @return string the normalized path * @return string the normalized path
* @throws Exception if the path does not refer to an existing directory. * @throws InvalidConfigException if the path does not refer to an existing directory.
*/ */
public static function ensureDirectory($path) public static function ensureDirectory($path)
{ {
...@@ -45,11 +46,25 @@ class FileHelper ...@@ -45,11 +46,25 @@ class FileHelper
if ($p !== false && ($p = realpath($p)) !== false && is_dir($p)) { if ($p !== false && ($p = realpath($p)) !== false && is_dir($p)) {
return $p; return $p;
} else { } else {
throw new Exception('Directory does not exist: ' . $path); throw new InvalidConfigException('Directory does not exist: ' . $path);
} }
} }
/** /**
* Normalizes a file/directory path.
* After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`,
* and any trailing directory separators will be removed. For example, '/home\demo/' on Linux
* will be normalized as '/home/demo'.
* @param string $path the file/directory path to be normalized
* @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
* @return string the normalized file/directory path
*/
public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
{
return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds);
}
/**
* Returns the localized version of a specified file. * Returns the localized version of a specified file.
* *
* The searching is based on the specified language code. In particular, * The searching is based on the specified language code. In particular,
......
<?php
/**
* ReflectionHelper class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\util;
use yii\base\Exception;
/**
* ReflectionHelper
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ReflectionHelper
{
/**
* Prepares parameters so that they can be bound to the specified method.
* This method converts the input parameters into an array that can later be
* passed to `call_user_func_array()` when calling the specified method.
* The conversion is based on the matching of method parameter names
* and the input array keys. For example,
*
* ~~~
* class Foo {
* function bar($a, $b) { ... }
* }
* $object = new Foo;
* $params = array('b' => 2, 'c' => 3, 'a' => 1);
* var_export(ReflectionHelper::extractMethodParams($object, 'bar', $params));
* // output: array('a' => 1, 'b' => 2);
* ~~~
*
* @param object|string $object the object or class name that owns the specified method
* @param string $method the method name
* @param array $params the parameters in terms of name-value pairs
* @return array parameters that are needed by the method only and
* can be passed to the method via `call_user_func_array()`.
* @throws Exception if any required method parameter is not found in the given parameters
*/
public static function extractMethodParams($object, $method, $params)
{
$m = new \ReflectionMethod($object, $method);
$ps = array();
foreach ($m->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$ps[$name] = $params[$name];
} elseif ($param->isDefaultValueAvailable()) {
$ps[$name] = $param->getDefaultValue();
} else {
throw new Exception(\Yii::t('yii', 'Missing required parameter "{name}".', array('{name}' => $name)));
}
}
return $ps;
}
/**
* Initializes an object with the given parameters.
* Only the public non-static properties of the object will be initialized, and their names must
* match the given parameter names. For example,
*
* ~~~
* class Foo {
* public $a;
* protected $b;
* }
* $object = new Foo;
* $params = array('b' => 2, 'c' => 3, 'a' => 1);
* $remaining = ReflectionHelper::bindObjectParams($object, $params);
* var_export($object); // output: $object->a = 1; $object->b = null;
* var_export($remaining); // output: array('b' => 2, 'c' => 3);
* ~~~
*
* @param object $object the object whose properties are to be initialized
* @param array $params the input parameters to be used to initialize the object
* @return array the remaining unused input parameters
*/
public static function initObjectWithParams($object, $params)
{
if (empty($params)) {
return array();
}
$class = new \ReflectionClass(get_class($object));
foreach ($params as $name => $value) {
if ($class->hasProperty($name)) {
$property = $class->getProperty($name);
if ($property->isPublic() && !$property->isStatic()) {
$object->$name = $value;
unset($params[$name]);
}
}
}
return $params;
}
}
<?php <?php
define('YII_DEBUG', true);
/** /**
* Yii console bootstrap file. * Yii console bootstrap file.
* *
...@@ -8,16 +7,17 @@ define('YII_DEBUG', true); ...@@ -8,16 +7,17 @@ define('YII_DEBUG', true);
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
defined('YII_DEBUG') or define('YII_DEBUG', true);
// fcgi doesn't have STDIN defined by default // fcgi doesn't have STDIN defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
require(__DIR__ . '/yii.php'); require(__DIR__ . '/yii.php');
$config = array(
'controllerPath' => '@yii/console/controllers',
);
$id = 'yiic'; $id = 'yiic';
$basePath = __DIR__ . '/console'; $basePath = __DIR__ . '/console';
$application = new yii\console\Application($id, $basePath, $config); $application = new yii\console\Application($id, $basePath, array(
'controllerPath' => '@yii/console/controllers',
));
$application->run(); $application->run();
<?php <?php
define('YII_ENABLE_EXCEPTION_HANDLER', false);
define('YII_ENABLE_ERROR_HANDLER', false); define('YII_ENABLE_ERROR_HANDLER', false);
define('YII_DEBUG', true); define('YII_DEBUG', true);
$_SERVER['SCRIPT_NAME'] = '/' . __DIR__; $_SERVER['SCRIPT_NAME'] = '/' . __DIR__;
......
- db - db
* pgsql, sql server, oracle, db2 drivers * pgsql, sql server, oracle, db2 drivers
* write a guide on creating own schema definitions * unit tests on different DB drivers
* document-based (should allow storage-specific methods additionally to generic ones) * document-based (should allow storage-specific methods additionally to generic ones)
* mongodb (put it under framework/db/mongodb) * mongodb (put it under framework/db/mongodb)
* key-value-based (should allow storage-specific methods additionally to generic ones) * key-value-based (should allow storage-specific methods additionally to generic ones)
...@@ -8,8 +8,10 @@ ...@@ -8,8 +8,10 @@
- logging - logging
* WebTarget (TBD after web is in place): should consider using javascript and make it into a toolbar * WebTarget (TBD after web is in place): should consider using javascript and make it into a toolbar
* ProfileTarget (TBD after web is in place): should consider using javascript and make it into a toolbar * ProfileTarget (TBD after web is in place): should consider using javascript and make it into a toolbar
* unit tests
- caching - caching
* a console command to clear cached data * a console command to clear cached data
* unit tests
- validators - validators
* FileValidator: depends on CUploadedFile * FileValidator: depends on CUploadedFile
* CaptchaValidator: depends on CaptchaAction * CaptchaValidator: depends on CaptchaAction
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment