Browse Source

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
tags/2.0.0-beta
Carsten Brandt 12 years ago
parent
commit
c5816d233c
  1. 32
      framework/YiiBase.php
  2. 54
      framework/base/Action.php
  3. 7
      framework/base/ActionEvent.php
  4. 90
      framework/base/ActionFilter.php
  5. 366
      framework/base/Application.php
  6. 260
      framework/base/Controller.php
  7. 106
      framework/base/ErrorHandler.php
  8. 12
      framework/base/Exception.php
  9. 4
      framework/base/HttpException.php
  10. 37
      framework/base/InlineAction.php
  11. 7
      framework/base/InvalidCallException.php
  12. 7
      framework/base/InvalidConfigException.php
  13. 12
      framework/base/InvalidRequestException.php
  14. 33
      framework/base/InvalidRouteException.php
  15. 180
      framework/base/Module.php
  16. 7
      framework/base/NotSupportedException.php
  17. 105
      framework/base/Theme.php
  18. 7
      framework/base/UnknownMethodException.php
  19. 7
      framework/base/UnknownPropertyException.php
  20. 558
      framework/base/View.php
  21. 16
      framework/base/Widget.php
  22. 43
      framework/console/Application.php
  23. 118
      framework/console/Controller.php
  24. 4
      framework/console/controllers/CreateController.php
  25. 35
      framework/console/controllers/HelpController.php
  26. 432
      framework/console/controllers/MigrateController.php
  27. 149
      framework/console/controllers/ShellController.php
  28. 29
      framework/db/Command.php
  29. 8
      framework/db/Exception.php
  30. 27
      framework/db/QueryBuilder.php
  31. 35
      framework/db/mysql/QueryBuilder.php
  32. 9
      framework/logging/DbTarget.php
  33. 9
      framework/logging/EmailTarget.php
  34. 25
      framework/logging/FileTarget.php
  35. 33
      framework/logging/Logger.php
  36. 21
      framework/logging/Router.php
  37. 65
      framework/logging/Target.php
  38. 19
      framework/util/FileHelper.php
  39. 103
      framework/util/ReflectionHelper.php
  40. 10
      framework/yiic.php
  41. 1
      tests/unit/bootstrap.php
  42. 4
      todo.md

32
framework/YiiBase.php

@ -8,9 +8,8 @@
*/
use yii\base\Exception;
use yii\logging\Logger;
use yii\base\InvalidCallException;
use yii\base\InvalidConfigException;
use yii\logging\Logger;
/**
* Gets the application start timestamp.
@ -30,6 +29,11 @@ defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 0);
* This constant defines the framework installation directory.
*/
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.
@ -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):
*
* - `@app/components/GoogleMap`: importing the `GoogleMap` class with a path alias;
* - `@app/components/*`: importing the whole `components` directory with a path alias;
* - `@application/components/GoogleMap`: importing the `GoogleMap` class 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
* when this class is used for the first time.
*
@ -189,14 +193,14 @@ class YiiBase
*
* Note, this method does not ensure the existence of the resulting path.
* @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.
* @throws Exception if the alias is invalid and $throwException is true.
* @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];
} elseif ($alias === '' || $alias[0] !== '@') { // not an alias
return $alias;
@ -206,11 +210,7 @@ class YiiBase
return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos);
}
}
if ($throwException) {
throw new Exception("Invalid path alias: $alias");
} else {
return false;
}
return false;
}
/**
@ -322,12 +322,12 @@ class YiiBase
* the class. For example,
*
* - `\app\components\GoogleMap`: fully-qualified namespaced class.
* - `@app/components/GoogleMap`: an alias
* - `@application/components/GoogleMap`: an alias
*
* Below are some usage examples:
*
* ~~~
* $object = \Yii::createObject('@app/components/GoogleMap');
* $object = \Yii::createObject('@application/components/GoogleMap');
* $object = \Yii::createObject(array(
* 'class' => '\app\components\GoogleMap',
* 'apiKey' => 'xyz',
@ -361,7 +361,7 @@ class YiiBase
$class = $config['class'];
unset($config['class']);
} 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)) {

54
framework/base/Action.php

@ -9,8 +9,6 @@
namespace yii\base;
use yii\util\ReflectionHelper;
/**
* Action is the base class for all controller action classes.
*
@ -21,6 +19,14 @@ use yii\util\ReflectionHelper;
* will be invoked by the controller when the action is requested.
* The `run()` method can have parameters which will be filled up
* 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>
* @since 2.0
@ -37,6 +43,7 @@ class Action extends Component
public $controller;
/**
* Constructor.
* @param string $id the ID of 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
@ -51,20 +58,45 @@ class Action extends Component
/**
* Runs this action with the specified parameters.
* 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).
* @throws InvalidConfigException if the action class does not have a run() method
*/
public function runWithParams($params)
{
try {
$ps = ReflectionHelper::extractMethodParams($this, 'run', $params);
} catch (Exception $e) {
$this->controller->invalidActionParams($this, $e);
return 1;
if (!method_exists($this, 'run')) {
throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
}
if ($params !== $ps) {
$this->controller->extraActionParams($this, $ps, $params);
$method = new \ReflectionMethod($this, 'run');
$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;
}
}

7
framework/base/ActionEvent.php

@ -12,8 +12,7 @@ namespace yii\base;
/**
* 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
* the action currently being executed.
* By setting the [[isValid]] property, one may control whether to continue running the action.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
@ -25,7 +24,7 @@ class ActionEvent extends Event
*/
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;
@ -34,7 +33,7 @@ class ActionEvent extends 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
*/
public function __construct(Action $action, $config = array())
public function __construct($action, $config = array())
{
$this->action = $action;
parent::__construct($config);

90
framework/base/ActionFilter.php

@ -1,90 +0,0 @@
<?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);
}
}

366
framework/base/Application.php

@ -9,8 +9,8 @@
namespace yii\base;
use yii\base\InvalidCallException;
use yii\util\StringHelper;
use Yii;
use yii\util\FileHelper;
/**
* Application is the base class for all application classes.
@ -36,7 +36,7 @@ use yii\util\StringHelper;
* Yii framework messages. This application component is dynamically loaded when needed.</li>
* </ul>
*
* Application will undergo the following lifecycles when processing a user request:
* Application will undergo the following life cycles when processing a user request:
* <ol>
* <li>load application configuration;</li>
* <li>set up class autoloader and error handling;</li>
@ -49,28 +49,6 @@ use yii\util\StringHelper;
* Starting from lifecycle 3, if a PHP error or an uncaught exception occurs,
* the application will switch to its error handling logic and jump to step 6 afterwards.
*
* @property string $basePath Returns the root path of the application.
* @property CCache $cache Returns the cache component.
* @property CPhpMessageSource $coreMessages Returns the core message translations.
* @property CDateFormatter $dateFormatter Returns the locale-dependent date formatter.
* @property \yii\db\Connection $db Returns the database connection component.
* @property CErrorHandler $errorHandler Returns the error handler component.
* @property string $extensionPath Returns the root directory that holds all third-party extensions.
* @property string $id Returns the unique identifier for the application.
* @property string $language Returns the language that the user is using and the application should be targeted to.
* @property CLocale $locale Returns the locale instance.
* @property string $localeDataPath Returns the directory that contains the locale data.
* @property CMessageSource $messages Returns the application message translations component.
* @property CNumberFormatter $numberFormatter The locale-dependent number formatter.
* @property CHttpRequest $request Returns the request component.
* @property string $runtimePath Returns the directory that stores runtime files.
* @property CSecurityManager $securityManager Returns the security manager component.
* @property CStatePersister $statePersister Returns the state persister component.
* @property string $timeZone Returns the time zone used by this application.
* @property UrlManager $urlManager Returns the URL manager component.
* @property string $baseUrl Returns the relative URL for the application
* @property string $homeUrl the homepage URL
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
@ -97,11 +75,9 @@ class Application extends Module
*/
public $sourceLanguage = 'en_us';
/**
* @var array IDs of application components that need to be loaded when the application starts.
* The default value is `array('errorHandler')`, which loads the [[errorHandler]] component
* to ensure errors and exceptions can be handled nicely.
* @var array IDs of the components that need to be loaded when the application starts.
*/
public $preload = array('errorHandler');
public $preload = array();
/**
* @var Controller the currently active controller instance
*/
@ -128,12 +104,19 @@ class Application extends Module
*/
public function __construct($id, $basePath, $config = array())
{
\Yii::$application = $this;
Yii::$application = $this;
$this->id = $id;
$this->setBasePath($basePath);
if (YII_ENABLE_ERROR_HANDLER) {
set_exception_handler(array($this, 'handleException'));
set_error_handler(array($this, 'handleError'), error_reporting());
}
$this->registerDefaultAliases();
$this->registerCoreComponents();
parent::__construct($id, $this, $config);
Component::__construct($config);
}
/**
@ -203,28 +186,6 @@ class Application extends Module
}
/**
* Runs a controller with the given route and parameters.
* @param string $route the route (e.g. `post/create`)
* @param array $params the parameters to be passed to the controller action
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
* @throws InvalidRequestException if the route cannot be resolved into a controller
*/
public function runController($route, $params = array())
{
$result = $this->createController($route);
if ($result === false) {
throw new InvalidRequestException(\Yii::t('yii', 'Unable to resolve the request.'));
}
/** @var $controller Controller */
list($controller, $action) = $result;
$priorController = $this->controller;
$this->controller = $controller;
$status = $controller->run($action, $params);
$this->controller = $priorController;
return $status;
}
/**
* Returns the directory that stores runtime files.
* @return string the directory that stores runtime files. Defaults to 'protected/runtime'.
*/
@ -239,15 +200,15 @@ class Application extends Module
/**
* Sets the directory that stores runtime files.
* @param string $path the directory that stores runtime files.
* @throws InvalidCallException if the directory does not exist or is not writable
* @throws InvalidConfigException if the directory does not exist or is not writable
*/
public function setRuntimePath($path)
{
$p = \Yii::getAlias($path);
if ($p === false || !is_dir($p) || !is_writable($path)) {
throw new InvalidCallException("Application runtime path \"$path\" is invalid. Please make sure it is a directory writable by the Web server process.");
} else {
$p = FileHelper::ensureDirectory($path);
if (is_writable($p)) {
$this->_runtimePath = $p;
} else {
throw new InvalidConfigException("Runtime path must be writable by the Web server process: $path");
}
}
@ -296,34 +257,61 @@ class Application extends Module
date_default_timezone_set($value);
}
/**
* 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.
* @return CLocale the locale instance
*/
public function getLocale($localeID = null)
{
return CLocale::getInstance($localeID === null ? $this->getLanguage() : $localeID);
}
/**
* @return CNumberFormatter the locale-dependent number formatter.
* The current {@link getLocale application locale} will be used.
*/
public function getNumberFormatter()
{
return $this->getLocale()->getNumberFormatter();
}
/**
* Returns the locale-dependent date formatter.
* @return CDateFormatter the locale-dependent date formatter.
* The current {@link getLocale application locale} will be used.
*/
public function getDateFormatter()
{
return $this->getLocale()->getDateFormatter();
}
// /**
// * Returns the security manager component.
// * @return SecurityManager the security manager application component.
// */
// public function getSecurityManager()
// {
// return $this->getComponent('securityManager');
// }
//
// /**
// * 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.
// * @return CLocale the locale instance
// */
// public function getLocale($localeID = null)
// {
// return CLocale::getInstance($localeID === null ? $this->getLanguage() : $localeID);
// }
//
// /**
// * @return CNumberFormatter the locale-dependent number formatter.
// * The current {@link getLocale application locale} will be used.
// */
// public function getNumberFormatter()
// {
// return $this->getLocale()->getNumberFormatter();
// }
//
// /**
// * Returns the locale-dependent date formatter.
// * @return CDateFormatter the locale-dependent date formatter.
// * The current {@link getLocale application locale} will be used.
// */
// public function getDateFormatter()
// {
// return $this->getLocale()->getDateFormatter();
// }
//
// /**
// * Returns the core message translations component.
// * @return \yii\i18n\MessageSource the core message translations
// */
// public function getCoreMessages()
// {
// return $this->getComponent('coreMessages');
// }
//
// /**
// * Returns the application message translations component.
// * @return \yii\i18n\MessageSource the application message translations
// */
// public function getMessages()
// {
// return $this->getComponent('messages');
// }
/**
* Returns the database connection component.
@ -353,15 +341,6 @@ class Application extends Module
}
/**
* Returns the security manager component.
* @return SecurityManager the security manager application component.
*/
public function getSecurityManager()
{
return $this->getComponent('securityManager');
}
/**
* Returns the cache component.
* @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
*/
@ -371,24 +350,6 @@ class Application extends Module
}
/**
* Returns the core message translations component.
* @return \yii\i18n\MessageSource the core message translations
*/
public function getCoreMessages()
{
return $this->getComponent('coreMessages');
}
/**
* Returns the application message translations component.
* @return \yii\i18n\MessageSource the application message translations
*/
public function getMessages()
{
return $this->getComponent('messages');
}
/**
* Returns the request component.
* @return Request the request component
*/
@ -402,9 +363,9 @@ class Application extends Module
*/
public function registerDefaultAliases()
{
\Yii::$aliases['@application'] = $this->getBasePath();
\Yii::$aliases['@entry'] = dirname($_SERVER['SCRIPT_FILENAME']);
\Yii::$aliases['@www'] = '';
Yii::$aliases['@application'] = $this->getBasePath();
Yii::$aliases['@entry'] = dirname($_SERVER['SCRIPT_FILENAME']);
Yii::$aliases['@www'] = '';
}
/**
@ -417,15 +378,6 @@ class Application extends Module
'errorHandler' => array(
'class' => 'yii\base\ErrorHandler',
),
'request' => array(
'class' => 'yii\base\Request',
),
'response' => array(
'class' => 'yii\base\Response',
),
'format' => array(
'class' => 'yii\base\Formatter',
),
'coreMessages' => array(
'class' => 'yii\i18n\PhpMessageSource',
'language' => 'en_us',
@ -444,124 +396,88 @@ class Application extends Module
}
/**
* Performs a controller action specified by a route.
* This method parses the specified route and creates the corresponding controller and action
* instances under the context of the specified module. It then runs the created action
* with the given parameters.
* @param string $route the route that specifies the action.
* @param array $params the parameters to be passed to the action
* @param Module $module the module which serves as the context of the route
* @return integer the action
* @throws InvalidConfigException if the module's defaultRoute is empty or the controller's defaultAction is empty
* @throws InvalidRequestException if the requested route cannot be resolved into an action successfully
* 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 runAction($route, $params = array(), $module = null)
public function handleError($code, $message, $file, $line)
{
if ($module === null) {
$module = $this;
}
$route = trim($route, '/');
if ($route === '') {
$route = trim($module->defaultRoute, '/');
if ($route == '') {
throw new InvalidConfigException(get_class($module) . '::defaultRoute cannot be empty.');
}
}
if (($pos = strpos($route, '/')) !== false) {
$id = substr($route, 0, $pos);
$route = substr($route, $pos + 1);
} else {
$id = $route;
$route = '';
}
$childModule = $module->getModule($id);
if ($childModule !== null) {
return $this->runAction($route, $params, $childModule);
}
/** @var $controller Controller */
if (isset($module->controllerMap[$id])) {
$controller = \Yii::createObject($module->controllerMap[$id], $id, $module);
} else {
$controller = $this->createController($id, $module);
if ($controller === null) {
throw new InvalidRequestException("Unable to resolve the request: $route");
}
}
if (isset($controller)) {
$action = $this->createAction($route, $controller);
if ($action !== null) {
return $action->runWithParams($params);
}
if (error_reporting() !== 0) {
throw new \ErrorException($message, 0, $code, $file, $line);
}
throw new InvalidRequestException("Unable to resolve the request: $route");
}
/**
* Creates a controller instance based on the controller ID.
* Handles uncaught PHP exceptions.
*
* The controller is created within the given module. The method first attempts to
* create the controller based on the [[controllerMap]] of the module. If not available,
* it will look for the controller class under the [[controllerPath]] and create an
* instance of it.
* This method is implemented as a PHP exception handler. It requires
* that constant YII_ENABLE_ERROR_HANDLER be defined true.
*
* @param string $id the controller ID
* @param Module $module the module that owns the controller
* @return Controller the newly created controller instance
* @param \Exception $exception exception that is not caught
*/
public function createController($id, $module)
public function handleException($exception)
{
if (isset($module->controllerMap[$id])) {
return \Yii::createObject($module->controllerMap[$id], $id, $module);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
$className = StringHelper::id2camel($id) . 'Controller';
$classFile = $module->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
if (is_file($classFile)) {
$className = $module->controllerNamespace . '\\' . $className;
if (!class_exists($className, false)) {
require($classFile);
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
return new $className($id, $module);
}
// disable error capturing to avoid recursive errors while handling exceptions
restore_error_handler();
restore_exception_handler();
try {
$this->logException($exception);
if (($handler = $this->getErrorHandler()) !== null) {
$handler->handle($exception);
} else {
$this->renderException($exception);
}
$this->end(1);
} catch(\Exception $e) {
// exception could be thrown in end() or ErrorHandler::handle()
$msg = (string)$e;
$msg .= "\nPrevious exception:\n";
$msg .= (string)$exception;
$msg .= "\n\$_SERVER = " . var_export($_SERVER, true);
error_log($msg);
exit(1);
}
return null;
}
/**
* Creates an action based on the given action ID.
* The action is created within the given controller. The method first attempts to
* create the action based on [[Controller::actions()]]. If not available,
* it will look for the inline action method within the controller.
* @param string $id the action ID
* @param Controller $controller the controller that owns the action
* @return Action the newly created action instance
* @throws InvalidConfigException if [[Controller::defaultAction]] is empty.
* Renders an exception without using rich format.
* @param \Exception $exception the exception to be rendered.
*/
public function createAction($id, $controller)
public function renderException($exception)
{
if ($id === '') {
$id = $controller->defaultAction;
if ($id == '') {
throw new InvalidConfigException(get_class($controller) . '::defaultAction cannot be empty.');
}
if ($exception instanceof Exception && ($exception->causedByUser || !YII_DEBUG)) {
$message = $exception->getName() . ': ' . $exception->getMessage();
} else {
$message = YII_DEBUG ? (string)$exception : 'Error: ' . $exception->getMessage();
}
if (isset($controller->actionMap[$id])) {
return \Yii::createObject($controller->actionMap[$id], $id, $controller);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
$methodName = 'action' . StringHelper::id2camel($id);
if (method_exists($controller, $methodName)) {
$method = new \ReflectionMethod($controller, $methodName);
if ($method->getName() === $methodName) {
return new InlineAction($id, $controller);
}
}
if (PHP_SAPI) {
echo $message . "\n";
} else {
echo '<pre>' . htmlspecialchars($message, ENT_QUOTES, $this->charset) . '</pre>';
}
}
// todo: to be polished
protected 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();
}
return null;
Yii::error((string)$exception, $category);
}
}

260
framework/base/Controller.php

@ -9,16 +9,12 @@
namespace yii\base;
use Yii;
use yii\util\StringHelper;
/**
* Controller is the base class for classes containing controller logic.
*
* Controller implements the action life cycles, which consist of the following steps:
*
* 1. [[authorize]]
* 2. [[beforeAction]]
* 3. [[afterAction]]
*
* @property array $actionParams the request parameters (name-value pairs) to be used for action parameter binding
* @property string $route the route (module ID, controller ID and action ID) of the current request.
* @property string $uniqueId the controller ID that is prefixed with the module ID (if any).
*
@ -27,6 +23,9 @@ namespace yii\base;
*/
class Controller extends Component
{
const EVENT_BEFORE_ACTION = 'beforeAction';
const EVENT_AFTER_ACTION = 'afterAction';
/**
* @var string the ID of this controller
*/
@ -91,200 +90,184 @@ class Controller extends Component
}
/**
* Runs the controller with the specified action and parameters.
* @param Action|string $action the action to be executed. This can be either an action object
* or the ID of the action.
* Runs an action with the specified action ID and parameters.
* If the action ID is empty, the method will use [[defaultAction]].
* @param string $id the ID of the action to be executed.
* @param array $params the parameters (name-value pairs) to be passed to the action.
* If null, the result of [[getActionParams()]] will be used as action parameters.
* @return integer the exit status of the action. 0 means normal, other values mean abnormal.
* @see missingAction
* @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 run($action, $params = null)
public function runAction($id, $params = array())
{
if (is_string($action)) {
if (($a = $this->createAction($action)) !== null) {
$action = $a;
$action = $this->createAction($id);
if ($action !== null) {
$oldAction = $this->action;
$this->action = $action;
if ($this->beforeAction($action)) {
$status = $action->runWithParams($params);
$this->afterAction($action);
} else {
$this->missingAction($action);
return 1;
$status = 1;
}
}
$priorAction = $this->action;
$this->action = $action;
$this->action = $oldAction;
if ($this->authorize($action) && $this->beforeAction($action)) {
if ($params === null) {
$params = $this->getActionParams();
}
$status = $action->runWithParams($params);
$this->afterAction($action);
return $status;
} else {
$status = 1;
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
$this->action = $priorAction;
return $status;
}
/**
* Creates the action instance based on the action ID.
* The action can be either an inline action or an object.
* The latter is created by looking up the action map specified in [[actions]].
* @param string $actionID ID of the action. If empty, it will take the value of [[defaultAction]].
* @return Action the action instance, null if the action does not exist.
* @see actions
* Runs a request specified in terms of a route.
* The route can be either an ID of an action within this controller or a complete route consisting
* of module IDs, controller ID and action ID. If the route starts with a slash '/', the parsing of
* the route will start from the application; otherwise, it will start from the parent module of this controller.
* @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
* @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.
* @see runAction
* @see forward
*/
public function createAction($actionID)
public function run($route, $params = array())
{
if ($actionID === '') {
$actionID = $this->defaultAction;
}
if (isset($this->actionMap[$actionID])) {
return \Yii::createObject($this->actionMap[$actionID], $actionID, $this);
} elseif (method_exists($this, 'action' . $actionID)) {
return new InlineAction($actionID, $this);
$pos = strpos($route, '/');
if ($pos === false) {
return $this->runAction($route, $params);
} elseif ($pos > 0) {
return $this->module->runAction($route, $params);
} else {
return null;
return \Yii::$application->runAction(ltrim($route, '/'), $params);
}
}
/**
* Returns the request parameters that will be used for action parameter binding.
* Default implementation simply returns an empty array.
* Child classes may override this method to customize the parameters to be provided
* for action parameter binding (e.g. `$_GET`).
* @return array the request parameters (name-value pairs) to be used for action parameter binding
* Forwards the current execution flow to handle a new request specified by a route.
* The only difference between this method and [[run()]] is that after calling this method,
* the application will exit.
* @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
* @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.
* @see run
*/
public function getActionParams()
public function forward($route, $params = array())
{
return array();
$status = $this->run($route, $params);
exit($status);
}
/**
* This method is invoked when the request parameters do not satisfy the requirement of the specified action.
* The default implementation will throw an exception.
* @param Action $action the action being executed
* @param Exception $exception the exception about the invalid parameters
* @throws Exception whenever this method is invoked
* Creates an action based on the given action ID.
* The method first checks if the action ID has been declared in [[actions()]]. If so,
* it will use the configuration declared there to create the action object.
* If not, it will look for a controller method whose name is in the format of `actionXyz`
* where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that
* method will be created and returned.
* @param string $id the action ID
* @return Action the newly created action instance. Null if the ID doesn't resolve into any action.
*/
public function invalidActionParams($action, $exception)
public function createAction($id)
{
throw $exception;
}
if ($id === '') {
$id = $this->defaultAction;
}
/**
* This method is invoked when extra parameters are provided to an action when it is executed.
* The default implementation does nothing.
* @param Action $action the action being executed
* @param array $expected the expected action parameters (name => value)
* @param array $actual the actual action parameters (name => value)
*/
public function extraActionParams($action, $expected, $actual)
{
$actionMap = $this->actions();
if (isset($actionMap[$id])) {
return Yii::createObject($actionMap[$id], $id, $this);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
$methodName = 'action' . StringHelper::id2camel($id);
if (method_exists($this, $methodName)) {
$method = new \ReflectionMethod($this, $methodName);
if ($method->getName() === $methodName) {
return new InlineAction($id, $this, $methodName);
}
}
}
return null;
}
/**
* Handles the request whose action is not recognized.
* This method is invoked when the controller cannot find the requested action.
* The default implementation simply throws an exception.
* @param string $actionID the missing action name
* @throws InvalidRequestException whenever this method is invoked
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function missingAction($actionID)
public function beforeAction($action)
{
throw new InvalidRequestException(\Yii::t('yii', 'The system is unable to find the requested action "{action}".',
array('{action}' => $actionID == '' ? $this->defaultAction : $actionID)));
$event = new ActionEvent($action);
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
return $event->isValid;
}
/**
* @return string the controller ID that is prefixed with the module ID (if any).
* This method is invoked right after an action is executed.
* You may override this method to do some postprocessing for the action.
* @param Action $action the action just executed.
*/
public function getUniqueId()
public function afterAction($action)
{
return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
$this->trigger(self::EVENT_AFTER_ACTION, new ActionEvent($action));
}
/**
* Returns the route of the current request.
* @return string the route (module ID, controller ID and action ID) of the current request.
* Returns the request parameters that will be used for action parameter binding.
* Default implementation simply returns an empty array.
* Child classes may override this method to customize the parameters to be provided
* for action parameter binding (e.g. `$_GET`).
* @return array the request parameters (name-value pairs) to be used for action parameter binding
*/
public function getRoute()
public function getActionParams()
{
return $this->action !== null ? $this->getUniqueId() . '/' . $this->action->id : $this->getUniqueId();
return array();
}
/**
* Processes the request using another controller action.
* @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,
* 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.
* Validates the parameter being bound to actions.
* This method is invoked when parameters are being bound to the currently requested action.
* Child classes may override this method to throw exceptions when there are missing and/or unknown parameters.
* @param Action $action the currently requested action
* @param array $missingParams the names of the missing parameters
* @param array $unknownParams the unknown parameters (name=>value)
*/
public function forward($route, $params = array(), $exit = true)
public function validateActionParams($action, $missingParams, $unknownParams)
{
if (strpos($route, '/') === false) {
$status = $this->run($route, $params);
} else {
if ($route[0] !== '/' && !$this->module instanceof Application) {
$route = '/' . $this->module->getUniqueId() . '/' . $route;
}
$status = \Yii::$application->runController($route, $params);
}
if ($exit) {
\Yii::$application->end($status);
}
}
/**
* This method is invoked when checking the access for the action to be executed.
* @param Action $action the action to be executed.
* @return boolean whether the action is allowed to be executed.
* @return string the controller ID that is prefixed with the module ID (if any).
*/
public function authorize($action)
public function getUniqueId()
{
$event = new ActionEvent($action);
$this->trigger(__METHOD__, $event);
return $event->isValid;
return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
}
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
* Returns the route of the current request.
* @return string the route (module ID, controller ID and action ID) of the current request.
*/
public function beforeAction($action)
public function getRoute()
{
$event = new ActionEvent($action);
$this->trigger(__METHOD__, $event);
return $event->isValid;
return $this->action !== null ? $this->getUniqueId() . '/' . $this->action->id : $this->getUniqueId();
}
/**
* This method is invoked right after an action is executed.
* You may override this method to do some postprocessing for the action.
* @param Action $action the action just executed.
* Renders a view and applies layout if available.
*
* @param $view
* @param array $params
* @return string
*/
public function afterAction($action)
{
$this->trigger(__METHOD__, new ActionEvent($action));
}
public function render($view, $params = array())
{
return $this->createView()->render($view, $params);
}
public function renderText($text)
public function renderContent($content)
{
return $this->createView()->renderText($text);
return $this->createView()->renderContent($content);
}
public function renderPartial($view, $params = array())
@ -296,4 +279,15 @@ class Controller extends Component
{
return new View($this);
}
/**
* Returns the directory containing view files for this controller.
* The default implementation returns the directory named as controller [[id]] under the [[module]]'s
* [[viewPath]] directory.
* @return string the directory containing the view files for this controller.
*/
public function getViewPath()
{
return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
}
}

106
framework/base/ErrorHandler.php

@ -52,90 +52,46 @@ class ErrorHandler extends Component
* @var \Exception the exception that is being handled currently
*/
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
*/
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->logException($exception);
if ($this->discardExistingOutput) {
$this->clearOutput();
}
try {
$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);
}
$this->render($exception);
}
protected function render($exception)
{
if ($this->errorAction !== null) {
\Yii::$application->runController($this->errorAction);
\Yii::$application->runAction($this->errorAction);
} elseif (\Yii::$application instanceof \yii\web\Application) {
if (!headers_sent()) {
$errorCode = $exception instanceof HttpException ? $exception->statusCode : 500;
header("HTTP/1.0 $errorCode " . get_class($exception));
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
$this->renderAsText($exception);
\Yii::$application->renderException($exception);
} 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 {
$this->renderAsText($exception);
\Yii::$application->renderException($exception);
}
}
@ -286,22 +242,6 @@ class ErrorHandler extends Component
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()
{
// the following manual level counting is to deal with zlib.output_compression set to On
@ -313,22 +253,14 @@ class ErrorHandler extends Component
/**
* @param \Exception $exception
*/
public function renderAsText($exception)
public function renderAsHtml($exception)
{
if (YII_DEBUG) {
echo $exception;
$view = new View($this);
if (!YII_DEBUG || $exception instanceof Exception && $exception->causedByUser) {
$viewName = $this->errorView;
} 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;
echo $view->render($name, array(
'exception' => $exception,

12
framework/base/Exception.php

@ -17,5 +17,17 @@ namespace yii\base;
*/
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');
}
}

4
framework/base/HttpException.php

@ -25,6 +25,10 @@ class HttpException extends Exception
* @var integer HTTP status code, such as 403, 404, 500, etc.
*/
public $statusCode;
/**
* @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
*/
public $causedByUser = true;
/**
* Constructor.

37
framework/base/InlineAction.php

@ -9,13 +9,11 @@
namespace yii\base;
use yii\util\ReflectionHelper;
/**
* InlineAction represents an action that is defined as a controller method.
*
* The name of the controller method should be in the format of `actionXyz`
* where `Xyz` stands for the action ID (e.g. `actionIndex`).
* The name of the controller method is available via [[actionMethod]] which
* is set by the [[controller]] who creates this action.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
@ -23,6 +21,23 @@ use yii\util\ReflectionHelper;
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.
* This method is mainly invoked by the controller.
* @param array $params action parameters
@ -30,16 +45,8 @@ class InlineAction extends Action
*/
public function runWithParams($params)
{
try {
$method = 'action' . $this->id;
$ps = ReflectionHelper::extractMethodParams($this->controller, $method, $params);
} 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);
$method = new \ReflectionMethod($this->controller, $this->actionMethod);
$args = $this->bindActionParams($method, $params);
return (int)$method->invokeArgs($this->controller, $args);
}
}

7
framework/base/InvalidCallException.php

@ -17,5 +17,12 @@ namespace yii\base;
*/
class InvalidCallException extends \Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Call');
}
}

7
framework/base/InvalidConfigException.php

@ -17,5 +17,12 @@ namespace yii\base;
*/
class InvalidConfigException extends \Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Invalid Configuration');
}
}

12
framework/base/InvalidRequestException.php

@ -17,5 +17,17 @@ namespace yii\base;
*/
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');
}
}

33
framework/base/InvalidRouteException.php

@ -0,0 +1,33 @@
<?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');
}
}

180
framework/base/Module.php

@ -9,18 +9,29 @@
namespace yii\base;
use Yii;
use yii\util\StringHelper;
use yii\util\FileHelper;
/**
* Module is the base class for module and application classes.
*
* Module mainly manages application components and sub-modules that belongs to a module.
* A module represents a sub-application which contains MVC elements by itself, such as
* models, views, controllers, etc.
*
* A module may consist of [[modules|sub-modules]].
*
* [[components|Components]] may be registered with the module so that they are globally
* accessible within the module.
*
* @property string $uniqueId An ID that uniquely identifies this module among all modules within
* the current application.
* @property string $basePath The root directory of the module. Defaults to the directory containing the module class.
* @property string $controllerPath The directory containing the controller classes. Defaults to "[[basePath]]/controllers".
* @property string $viewPath The directory containing the view files within this module. Defaults to "[[basePath]]/views".
* @property string $layoutPath The directory containing the layout view files within this module. Defaults to "[[viewPath]]/layouts".
* @property array $modules The configuration of the currently installed modules (module ID => configuration).
* @property array $components The application components (indexed by their IDs).
* @property array $components The components (indexed by their IDs) registered within this module.
* @property array $import List of aliases to be imported. This property is write-only.
* @property array $aliases List of aliases to be defined. This property is write-only.
*
@ -34,7 +45,7 @@ abstract class Module extends Component
*/
public $params = array();
/**
* @var array the IDs of the application components that should be preloaded when this module is created.
* @var array the IDs of the components that should be preloaded when this module is created.
*/
public $preload = array();
/**
@ -86,27 +97,27 @@ abstract class Module extends Component
/**
* @var string the root directory of the module.
*/
protected $_basePath;
private $_basePath;
/**
* @var string the root directory that contains view files for this module
*/
protected $_viewPath;
private $_viewPath;
/**
* @var string the root directory that contains layout view files for this module.
*/
protected $_layoutPath;
private $_layoutPath;
/**
* @var string the directory containing controller classes in the module.
*/
protected $_controllerPath;
private $_controllerPath;
/**
* @var array child modules of this module
*/
protected $_modules = array();
private $_modules = array();
/**
* @var array application components of this module
* @var array components registered under this module
*/
protected $_components = array();
private $_components = array();
/**
* Constructor.
@ -123,9 +134,9 @@ abstract class Module extends Component
/**
* Getter magic method.
* This method is overridden to support accessing application components
* This method is overridden to support accessing components
* like reading module properties.
* @param string $name application component or property name
* @param string $name component or property name
* @return mixed the named property value
*/
public function __get($name)
@ -140,7 +151,7 @@ abstract class Module extends Component
/**
* Checks if a property value is null.
* This method overrides the parent implementation by checking
* if the named application component is loaded.
* if the named component is loaded.
* @param string $name the property name or the event name
* @return boolean whether the property value is null
*/
@ -161,18 +172,21 @@ abstract class Module extends Component
*/
public function init()
{
\Yii::setAlias('@' . $this->id, $this->getBasePath());
Yii::setAlias('@' . $this->id, $this->getBasePath());
$this->preloadComponents();
}
/**
* Returns an ID that uniquely identifies this module among all modules within the current application.
* Note that if the module is an application, an empty string will be returned.
* @return string the unique ID of the module.
*/
public function getUniqueId()
{
if ($this->module && !$this->module instanceof Application) {
return $this->module->getUniqueId() . "/{$this->id}";
if ($this instanceof Application) {
return '';
} elseif ($this->module) {
return $this->module->getUniqueId() . '/' . $this->id;
} else {
return $this->id;
}
@ -229,8 +243,8 @@ abstract class Module extends Component
}
/**
* @return string the root directory of view files. Defaults to 'moduleDir/views' where
* moduleDir is the directory containing the module class.
* Returns the directory that contains the view files for this module.
* @return string the root directory of view files. Defaults to "[[basePath]]/view".
*/
public function getViewPath()
{
@ -252,8 +266,8 @@ abstract class Module extends Component
}
/**
* @return string the root directory of layout files. Defaults to 'moduleDir/views/layouts' where
* moduleDir is the directory containing the module class.
* Returns the directory that contains layout view files for this module.
* @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
*/
public function getLayoutPath()
{
@ -277,19 +291,19 @@ abstract class Module extends Component
/**
* Imports the specified path aliases.
* 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()]].
* @param array $aliases list of path aliases to be imported
*/
public function setImport($aliases)
{
foreach ($aliases as $alias) {
\Yii::import($alias);
Yii::import($alias);
}
}
/**
* Defines path aliases.
* This method calls [[\Yii::setAlias()]] to register the path aliases.
* This method calls [[Yii::setAlias()]] to register the path aliases.
* This method is provided so that you can define path aliases when configuring a module.
* @param array $aliases list of path aliases to be defined. The array keys are alias names
* (must start with '@') and the array values are the corresponding paths or aliases.
@ -297,7 +311,7 @@ abstract class Module extends Component
*
* ~~~
* array(
* '@models' => '@app/models', // an existing alias
* '@models' => '@application/models', // an existing alias
* '@backend' => __DIR__ . '/../backend', // a directory
* )
* ~~~
@ -305,7 +319,7 @@ abstract class Module extends Component
public function setAliases($aliases)
{
foreach ($aliases as $name => $alias) {
\Yii::setAlias($name, $alias);
Yii::setAlias($name, $alias);
}
}
@ -334,8 +348,8 @@ abstract class Module extends Component
if ($this->_modules[$id] instanceof Module) {
return $this->_modules[$id];
} elseif ($load) {
\Yii::trace("Loading \"$id\" module", __CLASS__);
return $this->_modules[$id] = \Yii::createObject($this->_modules[$id], $id, $this);
Yii::trace("Loading module: $id", __CLASS__);
return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
}
}
return null;
@ -388,7 +402,7 @@ abstract class Module extends Component
*
* Each sub-module should be specified as a name-value pair, where
* name refers to the ID of the module and value the module or a configuration
* array that can be used to create the module. In the latter case, [[\Yii::createObject()]]
* array that can be used to create the module. In the latter case, [[Yii::createObject()]]
* will be used to create the module.
*
* If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
@ -418,8 +432,8 @@ abstract class Module extends Component
/**
* Checks whether the named component exists.
* @param string $id application component ID
* @return boolean whether the named application component exists. Both loaded and unloaded components
* @param string $id component ID
* @return boolean whether the named component exists. Both loaded and unloaded components
* are considered.
*/
public function hasComponent($id)
@ -428,11 +442,10 @@ abstract class Module extends Component
}
/**
* Retrieves the named application component.
* @param string $id application component ID (case-sensitive)
* Retrieves the named component.
* @param string $id component ID (case-sensitive)
* @param boolean $load whether to load the component if it is not yet loaded.
* @return Component|null the application component instance, null if the application component
* does not exist.
* @return Component|null the component instance, null if the component does not exist.
* @see hasComponent()
*/
public function getComponent($id, $load = true)
@ -441,22 +454,22 @@ abstract class Module extends Component
if ($this->_components[$id] instanceof Component) {
return $this->_components[$id];
} elseif ($load) {
\Yii::trace("Loading \"$id\" application component", __CLASS__);
return $this->_components[$id] = \Yii::createObject($this->_components[$id]);
Yii::trace("Loading component: $id", __CLASS__);
return $this->_components[$id] = Yii::createObject($this->_components[$id]);
}
}
return null;
}
/**
* Registers an application component in this module.
* Registers a component with this module.
* @param string $id component ID
* @param Component|array|null $component the component to be added to the module. This can
* @param Component|array|null $component the component to be registered with the module. This can
* be one of the followings:
*
* - a [[Component]] object
* - a configuration array: when [[getComponent()]] is called initially for this component, the array
* will be used to instantiate the component
* will be used to instantiate the component via [[Yii::createObject()]].
* - null: the named component will be removed from the module
*/
public function setComponent($id, $component)
@ -469,11 +482,11 @@ abstract class Module extends Component
}
/**
* Returns the application components.
* Returns the registered components.
* @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
* then all components specified in the configuration will be returned, whether they are loaded or not.
* Loaded components will be returned as objects, while unloaded components as configuration arrays.
* @return array the application components (indexed by their IDs)
* @return array the components (indexed by their IDs)
*/
public function getComponents($loadedOnly = false)
{
@ -491,11 +504,11 @@ abstract class Module extends Component
}
/**
* Registers a set of application components in this module.
* Registers a set of components in this module.
*
* Each application component should be specified as a name-value pair, where
* Each component should be specified as a name-value pair, where
* name refers to the ID of the component and value the component or a configuration
* array that can be used to create the component. In the latter case, [[\Yii::createObject()]]
* array that can be used to create the component. In the latter case, [[Yii::createObject()]]
* will be used to create the component.
*
* If a new component has the same ID as an existing one, the existing one will be overwritten silently.
@ -515,7 +528,7 @@ abstract class Module extends Component
* )
* ~~~
*
* @param array $components application components (id => component configuration or instance)
* @param array $components components (id => component configuration or instance)
*/
public function setComponents($components)
{
@ -525,7 +538,7 @@ abstract class Module extends Component
}
/**
* Loads application components that are declared in [[preload]].
* Loads components that are declared in [[preload]].
*/
public function preloadComponents()
{
@ -533,4 +546,79 @@ abstract class Module extends Component
$this->getComponent($id);
}
}
/**
* Runs a controller action specified by a route.
* This method parses the specified route and creates the corresponding child module(s), controller and action
* instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
* If the route is empty, the method will use [[defaultRoute]].
* @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.
* @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
*/
public function runAction($route, $params = array())
{
$result = $this->createController($route);
if (is_array($result)) {
/** @var $controller Controller */
list($controller, $actionID) = $result;
$oldController = Yii::$application->controller;
Yii::$application->controller = $controller;
$status = $controller->runAction($actionID, $params);
Yii::$application->controller = $oldController;
return $status;
} else {
throw new InvalidRouteException('Unable to resolve the request: ' . trim($this->getUniqueId() . '/' . $route, '/'));
}
}
/**
* Creates a controller instance based on the controller ID.
*
* The controller is created within this module. The method first attempts to
* create the controller based on the [[controllerMap]] of the module. If not available,
* it will look for the controller class under the [[controllerPath]] and create an
* instance of it.
*
* @param string $route the route consisting of module, controller and action IDs.
* @return array|boolean if the controller is created successfully, it will be returned together
* with the remainder of the route which represents the action ID. Otherwise false will be returned.
*/
public function createController($route)
{
if ($route === '') {
$route = $this->defaultRoute;
}
if (($pos = strpos($route, '/')) !== false) {
$id = substr($route, 0, $pos);
$route = substr($route, $pos + 1);
} else {
$id = $route;
$route = '';
}
$module = $this->getModule($id);
if ($module !== null) {
return $module->createController($route);
}
if (isset($this->controllerMap[$id])) {
$controller = Yii::createObject($this->controllerMap[$id], $id, $this);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
$className = StringHelper::id2camel($id) . 'Controller';
$classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
if (is_file($classFile)) {
$className = $this->controllerNamespace . '\\' . $className;
if (!class_exists($className, false)) {
require($classFile);
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
$controller = new $className($id, $this);
}
}
}
return isset($controller) ? array($controller, $route) : false;
}
}

7
framework/base/NotSupportedException.php

@ -17,5 +17,12 @@ namespace yii\base;
*/
class NotSupportedException extends \Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Not Supported');
}
}

105
framework/base/Theme.php

@ -9,55 +9,114 @@
namespace yii\base;
use Yii;
use yii\base\InvalidConfigException;
use yii\util\FileHelper;
/**
* 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>
* @since 2.0
*/
class Theme extends Component
{
/**
* @var string the root path of this theme.
* @see pathMap
*/
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()
{
if ($this->basePath !== null) {
$this->basePath = \Yii::getAlias($this->basePath, true);
} else {
throw new InvalidConfigException("Theme.basePath must be set.");
parent::init();
if (empty($this->pathMap)) {
if ($this->basePath !== null) {
$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) {
$this->baseUrl = \Yii::getAlias($this->baseUrl, true);
} else {
throw new InvalidConfigException("Theme.baseUrl must be set.");
$paths = array();
foreach ($this->pathMap as $from => $to) {
$paths[FileHelper::normalizePath($from) . DIRECTORY_SEPARATOR] = FileHelper::normalizePath($to) . DIRECTORY_SEPARATOR;
}
$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
* @return string
* Converts a file to a themed file if possible.
* 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';
if ($context === null || $context instanceof Application) {
return $viewPath;
} elseif ($context instanceof Controller || $context instanceof Module) {
return $viewPath . DIRECTORY_SEPARATOR . $context->getUniqueId();
} else {
return $viewPath . DIRECTORY_SEPARATOR . str_replace('\\', '_', get_class($context));
$path = FileHelper::normalizePath($path);
foreach ($this->pathMap as $from => $to) {
if (strpos($path, $from) === 0) {
$n = strlen($from);
$file = $to . substr($path, $n);
if (is_file($file)) {
return $file;
}
}
}
return $path;
}
/**
* @param Module $module
* @return string
* Converts a relative URL into an absolute URL using [[basePath]].
* @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, '/');
}
}

7
framework/base/UnknownMethodException.php

@ -17,5 +17,12 @@ namespace yii\base;
*/
class UnknownMethodException extends \Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Unknown Method');
}
}

7
framework/base/UnknownPropertyException.php

@ -17,5 +17,12 @@ namespace yii\base;
*/
class UnknownPropertyException extends \Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Unknown Property');
}
}

558
framework/base/View.php

@ -9,27 +9,29 @@
namespace yii\base;
use Yii;
use yii\util\FileHelper;
use yii\base\Application;
/**
* View represents a view object in the MVC pattern.
*
* View provides a set of methods (e.g. [[render()]]) for rendering purpose.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class View extends Component
{
/**
* @var Controller|Widget|Object the context under which this view is being rendered
* @var object the object that owns this view. This can be a controller, a widget, or any other object.
*/
public $context;
public $owner;
/**
* @var string|array the directories where the view file should be looked for when a *relative* view name is given.
* This can be either a string representing a single directory, or an array representing multiple directories.
* If the latter, the view file will be looked for in the directories in the order they are specified.
* Path aliases can be used. If this property is not set, relative view names should be treated as absolute ones.
* @see roothPath
* @var string the layout to be applied when [[render()]] or [[renderContent()]] is called.
* If not set, it will use the [[Module::layout]] of the currently active module.
*/
public $basePath;
public $layout;
/**
* @var string the language that the view should be rendered in. If not set, it will use
* the value of [[Application::language]].
@ -45,45 +47,67 @@ class View extends Component
* Note that when this is true, if a localized view cannot be found, the original view will be rendered.
* No error will be reported.
*/
public $localizeView = true;
public $enableI18N = true;
/**
* @var boolean whether to theme the view when possible. Defaults to true.
* Note that theming will be disabled if [[Application::theme]] is null.
* Note that theming will be disabled if [[Application::theme]] is not set.
*/
public $themeView = true;
public $enableTheme = true;
/**
* @var mixed custom parameters that are available in the view template
*/
public $params;
/**
* @var Widget[] the widgets that are currently not ended
*/
protected $widgetStack = array();
private $_widgetStack = array();
/**
* Constructor.
* @param Controller|Widget|Object $context the context under which this view is being rendered (e.g. controller, widget)
* @param object $owner the owner of this view. This usually is a controller or a widget.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($context = null, $config = array())
public function __construct($owner, $config = array())
{
$this->context = $context;
$this->owner = $owner;
parent::__construct($config);
}
/**
* Renders a view within a layout.
* This method is similar to [[renderPartial()]] except that if a layout is available,
* this method will embed the view result into the layout and then return it.
* @param string $view the view to be rendered. Please refer to [[findViewFile()]] on possible formats of the view name.
* @param array $params the parameters that should be made available in the view. The PHP function `extract()`
* will be called on this variable to extract the variables from this parameter.
* @return string the rendering result
* @throws InvalidConfigException if the view file or layout file cannot be found
* @see findViewFile()
* @see findLayoutFile()
*/
public function render($view, $params = array())
{
$content = $this->renderPartial($view, $params);
return $this->renderText($content);
return $this->renderContent($content);
}
public function renderText($text)
/**
* Renders a text content within a layout.
* The layout being used is resolved by [[findLayout()]].
* If no layout is available, the content will be returned back.
* @param string $content the content to be rendered
* @return string the rendering result
* @throws InvalidConfigException if the layout file cannot be found
* @see findLayoutFile()
*/
public function renderContent($content)
{
$layoutFile = $this->findLayoutFile();
if ($layoutFile !== false) {
return $this->renderFile($layoutFile, array('content' => $text));
return $this->renderFile($layoutFile, array('content' => $content));
} else {
return $text;
return $content;
}
}
@ -94,18 +118,12 @@ class View extends Component
* It then calls [[renderFile()]] to render the view file. The rendering result is returned
* as a string. If the view file does not exist, an exception will be thrown.
*
* To determine which view file should be rendered, the method calls [[findViewFile()]] which
* 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`),
* or a path relative to [[basePath]]. The file suffix is optional and defaults to `.php` if not given
* in the view name.
*
* @param string $view the view to be rendered. This can be either a path alias or a path relative to [[basePath]].
* @param string $view the view to be rendered. Please refer to [[findViewFile()]] on possible formats of the view name.
* @param array $params the parameters that should be made available in the view. The PHP function `extract()`
* will be called on this variable to extract the variables from this parameter.
* @return string the rendering result
* @throws InvalidCallException if the view file cannot be found
* @see findViewFile()
*/
public function renderPartial($view, $params = array())
{
@ -119,21 +137,44 @@ class View extends Component
/**
* Renders a view file.
* @param string $file the view file path
* @param array $params the parameters to be extracted and made available in the view file
* This method will extract the given parameters and include the view file.
* It captures the output of the included view file and returns it as a string.
* @param string $_file_ the view file.
* @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
* @return string the rendering result
*/
public function renderFile($file, $params = array())
public function renderFile($_file_, $_params_ = array())
{
return $this->renderFileInternal($file, $params);
ob_start();
ob_implicit_flush(false);
extract($_params_, EXTR_OVERWRITE);
require($_file_);
return ob_get_clean();
}
/**
* Creates a widget.
* This method will use [[Yii::createObject()]] to create the widget.
* @param string $class the widget class name or path alias
* @param array $properties the initial property values of the widget.
* @return Widget the newly created widget instance
*/
public function createWidget($class, $properties = array())
{
$properties['class'] = $class;
return \Yii::createObject($properties, $this->context);
return Yii::createObject($properties, $this->owner);
}
/**
* Creates and runs a widget.
* Compared with [[createWidget()]], this method does one more thing: it will
* run the widget after it is created.
* @param string $class the widget class name or path alias
* @param array $properties the initial property values of the widget.
* @param boolean $captureOutput whether to capture the output of the widget and return it as a string
* @return string|Widget if $captureOutput is true, the output of the widget will be returned;
* otherwise the widget object will be returned.
*/
public function widget($class, $properties = array(), $captureOutput = false)
{
if ($captureOutput) {
@ -151,14 +192,16 @@ class View extends Component
/**
* Begins a widget.
* @param string $class the widget class
* @param array $properties the initial property values of the widget
* This method is similar to [[createWidget()]] except that it will expect a matching
* [[endWidget()]] call after this.
* @param string $class the widget class name or path alias
* @param array $properties the initial property values of the widget.
* @return Widget the widget instance
*/
public function beginWidget($class, $properties = array())
{
$widget = $this->createWidget($class, $properties);
$this->widgetStack[] = $widget;
$this->_widgetStack[] = $widget;
return $widget;
}
@ -172,293 +215,256 @@ class View extends Component
*/
public function endWidget()
{
/** @var $widget Widget */
if (($widget = array_pop($this->widgetStack)) !== null) {
$widget = array_pop($this->_widgetStack);
if ($widget instanceof Widget) {
$widget->run();
return $widget;
} else {
throw new Exception("Unmatched beginWidget() and endWidget() calls.");
}
}
//
// /**
// * Begins recording a clip.
// * This method is a shortcut to beginning [[yii\widgets\Clip]]
// * @param string $id the clip ID.
// * @param array $properties initial property values for [[yii\widgets\Clip]]
// */
// public function beginClip($id, $properties = array())
// {
// $properties['id'] = $id;
// $this->beginWidget('yii\widgets\Clip', $properties);
// }
//
// /**
// * Ends recording a clip.
// */
// public function endClip()
// {
// $this->endWidget();
// }
//
// /**
// * Begins fragment caching.
// * This method will display cached content if it is available.
// * If not, it will start caching and would expect an [[endCache()]]
// * call to end the cache and save the content into cache.
// * A typical usage of fragment caching is as follows,
// *
// * ~~~
// * if($this->beginCache($id)) {
// * // ...generate content here
// * $this->endCache();
// * }
// * ~~~
// *
// * @param string $id a unique ID identifying the fragment to be cached.
// * @param array $properties initial property values for [[yii\widgets\OutputCache]]
// * @return boolean whether we need to generate content for caching. False if cached version is available.
// * @see endCache
// */
// public function beginCache($id, $properties = array())
// {
// $properties['id'] = $id;
// $cache = $this->beginWidget('yii\widgets\OutputCache', $properties);
// if ($cache->getIsContentCached()) {
// $this->endCache();
// return false;
// } else {
// return true;
// }
// }
//
// /**
// * Ends fragment caching.
// * This is an alias to [[endWidget()]]
// * @see beginCache
// */
// public function endCache()
// {
// $this->endWidget();
// }
//
// /**
// * Begins the rendering of content that is to be decorated by the specified view.
// * @param mixed $view the name of the view that will be used to decorate the content. The actual view script
// * is resolved via {@link getViewFile}. If this parameter is null (default),
// * the default layout will be used as the decorative view.
// * Note that if the current controller does not belong to
// * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
// * If the controller belongs to a module, the default layout refers to the module's
// * {@link CWebModule::layout default layout}.
// * @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
// * @see endContent
// * @see yii\widgets\ContentDecorator
// */
// public function beginContent($view, $params = array())
// {
// $this->beginWidget('yii\widgets\ContentDecorator', array(
// 'view' => $view,
// 'params' => $params,
// ));
// }
//
// /**
// * Ends the rendering of content.
// * @see beginContent
// */
// public function endContent()
// {
// $this->endWidget();
// }
/**
* Begins recording a clip.
* This method is a shortcut to beginning [[yii\widgets\Clip]]
* @param string $id the clip ID.
* @param array $properties initial property values for [[yii\widgets\Clip]]
*/
public function beginClip($id, $properties = array())
{
$properties['id'] = $id;
$this->beginWidget('yii\widgets\Clip', $properties);
}
/**
* Ends recording a clip.
*/
public function endClip()
{
$this->endWidget();
}
/**
* Begins fragment caching.
* This method will display cached content if it is available.
* If not, it will start caching and would expect an [[endCache()]]
* call to end the cache and save the content into cache.
* A typical usage of fragment caching is as follows,
* Finds the view file based on the given view name.
*
* ~~~
* if($this->beginCache($id)) {
* // ...generate content here
* $this->endCache();
* }
* ~~~
* A view name can be specified in one of the following formats:
*
* - path alias (e.g. "@application/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
* active module.
* - relative path (e.g. "index"): the actual view file will be looked for under the [[owner]]'s view path.
* If [[owner]] is a widget or a controller, its view path is given by their `viewPath` property.
* If [[owner]] is an object of any other type, its view path is the `view` sub-directory of the directory
* containing the owner class file.
*
* If the view name does not contain a file extension, it will default to `.php`.
*
* If [[enableTheme]] is true and there is an active application them, the method will also
* attempt to use a themed version of the view file, when available.
*
* And if [[enableI18N]] is true, the method will attempt to use a translated version of the view file,
* when available.
*
* @param string $id a unique ID identifying the fragment to be cached.
* @param array $properties initial property values for [[yii\widgets\OutputCache]]
* @return boolean whether we need to generate content for caching. False if cached version is available.
* @see endCache
*/
public function beginCache($id, $properties = array())
{
$properties['id'] = $id;
$cache = $this->beginWidget('yii\widgets\OutputCache', $properties);
if ($cache->getIsContentCached()) {
$this->endCache();
return false;
} else {
return true;
}
}
/**
* Ends fragment caching.
* This is an alias to [[endWidget()]]
* @see beginCache
*/
public function endCache()
{
$this->endWidget();
}
/**
* Begins the rendering of content that is to be decorated by the specified view.
* @param mixed $view the name of the view that will be used to decorate the content. The actual view script
* is resolved via {@link getViewFile}. If this parameter is null (default),
* the default layout will be used as the decorative view.
* Note that if the current controller does not belong to
* any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
* If the controller belongs to a module, the default layout refers to the module's
* {@link CWebModule::layout default layout}.
* @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
* @see endContent
* @see yii\widgets\ContentDecorator
*/
public function beginContent($view, $params = array())
{
$this->beginWidget('yii\widgets\ContentDecorator', array(
'view' => $view,
'params' => $params,
));
}
/**
* Ends the rendering of content.
* @see beginContent
*/
public function endContent()
{
$this->endWidget();
}
/**
* Renders a view file.
* This method will extract the given parameters and include the view file.
* It captures the output of the included view file and returns it as a string.
* @param string $_file_ the view file.
* @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
* @return string the rendering result
*/
protected function renderFileInternal($_file_, $_params_ = array())
{
ob_start();
ob_implicit_flush(false);
extract($_params_, EXTR_OVERWRITE);
require($_file_);
return ob_get_clean();
}
/**
* Finds the view file based on the given view name.
* @param string $view the view name or path alias. If the view name does not specify
* the view file extension name, it will use `.php` as the extension name.
* @return string|boolean the view file if it exists. False if the view file cannot be found.
* @return string the view file path if it exists. False if the view file cannot be found.
* @throws InvalidConfigException if the view file does not exist
*/
public function findViewFile($view)
{
if (($extension = FileHelper::getExtension($view)) === '') {
if (FileHelper::getExtension($view) === '') {
$view .= '.php';
}
if (strncmp($view, '@', 1) === 0) {
$file = \Yii::getAlias($view);
// e.g. "@application/views/common"
if (($file = Yii::getAlias($view)) === false) {
throw new InvalidConfigException("Invalid path alias: $view");
}
} elseif (strncmp($view, '/', 1) !== 0) {
$file = $this->findRelativeViewFile($view);
} else {
$file = $this->findAbsoluteViewFile($view);
}
if ($file === false || !is_file($file)) {
return false;
} elseif ($this->localizeView) {
return FileHelper::localize($file, $this->language, $this->sourceLanguage);
// e.g. "index"
if ($this->owner instanceof Controller || $this->owner instanceof Widget) {
$file = $this->owner->getViewPath() . DIRECTORY_SEPARATOR . $view;
} elseif ($this->owner !== null) {
$class = new \ReflectionClass($this->owner);
$file = dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $view;
} else {
$file = Yii::$application->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
} elseif (strncmp($view, '//', 2) !== 0 && Yii::$application->controller !== null) {
// e.g. "/site/index"
$file = Yii::$application->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
return $file;
// e.g. "//layouts/main"
$file = Yii::$application->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
}
}
/**
* Finds the view file corresponding to the given relative view name.
* The method will look for the view file under a set of directories returned by [[resolveBasePath()]].
* If no base path is given, the view will be treated as an absolute view and the result of
* [[findAbsoluteViewFile()]] will be returned.
* @param string $view the relative view name
* @return string|boolean the view file path, or false if the view file cannot be found
*/
protected function findRelativeViewFile($view)
{
$paths = $this->resolveBasePath();
if ($paths === array()) {
return $this->findAbsoluteViewFile($view);
}
if ($this->themeView && $this->context !== null && ($theme = \Yii::$application->getTheme()) !== null) {
array_unshift($paths, $theme->getViewPath($this->context));
}
foreach ($paths as $path) {
$file = \Yii::getAlias($path . '/' . $view);
if ($file !== false && is_file($file)) {
return $file;
if (is_file($file)) {
if ($this->enableTheme && ($theme = Yii::$application->getTheme()) !== null) {
$file = $theme->apply($file);
}
}
return $paths === array() ? $this->findAbsoluteViewFile($view) : false;
}
/**
* Finds the view file corresponding to the given absolute view name.
* If the view name starts with double slashes `//`, the method will look for the view file
* under [[Application::getViewPath()]]. Otherwise, it will look for the view file under the
* view path of the currently active module.
* @param string $view the absolute view name
* @return string|boolean the view file path, or false if the view file cannot be found
*/
protected function findAbsoluteViewFile($view)
{
$app = \Yii::$application;
if (strncmp($view, '//', 2) !== 0 && $app->controller !== null) {
$module = $app->controller->module;
return $this->enableI18N ? FileHelper::localize($file, $this->language, $this->sourceLanguage) : $file;
} else {
$module = $app;
throw new InvalidConfigException("View file for view '$view' does not exist: $file");
}
if ($this->themeView && ($theme = $app->getTheme()) !== null) {
$paths[] = $theme->getViewPath($module);
}
$paths[] = $module->getViewPath();
$view = ltrim($view, '/');
foreach ($paths as $path) {
$file = \Yii::getAlias($path . '/' . $view);
if ($file !== false && is_file($file)) {
return $file;
}
}
return false;
}
/**
* Resolves the base paths that will be used to determine view files for relative view names.
* The method resolves the base path using the following algorithm:
* Finds the layout file that can be applied to the view.
*
* - If [[basePath]] is not empty, it is returned;
* - If [[context]] is a controller, it will return the subdirectory named as
* [[Controller::uniqueId]] under the controller's module view path;
* - If [[context]] is an object, it will return the `views` subdirectory under
* the directory containing the object class file.
* - Otherwise, it will return false.
* @return array the base paths
*/
protected function resolveBasePath()
{
if (!empty($this->basePath)) {
return (array)$this->basePath;
} elseif ($this->context instanceof Controller) {
return array($this->context->module->getViewPath() . '/' . $this->context->getUniqueId());
} elseif ($this->context !== null) {
$class = new \ReflectionClass($this->context);
return array(dirname($class->getFileName()) . '/views');
} else {
return array();
}
}
/**
* Finds the layout file for the current [[context]].
* The method will return false if [[context]] is not a controller.
* When [[context]] is a controller, the following algorithm is used to determine the layout file:
* The applicable layout is resolved according to the following rules:
*
* - If [[layout]] is specified as a string, use it as the layout name and search for the layout file
* under the layout path of the currently active module;
* - If [[layout]] is null and [[owner]] is a controller:
* * If the controller's [[Controller::layout|layout]] is a string, use it as the layout name
* and search for the layout file under the layout path of the parent module of the controller;
* * If the controller's [[Controller::layout|layout]] is null, look through its ancestor modules
* and find the first one whose [[Module::layout|layout]] is not null. Use the layout specified
* by that module;
* - Returns false for all other cases.
*
* Like view names, a layout name can take several formats:
*
* - If `context.layout` is false, it will return false;
* - If `context.layout` is a string, it will look for the layout file under the [[Module::layoutPath|layout path]]
* of the controller's parent module;
* - If `context.layout` is null, the following steps are taken to resolve the actual layout to be returned:
* * Check the `layout` property of the parent module. If it is null, check the grand parent module and so on
* until a non-null layout is encountered. Let's call this module the *effective module*.
* * If the layout is null or false, it will return false;
* * Otherwise, it will look for the layout file under the layout path of the effective module.
* - path alias (e.g. "@application/views/layouts/main");
* - absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - relative path (e.g. "main"): the actual layout layout file will be looked for under the
* [[Module::viewPath|view path]] of the context module determined by the above layout resolution process.
*
* The themed layout file will be returned if theme is enabled and the theme contains such a layout file.
* If the layout name does not contain a file extension, it will default to `.php`.
*
* @return string|boolean the layout file path, or false if the context does not need layout.
* @throws InvalidCallException if the layout file cannot be found
* If [[enableTheme]] is true and there is an active application them, the method will also
* attempt to use a themed version of the layout file, when available.
*
* And if [[enableI18N]] is true, the method will attempt to use a translated version of the layout file,
* when available.
*
* @return string|boolean the layout file path, or false if layout is not needed.
* @throws InvalidConfigException if the layout file cannot be found
*/
public function findLayoutFile()
{
if (!$this->context instanceof Controller || $this->context->layout === false) {
return false;
}
$module = $this->context->module;
while ($module !== null && $module->layout === null) {
$module = $module->module;
/** @var $module Module */
if (is_string($this->layout)) {
if (Yii::$application->controller) {
$module = Yii::$application->controller->module;
} else {
$module = Yii::$application;
}
$view = $this->layout;
} elseif ($this->owner instanceof Controller) {
if (is_string($this->owner->layout)) {
$module = $this->owner->module;
$view = $this->owner->layout;
} elseif ($this->owner->layout === null) {
$module = $this->owner->module;
while ($module !== null && $module->layout === null) {
$module = $module->module;
}
if ($module !== null && is_string($module->layout)) {
$view = $module->layout;
}
}
}
if ($module === null || $module->layout === null || $module->layout === false) {
if (!isset($view)) {
return false;
}
$view = $module->layout;
if (($extension = FileHelper::getExtension($view)) === '') {
if (FileHelper::getExtension($view) === '') {
$view .= '.php';
}
if (strncmp($view, '@', 1) === 0) {
$file = \Yii::getAlias($view);
if (($file = Yii::getAlias($view)) === false) {
throw new InvalidConfigException("Invalid path alias: $view");
}
} elseif (strncmp($view, '/', 1) === 0) {
$file = $this->findAbsoluteViewFile($view);
$file = Yii::$application->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
} else {
if ($this->themeView && ($theme = \Yii::$application->getTheme()) !== null) {
$paths[] = $theme->getLayoutPath($module);
}
$paths[] = $module->getLayoutPath();
$file = false;
foreach ($paths as $path) {
$f = \Yii::getAlias($path . '/' . $view);
if ($f !== false && is_file($f)) {
$file = $f;
break;
}
}
$file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
}
if ($file === false || !is_file($file)) {
throw new InvalidCallException("Unable to find the layout file for layout '{$module->layout}' (specified by " . get_class($module) . ")");
} elseif ($this->localizeView) {
return FileHelper::localize($file, $this->language, $this->sourceLanguage);
if (is_file($file)) {
if ($this->enableTheme && ($theme = Yii::$application->getTheme()) !== null) {
$file = $theme->apply($file);
}
return $this->enableI18N ? FileHelper::localize($file, $this->language, $this->sourceLanguage) : $file;
} else {
return $file;
throw new InvalidConfigException("Layout file for layout '$view' does not exist: $file");
}
}
}

16
framework/base/Widget.php

@ -49,7 +49,7 @@ class Widget extends Component
public function getId($autoGenerate = true)
{
if ($autoGenerate && $this->_id === null) {
$this->_id = 'yw' . self::$_counter++;
$this->_id = 'w' . self::$_counter++;
}
return $this->_id;
}
@ -80,7 +80,7 @@ class Widget extends Component
* To determine which view file should be rendered, the method calls [[findViewFile()]] which
* 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
* in the view name.
*
@ -102,4 +102,16 @@ class Widget extends Component
{
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';
}
}

43
framework/console/Application.php

@ -10,7 +10,7 @@
namespace yii\console;
use yii\base\Exception;
use yii\util\ReflectionHelper;
use yii\base\InvalidRouteException;
/**
* Application represents a console application.
@ -85,40 +85,36 @@ class Application extends \yii\base\Application
* Processes the request.
* 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)
* @throws Exception if the route cannot be resolved into a controller
*/
public function processRequest()
{
/** @var $request Request */
$request = $this->getRequest();
if ($request->getIsConsoleRequest()) {
return $this->runController($request->route, $request->params);
return $this->runAction($request->route, $request->params);
} 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.
* @param string $route the route (e.g. `post/create`)
* @param array $params the parameters to be passed to the controller action
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
* @throws Exception if the route cannot be resolved into a controller
* Runs a controller action specified by a route.
* This method parses the specified route and creates the corresponding child module(s), controller and action
* instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
* If the route is empty, the method will use [[defaultRoute]].
* @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);
if ($result === false) {
throw new Exception(\Yii::t('yii', 'Unable to resolve the request.'));
try {
return parent::runAction($route, $params);
} 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
),
));
}
public function usageError($message)
{
}
}

118
framework/console/Controller.php

@ -9,8 +9,10 @@
namespace yii\console;
use Yii;
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.
@ -30,72 +32,56 @@ use yii\base\Exception;
class Controller extends \yii\base\Controller
{
/**
* This method is invoked when the request parameters do not satisfy the requirement of the specified action.
* The default implementation will throw an exception.
* @param Action $action the action being executed
* @param Exception $exception the exception about the invalid parameters
* @var boolean whether the call of [[confirm()]] requires a user input.
* If false, [[confirm()]] will always return true no matter what user enters or not.
*/
public function invalidActionParams($action, $exception)
{
echo \Yii::t('yii', 'Error: {message}', array(
'{message}' => $exception->getMessage(),
));
\Yii::$application->end(1);
}
public $interactive = true;
/**
* This method is invoked when extra parameters are provided to an action while it is executed.
* @param Action $action the action being executed
* @param array $expected the expected action parameters (name => value)
* @param array $actual the actual action parameters (name => value)
* Runs an action with the specified action ID and parameters.
* If the action ID is empty, the method will use [[defaultAction]].
* @param string $id the ID of the action to be executed.
* @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']);
$keys = array_diff(array_keys($actual), array_keys($expected));
if (!empty($keys)) {
echo \Yii::t('yii', 'Error: Unknown parameter(s): {params}', array(
'{params}' => implode(', ', $keys),
)) . "\n";
\Yii::$application->end(1);
if ($params !== array()) {
$class = new \ReflectionClass($this);
foreach ($params as $name => $value) {
if ($class->hasProperty($name)) {
$property = $class->getProperty($name);
if ($property->isPublic() && !$property->isStatic() && $property->getDeclaringClass()->getName() === get_class($this)) {
$this->$name = $value;
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.
*
* @param string $message to echo out before waiting for user input
* @param string $default the default string to be returned when user does not write anything.
* Defaults to null, means that default string is disabled.
* @return mixed line read as a string, or false if input has been closed
* Validates the parameter being bound to actions.
* This method is invoked when parameters are being bound to the currently requested action.
* Child classes may override this method to throw exceptions when there are missing and/or unknown parameters.
* @param Action $action the currently requested action
* @param array $missingParams the names of the missing parameters
* @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) {
$message .= " [$default] ";
}
else {
$message .= ' ';
}
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;
if (!empty($missingParams)) {
throw new InvalidRequestException(Yii::t('yii', 'Missing required options: {params}', array(
'{params}' => implode(', ', $missingParams),
)));
} elseif (!empty($unknownParams)) {
throw new InvalidRequestException(Yii::t('yii', 'Unknown options: {params}', array(
'{params}' => implode(', ', $unknownParams),
)));
}
}
@ -108,9 +94,23 @@ class Controller extends \yii\base\Controller
*/
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));
return empty($input) ? $default : !strncasecmp($input, 'y', 1);
public function globalOptions()
{
return array();
}
}

4
framework/console/controllers/CreateController.php

@ -165,8 +165,8 @@ class CreateController extends Controller
}
/**
* @param string $path1 abosolute path
* @param string $path2 abosolute path
* @param string $path1 absolute path
* @param string $path2 absolute path
*
* @return string relative path
*/

35
framework/console/controllers/HelpController.php

@ -12,6 +12,7 @@ namespace yii\console\controllers;
use yii\base\Application;
use yii\base\InlineAction;
use yii\console\Controller;
use yii\util\StringHelper;
/**
* This command provides help information about console commands.
@ -54,16 +55,16 @@ class HelpController extends Controller
} else {
$result = \Yii::$application->createController($args[0]);
if ($result === false) {
echo "Unknown command: " . $args[0] . "\n";
echo "Error: no help for unknown command \"{$args[0]}\".\n";
return 1;
}
list($controller, $action) = $result;
list($controller, $actionID) = $result;
if ($action === '') {
if ($actionID === '') {
$status = $this->getControllerHelp($controller);
} else {
$status = $this->getActionHelp($controller, $action);
$status = $this->getActionHelp($controller, $actionID);
}
}
return $status;
@ -87,13 +88,13 @@ class HelpController extends Controller
*/
public function getActions($controller)
{
$actions = array_keys($controller->actionMap);
$actions = array_keys($controller->actions());
$class = new \ReflectionClass($controller);
foreach ($class->getMethods() as $method) {
/** @var $method \ReflectionMethod */
$name = $method->getName();
if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0) {
$actions[] = lcfirst(substr($name, 6));
if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
$actions[] = StringHelper::camel2id(substr($name, 6));
}
}
sort($actions);
@ -107,11 +108,7 @@ class HelpController extends Controller
*/
protected function getModuleCommands($module)
{
if ($module instanceof Application) {
$prefix = '';
} else {
$prefix = $module->getUniqueId() . '/';
}
$prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';
$commands = array();
foreach (array_keys($module->controllerMap) as $id) {
@ -145,12 +142,12 @@ class HelpController extends Controller
{
$commands = $this->getCommands();
if ($commands !== array()) {
echo "\n Usage: yiic <command-name> [...options...]\n\n";
echo "The following commands are available:\n";
echo "\nUsage: yiic <command-name> [...options...]\n\n";
echo "The following commands are available:\n\n";
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";
} else {
echo "\nNo commands are found.\n";
@ -195,7 +192,7 @@ class HelpController extends Controller
$prefix = $controller->getUniqueId();
foreach ($actions as $action) {
if ($controller->defaultAction === $action) {
echo " * $prefix/$action (default)\n";
echo " * $prefix (default)\n";
} else {
echo " * $prefix/$action\n";
}
@ -216,7 +213,7 @@ class HelpController extends Controller
{
$action = $controller->createAction($actionID);
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;
}
if ($action instanceof InlineAction) {
@ -312,7 +309,7 @@ class HelpController extends Controller
{
$options = array();
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;
}
$name = $property->getName();

432
framework/console/controllers/MigrateController.php

@ -10,6 +10,7 @@
namespace yii\console\controllers;
use Yii;
use yii\console\Controller;
/**
@ -60,25 +61,25 @@ use yii\console\Controller;
*/
class MigrateController extends Controller
{
const BASE_MIGRATION='m000000_000000_base';
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';
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';
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';
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).
@ -88,26 +89,29 @@ class MigrateController extends Controller
/**
* @var string the default command action. It defaults to 'up'.
*/
public $defaultAction='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 $interactive = true;
public function beforeAction($action)
{
$path = \Yii::getAlias($this->migrationPath);
if($path===false || !is_dir($path)) {
echo 'Error: The migration directory does not exist: ' . $this->migrationPath . "\n";
\Yii::$application->end(1);
if (parent::beforeAction($action)) {
$path = Yii::getAlias($this->migrationPath);
if ($path === false || !is_dir($path)) {
echo 'Error: the migration directory does not exist "' . $this->migrationPath . "\"\n";
return false;
}
$this->migrationPath = $path;
$version = Yii::getVersion();
echo "\nYii Migration Tool v2.0 (based on Yii v{$version})\n\n";
return true;
} else {
return false;
}
$this->migrationPath=$path;
$yiiVersion = \Yii::getVersion();
echo "\nYii Migration Tool v2.0 (based on Yii v{$yiiVersion})\n\n";
return parent::beforeAction($action);
}
/**
@ -115,34 +119,32 @@ class MigrateController extends Controller
*/
public function actionUp($args)
{
if(($migrations = $this->getNewMigrations())===array())
{
if (($migrations = $this->getNewMigrations()) === array()) {
echo "No new migration found. Your system is up-to-date.\n";
\Yii::$application->end();
Yii::$application->end();
}
$total=count($migrations);
$step=isset($args[0]) ? (int)$args[0] : 0;
if($step>0) {
$migrations=array_slice($migrations,0,$step);
$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";
$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)
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)
{
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;
}
@ -153,29 +155,27 @@ class MigrateController extends Controller
public function actionDown($args)
{
$step=isset($args[0]) ? (int)$args[0] : 1;
if($step<1)
$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())
{
if (($migrations = $this->getMigrationHistory($step)) === array()) {
echo "No migration has been done before.\n";
return;
}
$migrations=array_keys($migrations);
$migrations = array_keys($migrations);
$n=count($migrations);
echo "Total $n ".($n===1 ? 'migration':'migrations')." to be reverted:\n";
foreach($migrations as $migration)
$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)
{
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;
}
@ -186,37 +186,33 @@ class MigrateController extends Controller
public function actionRedo($args)
{
$step=isset($args[0]) ? (int)$args[0] : 1;
if($step<1)
$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())
{
if (($migrations = $this->getMigrationHistory($step)) === array()) {
echo "No migration has been done before.\n";
return;
}
$migrations=array_keys($migrations);
$migrations = array_keys($migrations);
$n=count($migrations);
echo "Total $n ".($n===1 ? 'migration':'migrations')." to be redone:\n";
foreach($migrations as $migration)
$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)
{
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)
{
foreach (array_reverse($migrations) as $migration) {
if ($this->migrateUp($migration) === false) {
echo "\nMigration failed. All later migrations are canceled.\n";
return;
}
@ -227,38 +223,37 @@ class MigrateController extends Controller
public function actionTo($args)
{
if(isset($args[0]))
$version=$args[0];
else
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
$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));
$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)
$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
} else {
$this->actionDown(array($i));
}
return;
}
}
@ -268,32 +263,30 @@ class MigrateController extends Controller
public function actionMark($args)
{
if(isset($args[0]))
$version=$args[0];
else
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
}
$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->getDb();
$db = $this->getDb();
// 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)
{
$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(),
'version' => $migrations[$j],
'apply_time' => time(),
));
}
echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
@ -303,20 +296,17 @@ class MigrateController extends Controller
}
// try mark down
$migrations=array_keys($this->getMigrationHistory(-1));
foreach($migrations as $i=>$migration)
{
if(strpos($migration,$version.'_')===0)
{
if($i===0)
$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]));
} 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";
}
}
@ -329,125 +319,115 @@ class MigrateController extends Controller
public function actionHistory($args)
{
$limit=isset($args[0]) ? (int)$args[0] : -1;
$migrations=$this->getMigrationHistory($limit);
if($migrations===array())
$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";
} 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())
$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 {
$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";
}
else
echo "Found $n new ".($n===1 ? 'migration' : 'migrations').":\n";
foreach($migrations as $migration)
echo " ".$migration."\n";
foreach ($migrations as $migration) {
echo " " . $migration . "\n";
}
}
}
public function actionCreate($args)
{
if(isset($args[0]))
$name=$args[0];
else
if (isset($args[0])) {
$name = $args[0];
} else {
$this->usageError('Please provide the name of the new migration.');
}
if(!preg_match('/^\w+$/',$name))
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';
$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'?"))
{
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)
if ($class === self::BASE_MIGRATION) {
return;
}
echo "*** applying $class\n";
$start=microtime(true);
$migration=$this->instantiateMigration($class);
if($migration->up()!==false)
{
$start = microtime(true);
$migration = $this->instantiateMigration($class);
if ($migration->up() !== false) {
$this->getDb()->createCommand()->insert($this->migrationTable, array(
'version'=>$class,
'apply_time'=>time(),
'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";
$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)
if ($class === self::BASE_MIGRATION) {
return;
}
echo "*** reverting $class\n";
$start=microtime(true);
$migration=$this->instantiateMigration($class);
if($migration->down()!==false)
{
$db=$this->getDb();
$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";
$start = microtime(true);
$migration = $this->instantiateMigration($class);
if ($migration->down() !== false) {
$db = $this->getDb();
$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';
$file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
require_once($file);
$migration=new $class;
$migration = new $class;
$migration->setDb($this->getDb());
return $migration;
}
@ -456,21 +436,24 @@ class MigrateController extends Controller
* @var CDbConnection
*/
private $_db;
protected function getDb()
{
if($this->_db!==null)
return $this->_db;
else if(($this->_db=\Yii::$application->getComponent($this->connectionID)) instanceof CDbConnection)
if ($this->_db !== null) {
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");
} else {
if (($this->_db = Yii::$application->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->getDb();
if($db->schema->getTable($this->migrationTable)===null)
{
$db = $this->getDb();
if ($db->schema->getTable($this->migrationTable) === null) {
$this->createMigrationHistoryTable();
}
return CHtml::listData($db->createCommand()
@ -483,34 +466,36 @@ class MigrateController extends Controller
protected function createMigrationHistoryTable()
{
$db=$this->getDb();
echo 'Creating migration history table "'.$this->migrationTable.'"...';
$db->createCommand()->createTable($this->migrationTable,array(
'version'=>'string NOT NULL PRIMARY KEY',
'apply_time'=>'integer',
$db = $this->getDb();
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(),
$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==='..')
$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];
}
$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);
@ -519,9 +504,9 @@ class MigrateController extends Controller
protected function getTemplate()
{
if($this->templateFile!==null)
return file_get_contents(Yii::getPathOfAlias($this->templateFile).'.php');
else
if ($this->templateFile !== null) {
return file_get_contents(Yii::getPathOfAlias($this->templateFile) . '.php');
} else {
return <<<EOD
<?php
@ -549,5 +534,6 @@ class {ClassName} extends CDbMigration
*/
}
EOD;
}
}
}

149
framework/console/controllers/ShellController.php

@ -1,149 +0,0 @@
<?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
{
}

29
framework/db/Command.php

@ -485,16 +485,41 @@ class Command extends \yii\base\Component
*
* @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 $params the parameters to be bound to the command
* @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);
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.
* For example,
*

8
framework/db/Exception.php

@ -34,4 +34,12 @@ class Exception extends \yii\base\Exception
$this->errorInfo = $errorInfo;
parent::__construct($message, $code);
}
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii', 'Database Exception');
}
}

27
framework/db/QueryBuilder.php

@ -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.
* For example,
*

35
framework/db/mysql/QueryBuilder.php

@ -129,4 +129,39 @@ class QueryBuilder extends \yii\db\QueryBuilder
{
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) . ')';
}
}

9
framework/logging/DbTarget.php

@ -89,16 +89,17 @@ class DbTarget extends Target
}
/**
* Stores log [[messages]] to DB.
* @param boolean $final whether this method is called at the end of the current application
* Stores log messages to DB.
* @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();
$tableName = $db->quoteTableName($this->tableName);
$sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)";
$command = $db->createCommand($sql);
foreach ($this->messages as $message) {
foreach ($messages as $message) {
$command->bindValues(array(
':level' => $message[1],
':category' => $message[2],

9
framework/logging/EmailTarget.php

@ -39,13 +39,14 @@ class EmailTarget extends Target
public $headers = array();
/**
* Sends log [[messages]] to specified email addresses.
* @param boolean $final whether this method is called at the end of the current application
* Sends log messages to specified email addresses.
* @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 = '';
foreach ($this->messages as $message) {
foreach ($messages as $message) {
$body .= $this->formatMessage($message);
}
$body = wordwrap($body, 70);

25
framework/logging/FileTarget.php

@ -65,19 +65,28 @@ class FileTarget extends Target
}
/**
* Sends log [[messages]] to specified email addresses.
* @param boolean $final whether this method is called at the end of the current application
* Sends log messages to specified email addresses.
* @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) {
$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);
}
/**

33
framework/logging/Logger.php

@ -8,16 +8,13 @@
*/
namespace yii\logging;
use yii\base\Event;
use yii\base\Exception;
use yii\base\InvalidConfigException;
/**
* Logger records logged messages in memory.
*
* When [[flushInterval()]] is reached or when application terminates, it will
* call [[flush()]] to send logged messages to different log targets, such as
* file, email, Web.
* When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]]
* to send logged messages to different log targets, such as file, email, Web.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
@ -25,15 +22,6 @@ use yii\base\Exception;
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
* application and may require developer's handling.
*/
@ -82,7 +70,7 @@ class Logger extends \yii\base\Component
*
* ~~~
* array(
* [0] => message (mixed)
* [0] => message (mixed, can be a string or some complex data, such as an exception object)
* [1] => level (integer)
* [2] => category (string)
* [3] => timestamp (float, obtained by microtime(true))
@ -90,6 +78,10 @@ class Logger extends \yii\base\Component
* ~~~
*/
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.
@ -138,7 +130,9 @@ class Logger extends \yii\base\Component
*/
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();
}
@ -149,7 +143,7 @@ class Logger extends \yii\base\Component
* of [[YiiBase]] class file.
* @return float the total elapsed time in seconds for current request.
*/
public function getExecutionTime()
public function getElapsedTime()
{
return microtime(true) - YII_BEGIN_TIME;
}
@ -218,7 +212,7 @@ class Logger extends \yii\base\Component
if (($last = array_pop($stack)) !== null && $last[0] === $token) {
$timings[] = array($token, $category, $timestamp - $last[3]);
} 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
return $timings;
}
}

21
framework/logging/Router.php

@ -81,26 +81,21 @@ class Router extends Component
$this->targets[$name] = Yii::createObject($target);
}
}
Yii::getLogger()->on(Logger::EVENT_FLUSH, array($this, 'processMessages'));
Yii::getLogger()->on(Logger::EVENT_FINAL_FLUSH, array($this, 'processMessages'));
Yii::getLogger()->router = $this;
}
/**
* Retrieves and processes log messages from the system logger.
* This method mainly serves the event handler to the [[Logger::EVENT_FLUSH]] event
* and the [[Logger::EVENT_FINAL_FLUSH]] event.
* It will retrieve the available log messages from the [[Yii::getLogger()|system logger]]
* and invoke the registered [[targets|log targets]] to do the actual processing.
* @param \yii\base\Event $event event parameter
* Dispatches log messages to [[targets]].
* This method is called by [[Logger]] when its [[Logger::flush()]] method is called.
* It will forward the messages to each log target registered in [[targets]].
* @param array $messages the messages to be processed
* @param boolean $final whether this is the final call during a request cycle
*/
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) {
if ($target->enabled) {
$target->processMessages($messages, $final);
$target->collect($messages, $final);
}
}
}

65
framework/logging/Target.php

@ -50,15 +50,6 @@ abstract class Target extends \yii\base\Component
*/
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.
* @see \yii\web\User
*/
@ -77,19 +68,18 @@ abstract class Target extends \yii\base\Component
public $exportInterval = 1000;
/**
* @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;
/**
* Exports log messages to a specific destination.
* Child classes must implement this method. Note that you may need
* to clean up [[messages]] in this method to avoid re-exporting messages.
* @param boolean $final whether this method is called at the end of the current application
* Child classes must implement this method.
* @param array $messages the messages to be exported. See [[Logger::messages]] for the structure
* of each message.
*/
abstract public function exportMessages($final);
abstract public function export($messages);
/**
* Processes the given log messages.
@ -99,45 +89,16 @@ abstract class Target extends \yii\base\Component
* of each message.
* @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_merge($this->messages, $messages);
$count = count($this->messages);
$this->_messages = array($this->_messages, $this->filterMessages($messages));
$count = count($this->_messages);
if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
$this->prepareExport($final);
$this->exportMessages($final);
$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];
if (($context = $this->getContextMessage()) !== '') {
$this->_messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME);
}
}
if ($final && ($context = $this->getContextMessage()) !== '') {
$this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME);
$this->export($this->_messages);
$this->_messages = array();
}
}
@ -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
* level values. Defaults to 0, meaning all available levels.
* level values. Defaults to 0, meaning all available levels.
*/
public function getLevels()
{

19
framework/util/FileHelper.php

@ -10,6 +10,7 @@
namespace yii\util;
use yii\base\Exception;
use yii\base\InvalidConfigException;
/**
* Filesystem helper
@ -37,7 +38,7 @@ class FileHelper
* 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.
* @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)
{
@ -45,11 +46,25 @@ class FileHelper
if ($p !== false && ($p = realpath($p)) !== false && is_dir($p)) {
return $p;
} 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.
*
* The searching is based on the specified language code. In particular,

103
framework/util/ReflectionHelper.php

@ -1,103 +0,0 @@
<?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;
}
}

10
framework/yiic.php

@ -1,5 +1,4 @@
<?php
define('YII_DEBUG', true);
/**
* Yii console bootstrap file.
*
@ -8,16 +7,17 @@ define('YII_DEBUG', true);
* @license http://www.yiiframework.com/license/
*/
defined('YII_DEBUG') or define('YII_DEBUG', true);
// fcgi doesn't have STDIN defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
require(__DIR__ . '/yii.php');
$config = array(
'controllerPath' => '@yii/console/controllers',
);
$id = 'yiic';
$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();

1
tests/unit/bootstrap.php

@ -1,6 +1,5 @@
<?php
define('YII_ENABLE_EXCEPTION_HANDLER', false);
define('YII_ENABLE_ERROR_HANDLER', false);
define('YII_DEBUG', true);
$_SERVER['SCRIPT_NAME'] = '/' . __DIR__;

4
todo.md

@ -1,6 +1,6 @@
- db
* 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)
* mongodb (put it under framework/db/mongodb)
* key-value-based (should allow storage-specific methods additionally to generic ones)
@ -8,8 +8,10 @@
- logging
* 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
* unit tests
- caching
* a console command to clear cached data
* unit tests
- validators
* FileValidator: depends on CUploadedFile
* CaptchaValidator: depends on CaptchaAction

Loading…
Cancel
Save