Browse Source

...

tags/2.0.0-beta
Qiang Xue 13 years ago
parent
commit
7f35f9ada1
  1. 2
      docs/internals/base.md
  2. 140
      framework/YiiBase.php
  3. 166
      framework/base/Application.php
  4. 4
      framework/base/Behavior.php
  5. 80
      framework/base/Component.php
  6. 52
      framework/base/Dictionary.php
  7. 2
      framework/base/Event.php
  8. 4
      framework/base/Initable.php
  9. 75
      framework/base/Model.php
  10. 99
      framework/base/Module.php
  11. 124
      framework/base/Object.php
  12. 47
      framework/base/Vector.php
  13. 17
      framework/db/dao/ColumnSchema.php
  14. 226
      framework/db/dao/Command.php
  15. 70
      framework/db/dao/Connection.php
  16. 2
      framework/db/dao/DataReader.php
  17. 79
      framework/db/dao/Query.php
  18. 65
      framework/db/dao/QueryBuilder.php
  19. 5
      framework/db/dao/Schema.php
  20. 6
      framework/db/dao/TableSchema.php
  21. 16
      framework/db/dao/Transaction.php
  22. 21
      framework/logging/Logger.php
  23. 4
      framework/logging/Router.php
  24. 2
      framework/validators/Validator.php
  25. 2
      tests/unit/framework/base/BehaviorTest.php
  26. 2
      tests/unit/framework/base/ComponentTest.php
  27. 34
      tests/unit/framework/base/ObjectTest.php

2
docs/internals/base.md

@ -16,7 +16,7 @@ change API. Results in less repetitive code. Performance drop isn't significant.
### callbacks and expressions ### callbacks and expressions
### [[Object::create()]|create] method ### [[Object::newInstance|newInstance]] method
This method is a powerful way to instantiate a class. Differences from `new`: This method is a powerful way to instantiate a class. Differences from `new`:

140
framework/YiiBase.php

@ -49,12 +49,16 @@ class YiiBase
* @var array class map used by the Yii autoloading mechanism. * @var array class map used by the Yii autoloading mechanism.
* The array keys are the class names, and the array values are the corresponding class file paths. * The array keys are the class names, and the array values are the corresponding class file paths.
* This property mainly affects how [[autoload]] works. * This property mainly affects how [[autoload]] works.
* @see import
* @see autoload
*/ */
public static $classMap = array(); public static $classMap = array();
/** /**
* @var array list of directories where Yii will search for new classes to be included. * @var array list of directories where Yii will search for new classes to be included.
* The first directory in the array will be searched first, and so on. * The first directory in the array will be searched first, and so on.
* This property mainly affects how [[autoload]] works. * This property mainly affects how [[autoload]] works.
* @see import
* @see autoload
*/ */
public static $classPath = array(); public static $classPath = array();
/** /**
@ -63,10 +67,34 @@ class YiiBase
public static $app; public static $app;
/** /**
* @var array registered path aliases * @var array registered path aliases
* @see getAlias
* @see setAlias
*/ */
public static $aliases = array( public static $aliases = array(
'@yii' => __DIR__, '@yii' => __DIR__,
); );
/**
* @var array initial property values that will be applied to objects newly created via [[createObject]].
* The array keys are fully qualified namespaced class names, and the array values are the corresponding
* name-value pairs for initializing the created class instances. Make sure the class names do not have
* the leading backslashes. For example,
*
* ~~~
* array(
* 'mycompany\foo\Bar' => array(
* 'prop1' => 'value1',
* 'prop2' => 'value2',
* ),
* 'mycompany\foo\Car' => array(
* 'prop1' => 'value1',
* 'prop2' => 'value2',
* ),
* )
* ~~~
*
* @see createObject
*/
public static $objectConfig = array();
private static $_imported = array(); // alias => class name or directory private static $_imported = array(); // alias => class name or directory
private static $_logger; private static $_logger;
@ -140,13 +168,11 @@ class YiiBase
if ($forceInclude) { if ($forceInclude) {
require($path . "/$className.php"); require($path . "/$className.php");
self::$_imported[$alias] = $className; self::$_imported[$alias] = $className;
} } else {
else {
self::$classMap[$className] = $path . "/$className.php"; self::$classMap[$className] = $path . "/$className.php";
} }
return $className; return $className;
} } else { // a directory
else { // a directory
array_unshift(self::$classPath, $path); array_unshift(self::$classPath, $path);
return self::$_imported[$alias] = $path; return self::$_imported[$alias] = $path;
} }
@ -172,11 +198,9 @@ class YiiBase
{ {
if (isset(self::$aliases[$alias])) { if (isset(self::$aliases[$alias])) {
return self::$aliases[$alias]; return self::$aliases[$alias];
} } elseif ($alias[0] !== '@') { // not an alias
elseif ($alias[0] !== '@') { // not an alias
return $alias; return $alias;
} } elseif (($pos = strpos($alias, '/')) !== false) {
elseif (($pos = strpos($alias, '/')) !== false) {
$rootAlias = substr($alias, 0, $pos); $rootAlias = substr($alias, 0, $pos);
if (isset(self::$aliases[$rootAlias])) { if (isset(self::$aliases[$rootAlias])) {
return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos); return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos);
@ -207,14 +231,11 @@ class YiiBase
{ {
if ($path === null) { if ($path === null) {
unset(self::$aliases[$alias]); unset(self::$aliases[$alias]);
} } elseif ($path[0] !== '@') {
elseif ($path[0] !== '@') {
self::$aliases[$alias] = rtrim($path, '\\/'); self::$aliases[$alias] = rtrim($path, '\\/');
} } elseif (($p = static::getAlias($path)) !== false) {
elseif (($p = static::getAlias($path)) !== false) {
self::$aliases[$alias] = $p; self::$aliases[$alias] = $p;
} } else {
else {
throw new \yii\base\Exception('Invalid path: ' . $path); throw new \yii\base\Exception('Invalid path: ' . $path);
} }
} }
@ -278,22 +299,34 @@ class YiiBase
} }
/** /**
* Creates a new component instance using the given configuration. * Creates a new object using the given configuration.
* *
* The specified configuration can be either a string or an array. * The configuration can be either a string or an array.
* If the former, the string is treated as the object type; if the latter, * If a string, it is treated as the *object type*; if an array,
* the array must contain a `class` element specifying the object type, and * it must contain a `class` element specifying the *object type*, and
* the rest of the name-value pairs in the array will be used to initialize * the rest of the name-value pairs in the array will be used to initialize
* the corresponding object properties. * the corresponding object properties.
* *
* The object type can be either a class name or [[getAlias|path alias]] of * The object type can be either a class name or the [[getAlias|alias]] of
* the class. For example, * the class. For example,
* *
* - `\app\components\GoogleMap`: namespaced class
* - `@app/components/GoogleMap`: an alias
*
* This method does the following steps to create an object:
*
* - create the object using the PHP `new` operator;
* - if [[objectConfig]] contains the configuration for the object class,
* initialize the object properties with that configuration;
* - initialize the object properties using the configuration passed to this method;
* - call the `init` method of the object if it implements the [[yii\base\Initable]] interface.
*
* Below are some usage examples:
*
* ~~~ * ~~~
* $component = Yii::create('@app/components/GoogleMap'); * $object = \Yii::createObject('@app/components/GoogleMap');
* $component = Yii::create('\application\components\GoogleMap'); * $object = \Yii::createObject(array(
* $component = Yii::create(array( * 'class' => '\app\components\GoogleMap',
* 'class' => '@app/components/GoogleMap',
* 'apiKey' => 'xyz', * 'apiKey' => 'xyz',
* )); * ));
* ~~~ * ~~~
@ -301,25 +334,19 @@ class YiiBase
* Any additional parameters passed to this method will be * Any additional parameters passed to this method will be
* passed to the constructor of the object being created. * passed to the constructor of the object being created.
* *
* If a component class implements the [[\yii\base\Initable]] interface,
* its [[\yii\base\Initable::init|init]] method will be invoked AFTER
* the component properties are initialized.
*
* @param mixed $config the configuration. It can be either a string or an array. * @param mixed $config the configuration. It can be either a string or an array.
* @return mixed the created object * @return mixed the created object
* @throws \yii\base\Exception if the configuration is invalid. * @throws \yii\base\Exception if the configuration is invalid.
*/ */
public static function create($config) public static function createObject($config)
{ {
if (is_string($config)) { if (is_string($config)) {
$class = $config; $class = $config;
$config = array(); $config = array();
} } elseif (isset($config['class'])) {
elseif (isset($config['class'])) {
$class = $config['class']; $class = $config['class'];
unset($config['class']); unset($config['class']);
} } else {
else {
throw new \yii\base\Exception('Object configuration must be an array containing a "class" element.'); throw new \yii\base\Exception('Object configuration must be an array containing a "class" element.');
} }
@ -327,36 +354,38 @@ class YiiBase
$class = static::import($class, true); $class = static::import($class, true);
} }
if (($n = func_num_args()) > 1) { if (($n = func_num_args()-1) > 0) {
$args = func_get_args(); $args = func_get_args();
if ($n === 2) { array_shift($args); // remove $config
$object = new $class($args[1]);
}
elseif ($n === 3) {
$object = new $class($args[1], $args[2]);
}
elseif ($n === 4) {
$object = new $class($args[1], $args[2], $args[3]);
}
else {
unset($args[0]);
$r = new ReflectionClass($class);
$object = $r->newInstanceArgs($args);
}
} }
else {
if ($n === 0) {
$object = new $class; $object = new $class;
} elseif ($n === 1) {
$object = new $class($args[0]);
} elseif ($n === 2) {
$object = new $class($args[0], $args[1]);
} elseif ($n === 3) {
$object = new $class($args[0], $args[1], $args[2]);
} else {
$r = new \ReflectionClass($class);
$object = $r->newInstanceArgs($args);
}
$c = get_class($object);
if (isset(\Yii::$objectConfig[$c])) {
$config = isset($config) ? array_merge(\Yii::$objectConfig[$c], $config) : \Yii::$objectConfig[$c];
} }
foreach ($config as $name => $value) { if (!empty($config)) {
$object->$name = $value; foreach ($config as $name => $value) {
$object->$name = $value;
}
} }
if ($object instanceof \yii\base\Initable) { if ($object instanceof \yii\base\Initable) {
$object->init(); $object->init();
} }
return $object;
} }
/** /**
@ -426,7 +455,7 @@ class YiiBase
* @param string $category the category of this log message * @param string $category the category of this log message
* @see endProfile * @see endProfile
*/ */
public static function beginProfile($token, $category) public static function beginProfile($token, $category = 'application')
{ {
self::getLogger()->beginProfile($token, $category); self::getLogger()->beginProfile($token, $category);
} }
@ -438,7 +467,7 @@ class YiiBase
* @param string $category the category of this log message * @param string $category the category of this log message
* @see beginProfile * @see beginProfile
*/ */
public static function endProfile($token, $category) public static function endProfile($token, $category = 'application')
{ {
self::getLogger()->endProfile($token, $category); self::getLogger()->endProfile($token, $category);
} }
@ -451,8 +480,7 @@ class YiiBase
{ {
if (self::$_logger !== null) { if (self::$_logger !== null) {
return self::$_logger; return self::$_logger;
} } else {
else {
return self::$_logger = new \yii\logging\Logger; return self::$_logger = new \yii\logging\Logger;
} }
} }

166
framework/base/Application.php

@ -119,15 +119,17 @@ abstract class Application extends Module
\Yii::$app = $this; \Yii::$app = $this;
// set basePath at early as possible to avoid trouble // set basePath at early as possible to avoid trouble
if (is_string($config)) if (is_string($config)) {
$config = require($config); $config = require($config);
if (isset($config['basePath'])) }
{ if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']); $this->setBasePath($config['basePath']);
unset($config['basePath']); unset($config['basePath']);
} }
else else
{
$this->setBasePath('protected'); $this->setBasePath('protected');
}
\Yii::setAlias('application', $this->getBasePath()); \Yii::setAlias('application', $this->getBasePath());
\Yii::setAlias('webroot', dirname($_SERVER['SCRIPT_FILENAME'])); \Yii::setAlias('webroot', dirname($_SERVER['SCRIPT_FILENAME']));
\Yii::setAlias('ext', $this->getBasePath() . DIRECTORY_SEPARATOR . 'extensions'); \Yii::setAlias('ext', $this->getBasePath() . DIRECTORY_SEPARATOR . 'extensions');
@ -153,11 +155,13 @@ abstract class Application extends Module
*/ */
public function run() public function run()
{ {
if ($this->hasEventHandlers('onBeginRequest')) if ($this->hasEventHandlers('onBeginRequest')) {
$this->onBeginRequest(new CEvent($this)); $this->onBeginRequest(new CEvent($this));
}
$this->processRequest(); $this->processRequest();
if ($this->hasEventHandlers('onEndRequest')) if ($this->hasEventHandlers('onEndRequest')) {
$this->onEndRequest(new CEvent($this)); $this->onEndRequest(new CEvent($this));
}
} }
/** /**
@ -170,10 +174,12 @@ abstract class Application extends Module
*/ */
public function end($status = 0, $exit = true) public function end($status = 0, $exit = true)
{ {
if ($this->hasEventHandlers('onEndRequest')) if ($this->hasEventHandlers('onEndRequest')) {
$this->onEndRequest(new CEvent($this)); $this->onEndRequest(new CEvent($this));
if ($exit) }
if ($exit) {
exit($status); exit($status);
}
} }
/** /**
@ -191,8 +197,7 @@ abstract class Application extends Module
*/ */
public function onEndRequest($event) public function onEndRequest($event)
{ {
if (!$this->_ended) if (!$this->_ended) {
{
$this->_ended = true; $this->_ended = true;
$this->raiseEvent('onEndRequest', $event); $this->raiseEvent('onEndRequest', $event);
} }
@ -204,10 +209,13 @@ abstract class Application extends Module
*/ */
public function getId() public function getId()
{ {
if ($this->_id !== null) if ($this->_id !== null) {
return $this->_id; return $this->_id;
}
else else
{
return $this->_id = sprintf('%x', crc32($this->getBasePath() . $this->name)); return $this->_id = sprintf('%x', crc32($this->getBasePath() . $this->name));
}
} }
/** /**
@ -236,9 +244,10 @@ abstract class Application extends Module
*/ */
public function setBasePath($path) public function setBasePath($path)
{ {
if (($this->_basePath = realpath($path)) === false || !is_dir($this->_basePath)) if (($this->_basePath = realpath($path)) === false || !is_dir($this->_basePath)) {
throw new \yii\base\Exception(\Yii::t('yii', 'Application base path "{path}" is not a valid directory.', throw new \yii\base\Exception(\Yii::t('yii', 'Application base path "{path}" is not a valid directory.',
array('{path}' => $path))); array('{path}' => $path)));
}
} }
/** /**
@ -247,8 +256,9 @@ abstract class Application extends Module
*/ */
public function getRuntimePath() public function getRuntimePath()
{ {
if ($this->_runtimePath !== null) if ($this->_runtimePath !== null) {
return $this->_runtimePath; return $this->_runtimePath;
}
else else
{ {
$this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime'); $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
@ -263,9 +273,10 @@ abstract class Application extends Module
*/ */
public function setRuntimePath($path) public function setRuntimePath($path)
{ {
if (($runtimePath = realpath($path)) === false || !is_dir($runtimePath) || !is_writable($runtimePath)) if (($runtimePath = realpath($path)) === false || !is_dir($runtimePath) || !is_writable($runtimePath)) {
throw new \yii\base\Exception(\Yii::t('yii', 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.', throw new \yii\base\Exception(\Yii::t('yii', 'Application runtime path "{path}" is not valid. Please make sure it is a directory writable by the Web server process.',
array('{path}' => $path))); array('{path}' => $path)));
}
$this->_runtimePath = $runtimePath; $this->_runtimePath = $runtimePath;
} }
@ -284,9 +295,10 @@ abstract class Application extends Module
*/ */
public function setExtensionPath($path) public function setExtensionPath($path)
{ {
if (($extensionPath = realpath($path)) === false || !is_dir($extensionPath)) if (($extensionPath = realpath($path)) === false || !is_dir($extensionPath)) {
throw new \yii\base\Exception(\Yii::t('yii', 'Extension path "{path}" does not exist.', throw new \yii\base\Exception(\Yii::t('yii', 'Extension path "{path}" does not exist.',
array('{path}' => $path))); array('{path}' => $path)));
}
\Yii::setAlias('ext', $extensionPath); \Yii::setAlias('ext', $extensionPath);
} }
@ -359,12 +371,15 @@ abstract class Application extends Module
*/ */
public function findLocalizedFile($srcFile, $srcLanguage = null, $language = null) public function findLocalizedFile($srcFile, $srcLanguage = null, $language = null)
{ {
if ($srcLanguage === null) if ($srcLanguage === null) {
$srcLanguage = $this->sourceLanguage; $srcLanguage = $this->sourceLanguage;
if ($language === null) }
if ($language === null) {
$language = $this->getLanguage(); $language = $this->getLanguage();
if ($language === $srcLanguage) }
if ($language === $srcLanguage) {
return $srcFile; return $srcFile;
}
$desiredFile = dirname($srcFile) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($srcFile); $desiredFile = dirname($srcFile) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($srcFile);
return is_file($desiredFile) ? $desiredFile : $srcFile; return is_file($desiredFile) ? $desiredFile : $srcFile;
} }
@ -528,10 +543,13 @@ abstract class Application extends Module
public function createAbsoluteUrl($route, $params = array(), $schema = '', $ampersand = '&') public function createAbsoluteUrl($route, $params = array(), $schema = '', $ampersand = '&')
{ {
$url = $this->createUrl($route, $params, $ampersand); $url = $this->createUrl($route, $params, $ampersand);
if (strpos($url, 'http') === 0) if (strpos($url, 'http') === 0) {
return $url; return $url;
}
else else
{
return $this->getRequest()->getHostInfo($schema) . $url; return $this->getRequest()->getHostInfo($schema) . $url;
}
} }
/** /**
@ -552,15 +570,19 @@ abstract class Application extends Module
*/ */
public function getHomeUrl() public function getHomeUrl()
{ {
if ($this->_homeUrl === null) if ($this->_homeUrl === null) {
{ if ($this->getUrlManager()->showScriptName) {
if ($this->getUrlManager()->showScriptName)
return $this->getRequest()->getScriptUrl(); return $this->getRequest()->getScriptUrl();
}
else else
{
return $this->getRequest()->getBaseUrl() . '/'; return $this->getRequest()->getBaseUrl() . '/';
}
} }
else else
{
return $this->_homeUrl; return $this->_homeUrl;
}
} }
/** /**
@ -582,12 +604,16 @@ abstract class Application extends Module
*/ */
public function getGlobalState($key, $defaultValue = null) public function getGlobalState($key, $defaultValue = null)
{ {
if ($this->_globalState === null) if ($this->_globalState === null) {
$this->loadGlobalState(); $this->loadGlobalState();
if (isset($this->_globalState[$key])) }
if (isset($this->_globalState[$key])) {
return $this->_globalState[$key]; return $this->_globalState[$key];
}
else else
{
return $defaultValue; return $defaultValue;
}
} }
/** /**
@ -602,14 +628,13 @@ abstract class Application extends Module
*/ */
public function setGlobalState($key, $value, $defaultValue = null) public function setGlobalState($key, $value, $defaultValue = null)
{ {
if ($this->_globalState === null) if ($this->_globalState === null) {
$this->loadGlobalState(); $this->loadGlobalState();
}
$changed = $this->_stateChanged; $changed = $this->_stateChanged;
if ($value === $defaultValue) if ($value === $defaultValue) {
{ if (isset($this->_globalState[$key])) {
if (isset($this->_globalState[$key]))
{
unset($this->_globalState[$key]); unset($this->_globalState[$key]);
$this->_stateChanged = true; $this->_stateChanged = true;
} }
@ -620,8 +645,9 @@ abstract class Application extends Module
$this->_stateChanged = true; $this->_stateChanged = true;
} }
if ($this->_stateChanged !== $changed) if ($this->_stateChanged !== $changed) {
$this->attachEventHandler('onEndRequest', array($this, 'saveGlobalState')); $this->attachEventHandler('onEndRequest', array($this, 'saveGlobalState'));
}
} }
/** /**
@ -643,8 +669,9 @@ abstract class Application extends Module
public function loadGlobalState() public function loadGlobalState()
{ {
$persister = $this->getStatePersister(); $persister = $this->getStatePersister();
if (($this->_globalState = $persister->load()) === null) if (($this->_globalState = $persister->load()) === null) {
$this->_globalState = array(); $this->_globalState = array();
}
$this->_stateChanged = false; $this->_stateChanged = false;
$this->detachEventHandler('onEndRequest', array($this, 'saveGlobalState')); $this->detachEventHandler('onEndRequest', array($this, 'saveGlobalState'));
} }
@ -656,8 +683,7 @@ abstract class Application extends Module
*/ */
public function saveGlobalState() public function saveGlobalState()
{ {
if ($this->_stateChanged) if ($this->_stateChanged) {
{
$this->_stateChanged = false; $this->_stateChanged = false;
$this->detachEventHandler('onEndRequest', array($this, 'saveGlobalState')); $this->detachEventHandler('onEndRequest', array($this, 'saveGlobalState'));
$this->getStatePersister()->save($this->_globalState); $this->getStatePersister()->save($this->_globalState);
@ -685,12 +711,14 @@ abstract class Application extends Module
restore_exception_handler(); restore_exception_handler();
$category = 'exception.' . get_class($exception); $category = 'exception.' . get_class($exception);
if ($exception instanceof \yii\web\HttpException) if ($exception instanceof \yii\web\HttpException) {
$category .= '.' . $exception->statusCode; $category .= '.' . $exception->statusCode;
}
// php <5.2 doesn't support string conversion auto-magically // php <5.2 doesn't support string conversion auto-magically
$message = $exception->__toString(); $message = $exception->__toString();
if (isset($_SERVER['REQUEST_URI'])) if (isset($_SERVER['REQUEST_URI'])) {
$message .= ' REQUEST_URI=' . $_SERVER['REQUEST_URI']; $message .= ' REQUEST_URI=' . $_SERVER['REQUEST_URI'];
}
\Yii::error($message, $category); \Yii::error($message, $category);
try try
@ -699,11 +727,9 @@ abstract class Application extends Module
//$event = new CExceptionEvent($this, $exception); //$event = new CExceptionEvent($this, $exception);
$event = new Event($this, array('exception' => $exception)); $event = new Event($this, array('exception' => $exception));
$this->onException($event); $this->onException($event);
if (!$event->handled) if (!$event->handled) {
{
// try an error handler // try an error handler
if (($handler = $this->getErrorHandler()) !== null) if (($handler = $this->getErrorHandler()) !== null) {
{
$handler->handle($event); $handler->handle($event);
} }
else else
@ -712,7 +738,7 @@ abstract class Application extends Module
} }
} }
} }
catch(Exception $e) catch (Exception $e)
{ {
$this->displayException($e); $this->displayException($e);
} }
@ -721,7 +747,7 @@ abstract class Application extends Module
{ {
$this->end(1); $this->end(1);
} }
catch(Exception $e) catch (Exception $e)
{ {
// use the most primitive way to log error // use the most primitive way to log error
$msg = get_class($e) . ': ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; $msg = get_class($e) . ': ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
@ -754,8 +780,7 @@ abstract class Application extends Module
*/ */
public function handleError($code, $message, $file, $line) public function handleError($code, $message, $file, $line)
{ {
if ($code & error_reporting()) if ($code & error_reporting()) {
{
// disable error capturing to avoid recursive errors // disable error capturing to avoid recursive errors
restore_error_handler(); restore_error_handler();
restore_exception_handler(); restore_exception_handler();
@ -763,23 +788,29 @@ abstract class Application extends Module
$log = "$message ($file:$line)\nStack trace:\n"; $log = "$message ($file:$line)\nStack trace:\n";
$trace = debug_backtrace(); $trace = debug_backtrace();
// skip the first 3 stacks as they do not tell the error position // skip the first 3 stacks as they do not tell the error position
if (count($trace) > 3) if (count($trace) > 3) {
$trace = array_slice($trace, 3); $trace = array_slice($trace, 3);
}
foreach ($trace as $i => $t) foreach ($trace as $i => $t)
{ {
if (!isset($t['file'])) if (!isset($t['file'])) {
$t['file'] = 'unknown'; $t['file'] = 'unknown';
if (!isset($t['line'])) }
if (!isset($t['line'])) {
$t['line'] = 0; $t['line'] = 0;
if (!isset($t['function'])) }
if (!isset($t['function'])) {
$t['function'] = 'unknown'; $t['function'] = 'unknown';
}
$log .= "#$i {$t['file']}( {$t['line']}): "; $log .= "#$i {$t['file']}( {$t['line']}): ";
if (isset($t['object']) && is_object($t['object'])) if (isset($t['object']) && is_object($t['object'])) {
$log .= get_class($t['object']) . '->'; $log .= get_class($t['object']) . '->';
}
$log .= " {$t['function']}()\n"; $log .= " {$t['function']}()\n";
} }
if (isset($_SERVER['REQUEST_URI'])) if (isset($_SERVER['REQUEST_URI'])) {
$log .= 'REQUEST_URI=' . $_SERVER['REQUEST_URI']; $log .= 'REQUEST_URI=' . $_SERVER['REQUEST_URI'];
}
\Yii::error($log, 'php'); \Yii::error($log, 'php');
try try
@ -787,16 +818,18 @@ abstract class Application extends Module
\Yii::import('CErrorEvent', true); \Yii::import('CErrorEvent', true);
$event = new CErrorEvent($this, $code, $message, $file, $line); $event = new CErrorEvent($this, $code, $message, $file, $line);
$this->onError($event); $this->onError($event);
if (!$event->handled) if (!$event->handled) {
{
// try an error handler // try an error handler
if (($handler = $this->getErrorHandler()) !== null) if (($handler = $this->getErrorHandler()) !== null) {
$handler->handle($event); $handler->handle($event);
}
else else
{
$this->displayError($code, $message, $file, $line); $this->displayError($code, $message, $file, $line);
}
} }
} }
catch(Exception $e) catch (Exception $e)
{ {
$this->displayException($e); $this->displayException($e);
} }
@ -805,7 +838,7 @@ abstract class Application extends Module
{ {
$this->end(1); $this->end(1);
} }
catch(Exception $e) catch (Exception $e)
{ {
// use the most primitive way to log error // use the most primitive way to log error
$msg = get_class($e) . ': ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; $msg = get_class($e) . ': ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
@ -860,27 +893,31 @@ abstract class Application extends Module
*/ */
public function displayError($code, $message, $file, $line) public function displayError($code, $message, $file, $line)
{ {
if (YII_DEBUG) if (YII_DEBUG) {
{
echo "<h1>PHP Error [$code]</h1>\n"; echo "<h1>PHP Error [$code]</h1>\n";
echo "<p>$message ($file:$line)</p>\n"; echo "<p>$message ($file:$line)</p>\n";
echo '<pre>'; echo '<pre>';
$trace = debug_backtrace(); $trace = debug_backtrace();
// skip the first 3 stacks as they do not tell the error position // skip the first 3 stacks as they do not tell the error position
if (count($trace) > 3) if (count($trace) > 3) {
$trace = array_slice($trace, 3); $trace = array_slice($trace, 3);
}
foreach ($trace as $i => $t) foreach ($trace as $i => $t)
{ {
if (!isset($t['file'])) if (!isset($t['file'])) {
$t['file'] = 'unknown'; $t['file'] = 'unknown';
if (!isset($t['line'])) }
if (!isset($t['line'])) {
$t['line'] = 0; $t['line'] = 0;
if (!isset($t['function'])) }
if (!isset($t['function'])) {
$t['function'] = 'unknown'; $t['function'] = 'unknown';
}
echo "#$i {$t['file']}( {$t['line']}): "; echo "#$i {$t['file']}( {$t['line']}): ";
if (isset($t['object']) && is_object($t['object'])) if (isset($t['object']) && is_object($t['object'])) {
echo get_class($t['object']) . '->'; echo get_class($t['object']) . '->';
}
echo " {$t['function']}()\n"; echo " {$t['function']}()\n";
} }
@ -901,8 +938,7 @@ abstract class Application extends Module
*/ */
public function displayException($exception) public function displayException($exception)
{ {
if (YII_DEBUG) if (YII_DEBUG) {
{
echo '<h1>' . get_class($exception) . "</h1>\n"; echo '<h1>' . get_class($exception) . "</h1>\n";
echo '<p>' . $exception->getMessage() . ' (' . $exception->getFile() . ':' . $exception->getLine() . ')</p>'; echo '<p>' . $exception->getMessage() . ' (' . $exception->getFile() . ':' . $exception->getLine() . ')</p>';
echo '<pre>' . $exception->getTraceAsString() . '</pre>'; echo '<pre>' . $exception->getTraceAsString() . '</pre>';
@ -919,10 +955,12 @@ abstract class Application extends Module
*/ */
protected function initSystemHandlers() protected function initSystemHandlers()
{ {
if (YII_ENABLE_EXCEPTION_HANDLER) if (YII_ENABLE_EXCEPTION_HANDLER) {
set_exception_handler(array($this, 'handleException')); set_exception_handler(array($this, 'handleException'));
if (YII_ENABLE_ERROR_HANDLER) }
if (YII_ENABLE_ERROR_HANDLER) {
set_error_handler(array($this, 'handleError'), error_reporting()); set_error_handler(array($this, 'handleError'), error_reporting());
}
} }
/** /**

4
framework/base/Behavior.php

@ -47,8 +47,8 @@ class Behavior extends Object
* *
* ~~~ * ~~~
* array( * array(
* 'onBeforeValidate' => 'myBeforeValidate', * 'onBeforeValidate' => 'myBeforeValidate',
* 'onAfterValidate' => 'myAfterValidate', * 'onAfterValidate' => 'myAfterValidate',
* ) * )
* ~~~ * ~~~
* *

80
framework/base/Component.php

@ -22,7 +22,7 @@ namespace yii\base;
* ~~~ * ~~~
* public function onClick($event) * public function onClick($event)
* { * {
* $this->raiseEvent('onClick', $event); * $this->raiseEvent('onClick', $event);
* } * }
* ~~~ * ~~~
* *
@ -54,8 +54,8 @@ namespace yii\base;
* *
* ~~~ * ~~~
* $component->onClick->insertAt(0, $callback); // attach a handler as the first one * $component->onClick->insertAt(0, $callback); // attach a handler as the first one
* $component->onClick[] = $callback; // attach a handler as the last one * $component->onClick[] = $callback; // attach a handler as the last one
* unset($component->onClick[0]); // detach the first handler * unset($component->onClick[0]); // detach the first handler
* ~~~ * ~~~
* *
* *
@ -98,15 +98,18 @@ class Component extends Object
$getter = 'get' . $name; $getter = 'get' . $name;
if (method_exists($this, $getter)) { // read property, e.g. getName() if (method_exists($this, $getter)) { // read property, e.g. getName()
return $this->$getter(); return $this->$getter();
} elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event, e.g. onClick() }
elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event, e.g. onClick()
$name = strtolower($name); $name = strtolower($name);
if (!isset($this->_e[$name])) { if (!isset($this->_e[$name])) {
$this->_e[$name] = new Vector; $this->_e[$name] = new Vector;
} }
return $this->_e[$name]; return $this->_e[$name];
} elseif (isset($this->_b[$name])) { // behavior }
elseif (isset($this->_b[$name])) { // behavior
return $this->_b[$name]; return $this->_b[$name];
} elseif (is_array($this->_b)) { // a behavior property }
elseif (is_array($this->_b)) { // a behavior property
foreach ($this->_b as $object) { foreach ($this->_b as $object) {
if ($object->canGetProperty($name)) { if ($object->canGetProperty($name)) {
return $object->$name; return $object->$name;
@ -135,15 +138,17 @@ class Component extends Object
public function __set($name, $value) public function __set($name, $value)
{ {
$setter = 'set' . $name; $setter = 'set' . $name;
if (method_exists($this, $setter)) { // write property if (method_exists($this, $setter)) { // write property
return $this->$setter($value); return $this->$setter($value);
} elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event }
elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event
$name = strtolower($name); $name = strtolower($name);
if (!isset($this->_e[$name])) { if (!isset($this->_e[$name])) {
$this->_e[$name] = new Vector; $this->_e[$name] = new Vector;
} }
return $this->_e[$name]->add($value); return $this->_e[$name]->add($value);
} elseif (is_array($this->_b)) { // behavior }
elseif (is_array($this->_b)) { // behavior
foreach ($this->_b as $object) { foreach ($this->_b as $object) {
if ($object->canSetProperty($name)) { if ($object->canSetProperty($name)) {
return $object->$name = $value; return $object->$name = $value;
@ -152,7 +157,8 @@ class Component extends Object
} }
if (method_exists($this, 'get' . $name)) { if (method_exists($this, 'get' . $name)) {
throw new Exception('Setting read-only property: ' . get_class($this) . '.' . $name); throw new Exception('Setting read-only property: ' . get_class($this) . '.' . $name);
} else { }
else {
throw new Exception('Setting unknown property: ' . get_class($this) . '.' . $name); throw new Exception('Setting unknown property: ' . get_class($this) . '.' . $name);
} }
} }
@ -175,12 +181,15 @@ class Component extends Object
$getter = 'get' . $name; $getter = 'get' . $name;
if (method_exists($this, $getter)) { // property is not null if (method_exists($this, $getter)) { // property is not null
return $this->$getter() !== null; return $this->$getter() !== null;
} elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // has event handler }
elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // has event handler
$name = strtolower($name); $name = strtolower($name);
return isset($this->_e[$name]) && $this->_e[$name]->getCount(); return isset($this->_e[$name]) && $this->_e[$name]->getCount();
} elseif (isset($this->_b[$name])) { // has behavior }
return true; elseif (isset($this->_b[$name])) { // has behavior
} elseif (is_array($this->_b)) { return true;
}
elseif (is_array($this->_b)) {
foreach ($this->_b as $object) { foreach ($this->_b as $object) {
if ($object->canGetProperty($name)) { if ($object->canGetProperty($name)) {
return $object->$name !== null; return $object->$name !== null;
@ -206,14 +215,17 @@ class Component extends Object
public function __unset($name) public function __unset($name)
{ {
$setter = 'set' . $name; $setter = 'set' . $name;
if (method_exists($this, $setter)) { // write property if (method_exists($this, $setter)) { // write property
return $this->$setter(null); return $this->$setter(null);
} elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event }
elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event
unset($this->_e[strtolower($name)]); unset($this->_e[strtolower($name)]);
return; return;
} elseif (isset($this->_b[$name])) { // behavior }
elseif (isset($this->_b[$name])) { // behavior
return $this->detachBehavior($name); return $this->detachBehavior($name);
} elseif (is_array($this->_b)) { // behavior property }
elseif (is_array($this->_b)) { // behavior property
foreach ($this->_b as $object) { foreach ($this->_b as $object) {
if ($object->canSetProperty($name)) { if ($object->canSetProperty($name)) {
return $object->$name = null; return $object->$name = null;
@ -266,7 +278,7 @@ class Component extends Object
*/ */
public function hasEvent($name) public function hasEvent($name)
{ {
return method_exists($this, $name) && strncasecmp($name, 'on', 2)===0; return method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0;
} }
/** /**
@ -318,10 +330,10 @@ class Component extends Object
* some examples: * some examples:
* *
* ~~~ * ~~~
* 'handleOnClick' // handleOnClick() is a global function * 'handleOnClick' // handleOnClick() is a global function
* array($object, 'handleOnClick') // $object->handleOnClick() * array($object, 'handleOnClick') // $object->handleOnClick()
* array('Page', 'handleOnClick') // Page::handleOnClick() * array('Page', 'handleOnClick') // Page::handleOnClick()
* function($event) { ... } // anonymous function * function($event) { ... } // anonymous function
* ~~~ * ~~~
* *
* An event handler must be defined with the following signature, * An event handler must be defined with the following signature,
@ -374,17 +386,21 @@ class Component extends Object
foreach ($this->_e[$name] as $handler) { foreach ($this->_e[$name] as $handler) {
if (is_string($handler) || $handler instanceof \Closure) { if (is_string($handler) || $handler instanceof \Closure) {
call_user_func($handler, $event); call_user_func($handler, $event);
} elseif (is_callable($handler, true)) { }
elseif (is_callable($handler, true)) {
// an array: 0 - object, 1 - method name // an array: 0 - object, 1 - method name
list($object, $method) = $handler; list($object, $method) = $handler;
if (is_string($object)) { // static method call if (is_string($object)) { // static method call
call_user_func($handler, $event); call_user_func($handler, $event);
} elseif (method_exists($object, $method)) { }
elseif (method_exists($object, $method)) {
$object->$method($event); $object->$method($event);
} else { }
else {
throw new Exception('Event "' . get_class($this) . '.' . $name . '" is attached with an invalid handler.'); throw new Exception('Event "' . get_class($this) . '.' . $name . '" is attached with an invalid handler.');
} }
} else { }
else {
throw new Exception('Event "' . get_class($this) . '.' . $name . '" is attached with an invalid handler.'); throw new Exception('Event "' . get_class($this) . '.' . $name . '" is attached with an invalid handler.');
} }
@ -393,7 +409,8 @@ class Component extends Object
return; return;
} }
} }
} elseif (!$this->hasEvent($name)) { }
elseif (!$this->hasEvent($name)) {
throw new Exception('Raising unknown event: ' . get_class($this) . '.' . $name); throw new Exception('Raising unknown event: ' . get_class($this) . '.' . $name);
} }
} }
@ -419,16 +436,15 @@ class Component extends Object
* *
* - a [[Behavior]] object * - a [[Behavior]] object
* - a string specifying the behavior class * - a string specifying the behavior class
* - an object configuration array * - an object configuration array that will be passed to [[\Yii::createObject]] to create the behavior object.
* *
* parameter to [[\Yii::create]] to create the behavior object.
* @return Behavior the behavior object * @return Behavior the behavior object
* @see detachBehavior * @see detachBehavior
*/ */
public function attachBehavior($name, $behavior) public function attachBehavior($name, $behavior)
{ {
if (!($behavior instanceof Behavior)) { if (!($behavior instanceof Behavior)) {
$behavior = \Yii::create($behavior); $behavior = \Yii::createObject($behavior);
} }
$behavior->attach($this); $behavior->attach($this);
return $this->_b[$name] = $behavior; return $this->_b[$name] = $behavior;

52
framework/base/Dictionary.php

@ -21,11 +21,11 @@ namespace yii\base;
* like a regular PHP array as follows, * like a regular PHP array as follows,
* *
* ~~~ * ~~~
* $dictionary[$key] = $value; // add a key-value pair * $dictionary[$key] = $value; // add a key-value pair
* unset($dictionary[$key]); // remove the value with the specified key * unset($dictionary[$key]); // remove the value with the specified key
* if (isset($dictionary[$key])) // if the dictionary contains the key * if (isset($dictionary[$key])) // if the dictionary contains the key
* foreach ($dictionary as $key=>$value) // traverse the items in the dictionary * foreach ($dictionary as $key=>$value) // traverse the items in the dictionary
* $n = count($dictionary); // returns the number of items in the dictionary * $n = count($dictionary); // returns the number of items in the dictionary
* ~~~ * ~~~
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
@ -114,7 +114,8 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
{ {
if ($key === null) { if ($key === null) {
$this->_d[] = $value; $this->_d[] = $value;
} else { }
else {
$this->_d[$key] = $value; $this->_d[$key] = $value;
} }
} }
@ -131,7 +132,8 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
$value = $this->_d[$key]; $value = $this->_d[$key];
unset($this->_d[$key]); unset($this->_d[$key]);
return $value; return $value;
} else { // the value is null }
else { // the value is null
unset($this->_d[$key]); unset($this->_d[$key]);
return null; return null;
} }
@ -149,7 +151,8 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
foreach (array_keys($this->_d) as $key) { foreach (array_keys($this->_d) as $key) {
$this->remove($key); $this->remove($key);
} }
} else { }
else {
$this->_d = array(); $this->_d = array();
} }
} }
@ -181,8 +184,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
*/ */
public function copyFrom($data) public function copyFrom($data)
{ {
if (is_array($data) || $data instanceof \Traversable) if (is_array($data) || $data instanceof \Traversable) {
{
if ($this->_d !== array()) { if ($this->_d !== array()) {
$this->clear(); $this->clear();
} }
@ -192,7 +194,8 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$this->add($key, $value); $this->add($key, $value);
} }
} else { }
else {
throw new Exception('Data must be either an array or an object implementing Traversable.'); throw new Exception('Data must be either an array or an object implementing Traversable.');
} }
} }
@ -214,7 +217,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
* *
* @throws CException If data is neither an array nor an iterator. * @throws CException If data is neither an array nor an iterator.
*/ */
public function mergeWith($data, $recursive=true) public function mergeWith($data, $recursive = true)
{ {
if (is_array($data) || $data instanceof \Traversable) { if (is_array($data) || $data instanceof \Traversable) {
if ($data instanceof self) { if ($data instanceof self) {
@ -222,20 +225,23 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
} }
if ($recursive) { if ($recursive) {
if ($data instanceof \Traversable) { if ($data instanceof \Traversable) {
$d=array(); $d = array();
foreach($data as $key => $value) { foreach ($data as $key => $value) {
$d[$key] = $value; $d[$key] = $value;
} }
$this->_d = self::mergeArray($this->_d, $d); $this->_d = self::mergeArray($this->_d, $d);
} else { }
else {
$this->_d = self::mergeArray($this->_d, $data); $this->_d = self::mergeArray($this->_d, $data);
} }
} else { }
foreach($data as $key => $value) { else {
foreach ($data as $key => $value) {
$this->add($key, $value); $this->add($key, $value);
} }
} }
} else { }
else {
throw new Exception('Dictionary data must be an array or an object implementing Traversable.'); throw new Exception('Dictionary data must be an array or an object implementing Traversable.');
} }
} }
@ -278,7 +284,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
*/ */
public function offsetSet($offset, $item) public function offsetSet($offset, $item)
{ {
$this->add($offset,$item); $this->add($offset, $item);
} }
/** /**
@ -308,12 +314,14 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
*/ */
public static function mergeArray($a, $b) public static function mergeArray($a, $b)
{ {
foreach($b as $k=>$v) { foreach ($b as $k => $v) {
if(is_integer($k)) { if (is_integer($k)) {
isset($a[$k]) ? $a[] = $v : $a[$k] = $v; isset($a[$k]) ? $a[] = $v : $a[$k] = $v;
} elseif(is_array($v) && isset($a[$k]) && is_array($a[$k])) { }
elseif (is_array($v) && isset($a[$k]) && is_array($a[$k])) {
$a[$k] = self::mergeArray($a[$k], $v); $a[$k] = self::mergeArray($a[$k], $v);
} else { }
else {
$a[$k] = $v; $a[$k] = $v;
} }
} }

2
framework/base/Event.php

@ -51,7 +51,7 @@ class Event extends Object
* @param mixed $sender sender of the event * @param mixed $sender sender of the event
* @param mixed $params parameters of the event * @param mixed $params parameters of the event
*/ */
public function __construct($sender=null, $params=null) public function __construct($sender = null, $params = null)
{ {
$this->sender = $sender; $this->sender = $sender;
$this->params = $params; $this->params = $params;

4
framework/base/Initable.php

@ -13,7 +13,7 @@ namespace yii\base;
* Initable is an interface indicating a class needs initialization to work properly. * Initable is an interface indicating a class needs initialization to work properly.
* *
* Initable requires a class to implement the [[init]] method. * Initable requires a class to implement the [[init]] method.
* When [[\Yii::create]] is being used to create a new component which implements * When [[\Yii::createObject]] is being used to create a new component which implements
* Initable, it will call the [[init]] method after setting the initial values of the * Initable, it will call the [[init]] method after setting the initial values of the
* component properties. * component properties.
* *
@ -24,7 +24,7 @@ interface Initable
{ {
/** /**
* Initializes this component. * Initializes this component.
* This method is invoked by [[\Yii::create]] after its creates the new * This method is invoked by [[\Yii::createObject]] after its creates the new
* component instance and initializes the component properties. In other words, * component instance and initializes the component properties. In other words,
* at this stage, the component has been fully configured. * at this stage, the component has been fully configured.
*/ */

75
framework/base/Model.php

@ -35,15 +35,15 @@ namespace yii\base;
class Model extends Component implements Initable, \IteratorAggregate, \ArrayAccess class Model extends Component implements Initable, \IteratorAggregate, \ArrayAccess
{ {
private static $_attributes = array(); // class name => array of attribute names private static $_attributes = array(); // class name => array of attribute names
private $_errors; // attribute name => array of errors private $_errors; // attribute name => array of errors
private $_validators; // validators private $_validators; // validators
private $_scenario; // scenario private $_scenario; // scenario
/** /**
* Constructor. * Constructor.
* @param string $scenario name of the [[scenario]] that this model is used in. * @param string $scenario name of the [[scenario]] that this model is used in.
*/ */
public function __construct($scenario='') public function __construct($scenario = '')
{ {
$this->_scenario = $scenario; $this->_scenario = $scenario;
$this->afterConstruct(); $this->afterConstruct();
@ -52,7 +52,7 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
/** /**
* Initializes this model. * Initializes this model.
* *
* This method is required by the [[Initable]] interface. It is invoked by [[\Yii::create]] * This method is required by the [[Initable]] interface. It is invoked by [[\Yii::createObject]]
* after it creates the new model instance and initializes the model properties. * after it creates the new model instance and initializes the model properties.
* *
* The default implementation calls [[behaviors]] and registers any available behaviors. * The default implementation calls [[behaviors]] and registers any available behaviors.
@ -72,9 +72,9 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* *
* ~~~ * ~~~
* 'behaviorName' => array( * 'behaviorName' => array(
* 'class' => 'BehaviorClass', * 'class' => 'BehaviorClass',
* 'property1' => 'value1', * 'property1' => 'value1',
* 'property2' => 'value2', * 'property2' => 'value2',
* ) * )
* ~~~ * ~~~
* *
@ -123,10 +123,10 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* *
* ~~~ * ~~~
* array( * array(
* 'attribute list', * 'attribute list',
* 'validator type', * 'validator type',
* 'on'=>'scenario name', * 'on'=>'scenario name',
* ...other parameters... * ...other parameters...
* ) * )
* ~~~ * ~~~
* *
@ -134,11 +134,11 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* *
* - attribute list: required, specifies the attributes (separated by commas) to be validated; * - attribute list: required, specifies the attributes (separated by commas) to be validated;
* - validator type: required, specifies the validator to be used. It can be the name of a model * - validator type: required, specifies the validator to be used. It can be the name of a model
* class method, the name of a built-in validator, or a validator class (or its path alias). * class method, the name of a built-in validator, or a validator class (or its path alias).
* - on: optional, specifies the [[scenario|scenarios]] (separated by commas) when the validation * - on: optional, specifies the [[scenario|scenarios]] (separated by commas) when the validation
* rule can be applied. If this option is not set, the rule will apply to any scenario. * rule can be applied. If this option is not set, the rule will apply to any scenario.
* - additional name-value pairs can be specified to initialize the corresponding validator properties. * - additional name-value pairs can be specified to initialize the corresponding validator properties.
* Please refer to individual validator class API for possible properties. * Please refer to individual validator class API for possible properties.
* *
* A validator can be either a model class method or an object. * A validator can be either a model class method or an object.
* If the former, the method must have the following signature: * If the former, the method must have the following signature:
@ -156,10 +156,10 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* *
* ~~~ * ~~~
* array( * array(
* array('username', 'required'), * array('username', 'required'),
* array('username', 'length', 'min'=>3, 'max'=>12), * array('username', 'length', 'min'=>3, 'max'=>12),
* array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'), * array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
* array('password', 'authenticate', 'on'=>'login'), * array('password', 'authenticate', 'on'=>'login'),
* ); * );
* ~~~ * ~~~
* *
@ -364,10 +364,11 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
{ {
$validators = new Vector; $validators = new Vector;
foreach ($this->rules() as $rule) { foreach ($this->rules() as $rule) {
if (isset($rule[0], $rule[1])) { // attributes, validator type if (isset($rule[0], $rule[1])) { // attributes, validator type
$validator = \yii\validators\Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2)); $validator = \yii\validators\Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2));
$validators->add($validator); $validators->add($validator);
} else { }
else {
throw new Exception('Invalid validation rule: a rule must specify both attribute names and validator type.'); throw new Exception('Invalid validation rule: a rule must specify both attribute names and validator type.');
} }
} }
@ -439,13 +440,13 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* *
* ~~~ * ~~~
* array( * array(
* 'username' => array( * 'username' => array(
* 'Username is required.', * 'Username is required.',
* 'Username must contain only word characters.', * 'Username must contain only word characters.',
* ), * ),
* 'email' => array( * 'email' => array(
* 'Email address is invalid.', * 'Email address is invalid.',
* ) * )
* ) * )
* ~~~ * ~~~
* *
@ -455,7 +456,8 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
{ {
if ($attribute === null) { if ($attribute === null) {
return $this->_errors === null ? array() : $this->_errors; return $this->_errors === null ? array() : $this->_errors;
} else { }
else {
return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array(); return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array();
} }
} }
@ -494,7 +496,8 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
foreach ($error as $e) { foreach ($error as $e) {
$this->_errors[$attribute][] = $e; $this->_errors[$attribute][] = $e;
} }
} else { }
else {
$this->_errors[$attribute][] = $error; $this->_errors[$attribute][] = $error;
} }
} }
@ -508,7 +511,8 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
{ {
if ($attribute === null) { if ($attribute === null) {
$this->_errors = array(); $this->_errors = array();
} else { }
else {
unset($this->_errors[$attribute]); unset($this->_errors[$attribute]);
} }
} }
@ -543,7 +547,8 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
$values[$name] = $this->$name; $values[$name] = $this->$name;
} }
} }
} else { }
else {
foreach ($this->attributeNames() as $name) { foreach ($this->attributeNames() as $name) {
$values[$name] = $this->$name; $values[$name] = $this->$name;
} }
@ -567,7 +572,8 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
if (isset($attributes[$name])) { if (isset($attributes[$name])) {
$this->$name = $value; $this->$name = $value;
} elseif ($safeOnly) { }
elseif ($safeOnly) {
$this->onUnsafeAttribute($name, $value); $this->onUnsafeAttribute($name, $value);
} }
} }
@ -633,7 +639,8 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
foreach ($validator->attributes as $name) { foreach ($validator->attributes as $name) {
$unsafe[] = $name; $unsafe[] = $name;
} }
} else { }
else {
foreach ($validator->attributes as $name) { foreach ($validator->attributes as $name) {
$attributes[$name] = true; $attributes[$name] = true;
} }

99
framework/base/Module.php

@ -59,10 +59,10 @@ abstract class Module extends Component
$this->_parentModule = $parent; $this->_parentModule = $parent;
// set basePath at early as possible to avoid trouble // set basePath at early as possible to avoid trouble
if (is_string($config)) if (is_string($config)) {
$config = require($config); $config = require($config);
if (isset($config['basePath'])) }
{ if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']); $this->setBasePath($config['basePath']);
unset($config['basePath']); unset($config['basePath']);
} }
@ -139,7 +139,7 @@ abstract class Module extends Component
*/ */
public function setId($id) public function setId($id)
{ {
$this->_id=$id; $this->_id = $id;
} }
/** /**
@ -216,8 +216,8 @@ abstract class Module extends Component
* *
* ~~~ * ~~~
* array( * array(
* '@models' => '@app/models', // an existing alias * '@models' => '@app/models', // an existing alias
* '@backend' => __DIR__ . '/../backend', // a directory * '@backend' => __DIR__ . '/../backend', // a directory
* ) * )
* ~~~ * ~~~
*/ */
@ -246,23 +246,22 @@ abstract class Module extends Component
*/ */
public function getModule($id) public function getModule($id)
{ {
if (isset($this->_modules[$id]) || array_key_exists($id, $this->_modules)) if (isset($this->_modules[$id]) || array_key_exists($id, $this->_modules)) {
return $this->_modules[$id]; return $this->_modules[$id];
}
elseif (isset($this->_moduleConfig[$id])) elseif (isset($this->_moduleConfig[$id]))
{ {
$config = $this->_moduleConfig[$id]; $config = $this->_moduleConfig[$id];
if (!isset($config['enabled']) || $config['enabled']) if (!isset($config['enabled']) || $config['enabled']) {
{
\Yii::trace("Loading \"$id\" module", 'system.base.CModule'); \Yii::trace("Loading \"$id\" module", 'system.base.CModule');
$class = $config['class']; $class = $config['class'];
unset($config['class'], $config['enabled']); unset($config['class'], $config['enabled']);
if ($this === \Yii::$app) if ($this === \Yii::$app) {
{ $module = \Yii::createObject($class, $id, null, $config);
$module = Yii::create($class, $id, null, $config);
} }
else else
{ {
$module = Yii::create($class, $this->getId() . '/' . $id, $this, $config); $module = \Yii::createObject($class, $this->getId() . '/' . $id, $this, $config);
} }
return $this->_modules[$id] = $module; return $this->_modules[$id] = $module;
} }
@ -299,10 +298,10 @@ abstract class Module extends Component
* For example, the following array declares two modules: * For example, the following array declares two modules:
* <pre> * <pre>
* array( * array(
* 'admin', // a single module ID * 'admin', // a single module ID
* 'payment'=>array( // ID-configuration pair * 'payment'=>array( // ID-configuration pair
* 'server'=>'paymentserver.com', * 'server'=>'paymentserver.com',
* ), * ),
* ) * )
* </pre> * </pre>
* *
@ -318,21 +317,22 @@ abstract class Module extends Component
{ {
foreach ($modules as $id => $module) foreach ($modules as $id => $module)
{ {
if (is_int($id)) if (is_int($id)) {
{
$id = $module; $id = $module;
$module = array(); $module = array();
} }
if (!isset($module['class'])) if (!isset($module['class'])) {
{
Yii::setPathOfAlias($id, $this->getModulePath() . DIRECTORY_SEPARATOR . $id); Yii::setPathOfAlias($id, $this->getModulePath() . DIRECTORY_SEPARATOR . $id);
$module['class'] = $id . '.' . ucfirst($id) . 'Module'; $module['class'] = $id . '.' . ucfirst($id) . 'Module';
} }
if (isset($this->_moduleConfig[$id])) if (isset($this->_moduleConfig[$id])) {
$this->_moduleConfig[$id] = CMap::mergeArray($this->_moduleConfig[$id], $module); $this->_moduleConfig[$id] = CMap::mergeArray($this->_moduleConfig[$id], $module);
}
else else
{
$this->_moduleConfig[$id] = $module; $this->_moduleConfig[$id] = $module;
}
} }
} }
@ -356,16 +356,16 @@ abstract class Module extends Component
*/ */
public function getComponent($id, $createIfNull = true) public function getComponent($id, $createIfNull = true)
{ {
if (isset($this->_components[$id])) if (isset($this->_components[$id])) {
return $this->_components[$id]; return $this->_components[$id];
}
elseif (isset($this->_componentConfig[$id]) && $createIfNull) elseif (isset($this->_componentConfig[$id]) && $createIfNull)
{ {
$config = $this->_componentConfig[$id]; $config = $this->_componentConfig[$id];
if (!isset($config['enabled']) || $config['enabled']) if (!isset($config['enabled']) || $config['enabled']) {
{
\Yii::trace("Loading \"$id\" application component", 'system.CModule'); \Yii::trace("Loading \"$id\" application component", 'system.CModule');
unset($config['enabled']); unset($config['enabled']);
$component = \Yii::create($config); $component = \Yii::createObject($config);
return $this->_components[$id] = $component; return $this->_components[$id] = $component;
} }
} }
@ -381,13 +381,14 @@ abstract class Module extends Component
*/ */
public function setComponent($id, $component) public function setComponent($id, $component)
{ {
if ($component === null) if ($component === null) {
unset($this->_components[$id]); unset($this->_components[$id]);
else }
{ else {
$this->_components[$id] = $component; $this->_components[$id] = $component;
if (!$component->getIsInitialized()) if (!$component->getIsInitialized()) {
$component->init(); $component->init();
}
} }
} }
@ -401,10 +402,12 @@ abstract class Module extends Component
*/ */
public function getComponents($loadedOnly = true) public function getComponents($loadedOnly = true)
{ {
if ($loadedOnly) if ($loadedOnly) {
return $this->_components; return $this->_components;
else }
else {
return array_merge($this->_componentConfig, $this->_components); return array_merge($this->_componentConfig, $this->_components);
}
} }
/** /**
@ -421,15 +424,15 @@ abstract class Module extends Component
* The following is the configuration for two components: * The following is the configuration for two components:
* <pre> * <pre>
* array( * array(
* 'db'=>array( * 'db'=>array(
* 'class'=>'CDbConnection', * 'class'=>'CDbConnection',
* 'connectionString'=>'sqlite:path/to/file.db', * 'connectionString'=>'sqlite:path/to/file.db',
* ), * ),
* 'cache'=>array( * 'cache'=>array(
* 'class'=>'CDbCache', * 'class'=>'CDbCache',
* 'connectionID'=>'db', * 'connectionID'=>'db',
* 'enabled'=>!YII_DEBUG, // enable caching in non-debug mode * 'enabled'=>!YII_DEBUG, // enable caching in non-debug mode
* ), * ),
* ) * )
* </pre> * </pre>
* *
@ -442,12 +445,17 @@ abstract class Module extends Component
{ {
foreach ($components as $id => $component) foreach ($components as $id => $component)
{ {
if ($component instanceof IApplicationComponent) if ($component instanceof IApplicationComponent) {
$this->setComponent($id, $component); $this->setComponent($id, $component);
}
elseif (isset($this->_componentConfig[$id]) && $merge) elseif (isset($this->_componentConfig[$id]) && $merge)
{
$this->_componentConfig[$id] = CMap::mergeArray($this->_componentConfig[$id], $component); $this->_componentConfig[$id] = CMap::mergeArray($this->_componentConfig[$id], $component);
}
else else
{
$this->_componentConfig[$id] = $component; $this->_componentConfig[$id] = $component;
}
} }
} }
@ -457,10 +465,11 @@ abstract class Module extends Component
*/ */
public function configure($config) public function configure($config)
{ {
if (is_array($config)) if (is_array($config)) {
{
foreach ($config as $key => $value) foreach ($config as $key => $value)
{
$this->$key = $value; $this->$key = $value;
}
} }
} }
@ -470,7 +479,9 @@ abstract class Module extends Component
public function preloadComponents() public function preloadComponents()
{ {
foreach ($this->preload as $id) foreach ($this->preload as $id)
{
$this->getComponent($id); $this->getComponent($id);
}
} }
/** /**

124
framework/base/Object.php

@ -21,12 +21,12 @@ namespace yii\base;
* *
* public function getLabel() * public function getLabel()
* { * {
* return $this->_label; * return $this->_label;
* } * }
* *
* public function setLabel($value) * public function setLabel($value)
* { * {
* $this->_label = $value; * $this->_label = $value;
* } * }
* ~~~ * ~~~
* *
@ -85,7 +85,8 @@ class Object
$getter = 'get' . $name; $getter = 'get' . $name;
if (method_exists($this, $getter)) { if (method_exists($this, $getter)) {
return $this->$getter(); return $this->$getter();
} else { }
else {
throw new Exception('Getting unknown property: ' . get_class($this) . '.' . $name); throw new Exception('Getting unknown property: ' . get_class($this) . '.' . $name);
} }
} }
@ -105,9 +106,11 @@ class Object
$setter = 'set' . $name; $setter = 'set' . $name;
if (method_exists($this, $setter)) { if (method_exists($this, $setter)) {
$this->$setter($value); $this->$setter($value);
} elseif (method_exists($this, 'get' . $name)) { }
elseif (method_exists($this, 'get' . $name)) {
throw new Exception('Setting read-only property: ' . get_class($this) . '.' . $name); throw new Exception('Setting read-only property: ' . get_class($this) . '.' . $name);
} else { }
else {
throw new Exception('Setting unknown property: ' . get_class($this) . '.' . $name); throw new Exception('Setting unknown property: ' . get_class($this) . '.' . $name);
} }
} }
@ -127,7 +130,8 @@ class Object
$getter = 'get' . $name; $getter = 'get' . $name;
if (method_exists($this, $getter)) { // property is not null if (method_exists($this, $getter)) { // property is not null
return $this->$getter() !== null; return $this->$getter() !== null;
} else { }
else {
return false; return false;
} }
} }
@ -146,9 +150,10 @@ class Object
public function __unset($name) public function __unset($name)
{ {
$setter = 'set' . $name; $setter = 'set' . $name;
if (method_exists($this, $setter)) { // write property if (method_exists($this, $setter)) { // write property
$this->$setter(null); $this->$setter(null);
} elseif (method_exists($this, 'get' . $name)) { }
elseif (method_exists($this, 'get' . $name)) {
throw new Exception('Unsetting read-only property: ' . get_class($this) . '.' . $name); throw new Exception('Unsetting read-only property: ' . get_class($this) . '.' . $name);
} }
} }
@ -244,74 +249,107 @@ class Object
* @param array $_data_ additional parameters to be passed to the above expression/callback. * @param array $_data_ additional parameters to be passed to the above expression/callback.
* @return mixed the expression result * @return mixed the expression result
*/ */
public function evaluateExpression($_expression_, $_data_=array()) public function evaluateExpression($_expression_, $_data_ = array())
{ {
if (is_string($_expression_)) { if (is_string($_expression_)) {
extract($_data_); extract($_data_);
return eval('return ' . $_expression_ . ';'); return eval('return ' . $_expression_ . ';');
} else { }
else {
$_data_[] = $this; $_data_[] = $this;
return call_user_func_array($_expression_, $_data_); return call_user_func_array($_expression_, $_data_);
} }
} }
/** /**
* Creates a new object instance. * Creates a new instance of the calling class.
*
* This method calls [[\Yii::create]] to create the new object instance.
*
* This method differs from the PHP `new` operator in that it does the following
* steps to create a new object instance:
*
* - Call class constructor (same as the `new` operator);
* - Initialize the object properties using the name-value pairs given as the
* last parameter to this method;
* - Call [[Initable::init|init]] if the class implements [[Initable]].
* *
* Parameters passed to this method will be used as the parameters to the object * Parameters passed to this method will be used as the parameters to the object
* constructor. * constructor.
* *
* Additionally, one can pass in an associative array as the last parameter to * This method does the following steps to create a object:
* this method. This method will treat the array as name-value pairs that initialize *
* the corresponding object properties. For example, * - create the object using the PHP `new` operator;
* - if [[Yii::objectConfig]] contains the configuration for the object class,
* initialize the object properties with that configuration;
* - if the number of the given parameters is more than the number of the parameters
* listed in the object constructor and the last given parameter is an array,
* initialize the object properties using that array;
* - call the `init` method of the object if it implements the [[yii\base\Initable]] interface.
*
* For example,
* *
* ~~~ * ~~~
* class Foo extends \yii\base\Object * class Foo extends \yii\base\Object implements \yii\base\Initable
* { * {
* public $c; * public $c;
* public function __construct($a, $b) * public function __construct($a, $b)
* { * {
* ... * ...
* } * }
* public function init()
* {
* ...
* }
* } * }
* *
* $model = Foo::create(1, 2, array('c' => 3)); * $model = Foo::newInstance(1, 2, array('c' => 3));
* // which is equivalent to the following lines: * // which is equivalent to the following lines:
* $model = new Foo(1, 2); * $model = new Foo(1, 2);
* $model->c = 3; * $model->c = 3;
* $model->init();
* ~~~ * ~~~
* *
* @return object the created object * @return object the created object
* @throws Exception if the configuration is invalid. * @throws Exception if the configuration is invalid.
*/ */
public static function create() public static function newInstance()
{ {
$class = '\\' . get_called_class(); $c = get_called_class();
$class = '\\' . $c;
if (($n = func_num_args()) > 0) { if (($n = func_num_args()) > 0) {
$args = func_get_args(); $args = func_get_args();
if (is_array($args[$n-1])) { if (is_array($args[$n - 1])) {
// the last parameter could be configuration array
$method = new \ReflectionMethod($class, '__construct'); $method = new \ReflectionMethod($class, '__construct');
if ($method->getNumberOfParameters()+1 == $n) { if ($method->getNumberOfParameters() < $n) {
$config = $args[$n-1]; // the last EXTRA parameter is a configuration array
array_pop($args); $config = $args[--$n];
unset($args[$n]);
} }
} }
$config['class'] = $class;
array_unshift($args, $config);
return call_user_func_array('\Yii::create', $args);
} else {
return \Yii::create($class);
} }
if ($n === 0) {
$object = new $class;
}
elseif ($n === 1) {
$object = new $class($args[0]);
}
elseif ($n === 2) {
$object = new $class($args[0], $args[1]);
}
elseif ($n === 3) {
$object = new $class($args[0], $args[1], $args[2]);
}
else {
$r = new \ReflectionClass($class);
$object = $r->newInstanceArgs($args);
}
if (isset(\Yii::$objectConfig[$c])) {
$config = isset($config) ? array_merge(\Yii::$objectConfig[$c], $config) : \Yii::$objectConfig[$c];
}
if (!empty($config)) {
foreach ($config as $name => $value) {
$object->$name = $value;
}
}
if ($object instanceof \yii\base\Initable) {
$object->init();
}
return $object;
} }
} }

47
framework/base/Vector.php

@ -22,12 +22,12 @@ namespace yii\base;
* like a regular PHP array as follows, * like a regular PHP array as follows,
* *
* ~~~ * ~~~
* $vector[] = $item; // append new item at the end * $vector[] = $item; // append new item at the end
* $vector[$index] = $item; // set new item at $index * $vector[$index] = $item; // set new item at $index
* unset($vector[$index]); // remove the item at $index * unset($vector[$index]); // remove the item at $index
* if (isset($vector[$index])) // if the vector has an item at $index * if (isset($vector[$index])) // if the vector has an item at $index
* foreach ($vector as $index=>$item) // traverse each item in the vector * foreach ($vector as $index=>$item) // traverse each item in the vector
* $n = count($vector); // count the number of items * $n = count($vector); // count the number of items
* ~~~ * ~~~
* *
* Note that if you plan to extend Vector by performing additional operations * Note that if you plan to extend Vector by performing additional operations
@ -103,9 +103,11 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
{ {
if (isset($this->_d[$index])) { if (isset($this->_d[$index])) {
return $this->_d[$index]; return $this->_d[$index];
} elseif ($index >= 0 && $index < $this->_c) { // in case the value is null }
elseif ($index >= 0 && $index < $this->_c) { // in case the value is null
return $this->_d[$index]; return $this->_d[$index];
} else { }
else {
throw new Exception('Index out of range: ' . $index); throw new Exception('Index out of range: ' . $index);
} }
} }
@ -119,7 +121,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
public function add($item) public function add($item)
{ {
$this->insertAt($this->_c, $item); $this->insertAt($this->_c, $item);
return $this->_c-1; return $this->_c - 1;
} }
/** /**
@ -134,10 +136,12 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
{ {
if ($index === $this->_c) { if ($index === $this->_c) {
$this->_d[$this->_c++] = $item; $this->_d[$this->_c++] = $item;
} elseif ($index >= 0 && $index < $this->_c) { }
elseif ($index >= 0 && $index < $this->_c) {
array_splice($this->_d, $index, 0, array($item)); array_splice($this->_d, $index, 0, array($item));
$this->_c++; $this->_c++;
} else { }
else {
throw new Exception('Index out of range: ' . $index); throw new Exception('Index out of range: ' . $index);
} }
} }
@ -156,7 +160,8 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
if (($index = $this->indexOf($item)) >= 0) { if (($index = $this->indexOf($item)) >= 0) {
$this->removeAt($index); $this->removeAt($index);
return $index; return $index;
} else { }
else {
return false; return false;
} }
} }
@ -173,12 +178,14 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
$this->_c--; $this->_c--;
if ($index === $this->_c) { if ($index === $this->_c) {
return array_pop($this->_d); return array_pop($this->_d);
} else { }
else {
$item = $this->_d[$index]; $item = $this->_d[$index];
array_splice($this->_d, $index, 1); array_splice($this->_d, $index, 1);
return $item; return $item;
} }
} else { }
else {
throw new Exception('Index out of range: ' . $index); throw new Exception('Index out of range: ' . $index);
} }
} }
@ -192,10 +199,11 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
public function clear($safeClear = false) public function clear($safeClear = false)
{ {
if ($safeClear) { if ($safeClear) {
for ($i = $this->_c-1;$i >= 0;--$i) { for ($i = $this->_c - 1; $i >= 0; --$i) {
$this->removeAt($i); $this->removeAt($i);
} }
} else { }
else {
$this->_d = array(); $this->_d = array();
$this->_c = 0; $this->_c = 0;
} }
@ -252,7 +260,8 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
foreach ($data as $item) { foreach ($data as $item) {
$this->add($item); $this->add($item);
} }
} else { }
else {
throw new Exception('Data must be either an array or an object implementing Traversable.'); throw new Exception('Data must be either an array or an object implementing Traversable.');
} }
} }
@ -272,7 +281,8 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
foreach ($data as $item) { foreach ($data as $item) {
$this->add($item); $this->add($item);
} }
} else { }
else {
throw new Exception('Data must be either an array or an object implementing Traversable.'); throw new Exception('Data must be either an array or an object implementing Traversable.');
} }
} }
@ -318,7 +328,8 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
{ {
if ($offset === null || $offset === $this->_c) { if ($offset === null || $offset === $this->_c) {
$this->insertAt($this->_c, $item); $this->insertAt($this->_c, $item);
} else { }
else {
$this->removeAt($offset); $this->removeAt($offset);
$this->insertAt($offset, $item); $this->insertAt($offset, $item);
} }

17
framework/db/dao/ColumnSchema.php

@ -86,10 +86,10 @@ class ColumnSchema extends \yii\base\Component
{ {
static $typeMap = array( // logical type => php type static $typeMap = array( // logical type => php type
'smallint' => 'integer', 'smallint' => 'integer',
'integer' => 'integer', 'integer' => 'integer',
'bigint' => 'integer', 'bigint' => 'integer',
'boolean' => 'boolean', 'boolean' => 'boolean',
'float' => 'double', 'float' => 'double',
); );
if (isset($typeMap[$this->type])) { if (isset($typeMap[$this->type])) {
if ($this->type === 'bigint') { if ($this->type === 'bigint') {
@ -114,9 +114,12 @@ class ColumnSchema extends \yii\base\Component
return $value; return $value;
} }
switch ($this->phpType) { switch ($this->phpType) {
case 'string': return (string)$value; case 'string':
case 'integer': return (integer)$value; return (string)$value;
case 'boolean': return (boolean)$value; case 'integer':
return (integer)$value;
case 'boolean':
return (boolean)$value;
} }
return $value; return $value;
} }

226
framework/db/dao/Command.php

@ -38,10 +38,10 @@ use yii\db\Exception;
* *
* ~~~ * ~~~
* $user = \Yii::app()->db->createCommand() * $user = \Yii::app()->db->createCommand()
* ->select('username, password') * ->select('username, password')
* ->from('tbl_user') * ->from('tbl_user')
* ->where('id=:id', array(':id'=>1)) * ->where('id=:id', array(':id'=>1))
* ->queryRow(); * ->queryRow();
* ~~~ * ~~~
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
@ -93,11 +93,13 @@ class Command extends \yii\base\Component
$this->connection = $connection; $this->connection = $connection;
if (is_object($query)) { if (is_object($query)) {
$this->query = $query; $this->query = $query;
} else { }
else {
$this->query = new Query; $this->query = new Query;
if (is_array($query)) { if (is_array($query)) {
$this->query->fromArray($query); $this->query->fromArray($query);
} else { }
else {
$this->_sql = $query; $this->_sql = $query;
} }
} }
@ -161,9 +163,10 @@ class Command extends \yii\base\Component
$sql = $this->getSql(); $sql = $this->getSql();
try { try {
$this->pdoStatement = $this->connection->pdo->prepare($sql); $this->pdoStatement = $this->connection->pdo->prepare($sql);
} catch(\Exception $e) { }
catch (\Exception $e) {
\Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__); \Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($e->getMessage(), (int)$e->getCode(), $errorInfo); throw new Exception($e->getMessage(), (int)$e->getCode(), $errorInfo);
} }
} }
@ -197,11 +200,14 @@ class Command extends \yii\base\Component
$this->prepare(); $this->prepare();
if ($dataType === null) { if ($dataType === null) {
$this->pdoStatement->bindParam($name, $value, $this->connection->getPdoType(gettype($value))); $this->pdoStatement->bindParam($name, $value, $this->connection->getPdoType(gettype($value)));
} elseif ($length === null) { }
elseif ($length === null) {
$this->pdoStatement->bindParam($name, $value, $dataType); $this->pdoStatement->bindParam($name, $value, $dataType);
} elseif ($driverOptions === null) { }
elseif ($driverOptions === null) {
$this->pdoStatement->bindParam($name, $value, $dataType, $length); $this->pdoStatement->bindParam($name, $value, $dataType, $length);
} else { }
else {
$this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions); $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
} }
$this->_params[$name] =& $value; $this->_params[$name] =& $value;
@ -224,7 +230,8 @@ class Command extends \yii\base\Component
$this->prepare(); $this->prepare();
if ($dataType === null) { if ($dataType === null) {
$this->pdoStatement->bindValue($name, $value, $this->connection->getPdoType(gettype($value))); $this->pdoStatement->bindValue($name, $value, $this->connection->getPdoType(gettype($value)));
} else { }
else {
$this->pdoStatement->bindValue($name, $value, $dataType); $this->pdoStatement->bindValue($name, $value, $dataType);
} }
$this->_params[$name] = $value; $this->_params[$name] = $value;
@ -248,7 +255,7 @@ class Command extends \yii\base\Component
/** /**
* Executes the SQL statement. * Executes the SQL statement.
* This method is meant only for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
* No result set will be returned. * No result set will be returned.
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]] * to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]]
@ -263,8 +270,9 @@ class Command extends \yii\base\Component
$this->_params = array_merge($this->_params, $params); $this->_params = array_merge($this->_params, $params);
if ($this->_params === array()) { if ($this->_params === array()) {
$paramLog = ''; $paramLog = '';
} else { }
$paramLog = "Parameters: " . var_export($this->_params, true); else {
$paramLog = "\nParameters: " . var_export($this->_params, true);
} }
\Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__); \Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__);
@ -277,7 +285,8 @@ class Command extends \yii\base\Component
$this->prepare(); $this->prepare();
if ($params === array()) { if ($params === array()) {
$this->pdoStatement->execute(); $this->pdoStatement->execute();
} else { }
else {
$this->pdoStatement->execute($params); $this->pdoStatement->execute($params);
} }
$n = $this->pdoStatement->rowCount(); $n = $this->pdoStatement->rowCount();
@ -286,7 +295,8 @@ class Command extends \yii\base\Component
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
} }
return $n; return $n;
} catch (Exception $e) { }
catch (Exception $e) {
if ($this->connection->enableProfiling) { if ($this->connection->enableProfiling) {
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
} }
@ -312,13 +322,13 @@ class Command extends \yii\base\Component
} }
/** /**
* Executes the SQL statement and returns all rows. * Executes the SQL statement and returns ALL rows at once.
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]] * to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]]
* or [[bindValue]] will be ignored. * or [[bindValue]] will be ignored.
* @param boolean $fetchAssociative whether each row should be returned as an associated array with * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
* column names as the keys or the array keys are column indexes (0-based). * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
* @return array all rows of the query result. Each array element is an array representing a row. * @return array all rows of the query result. Each array element is an array representing a row of data.
* An empty array is returned if the query results in nothing. * An empty array is returned if the query results in nothing.
* @throws Exception execution failed * @throws Exception execution failed
*/ */
@ -329,13 +339,14 @@ class Command extends \yii\base\Component
/** /**
* Executes the SQL statement and returns the first row of the result. * Executes the SQL statement and returns the first row of the result.
* This is a convenient method of {@link query} when only the first row of data is needed. * This method is best used when only the first row of result is needed for a query.
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]] * to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]]
* or [[bindValue]] will be ignored. * or [[bindValue]] will be ignored.
* @param boolean $fetchAssociative whether the row should be returned as an associated array with * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
* column names as the keys or the array keys are column indexes (0-based). * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
* @return mixed the first row (in terms of an array) of the query result, false if no result. * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
* results in nothing.
* @throws Exception execution failed * @throws Exception execution failed
*/ */
public function queryRow($params = array(), $fetchMode = null) public function queryRow($params = array(), $fetchMode = null)
@ -345,12 +356,12 @@ class Command extends \yii\base\Component
/** /**
* Executes the SQL statement and returns the value of the first column in the first row of data. * Executes the SQL statement and returns the value of the first column in the first row of data.
* This is a convenient method of {@link query} when only a single scalar * This method is best used when only a single value is needed for a query.
* value is needed (e.g. obtaining the count of the records).
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]] * to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]]
* or [[bindValue]] will be ignored. * or [[bindValue]] will be ignored.
* @return mixed the value of the first column in the first row of the query result. False is returned if there is no value. * @return mixed the value of the first column in the first row of the query result.
* False is returned if there is no value.
* @throws Exception execution failed * @throws Exception execution failed
*/ */
public function queryScalar($params = array()) public function queryScalar($params = array())
@ -366,12 +377,12 @@ class Command extends \yii\base\Component
/** /**
* Executes the SQL statement and returns the first column of the result. * Executes the SQL statement and returns the first column of the result.
* This is a convenient method of {@link query} when only the first column of data is needed. * This method is best used when only the first column of result (i.e. the first element in each row)
* Note, the column returned will contain the first element in each row of result. * is needed for a query.
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]] * to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]]
* or [[bindValue]] will be ignored. * or [[bindValue]] will be ignored.
* @return array the first column of the query result. Empty array if no result. * @return array the first column of the query result. Empty array is returned if the query results in nothing.
* @throws Exception execution failed * @throws Exception execution failed
*/ */
public function queryColumn($params = array()) public function queryColumn($params = array())
@ -380,11 +391,13 @@ class Command extends \yii\base\Component
} }
/** /**
* Performs the actual DB query of a SQL statement.
* @param string $method method of PDOStatement to be called * @param string $method method of PDOStatement to be called
* @param mixed $mode parameters to be passed to the method
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]] * to [[bindValues]]. Note that if you pass parameters in this way, any previous call to [[bindParam]]
* or [[bindValue]] will be ignored. * or [[bindValue]] will be ignored.
* @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
* for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
* @return mixed the method execution result * @return mixed the method execution result
*/ */
private function queryInternal($method, $params, $fetchMode = null) private function queryInternal($method, $params, $fetchMode = null)
@ -395,21 +408,22 @@ class Command extends \yii\base\Component
$this->_params = array_merge($this->_params, $params); $this->_params = array_merge($this->_params, $params);
if ($this->_params === array()) { if ($this->_params === array()) {
$paramLog = ''; $paramLog = '';
} else { }
$paramLog = "Parameters: " . var_export($this->_params, true); else {
$paramLog = "\nParameters: " . var_export($this->_params, true);
} }
\Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__); \Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
$cachingEnabled = $db->queryCachingCount > 0 && $method !== '' if ($db->queryCachingCount > 0 && $db->queryCachingDuration >= 0 && $method !== '') {
&& $db->queryCachingDuration >= 0 $cache = \Yii::app()->getComponent($db->queryCacheID);
&& ($cache = \Yii::app()->getComponent($db->queryCacheID)) !== null; }
if ($cachingEnabled) {
if (isset($cache)) {
$db->queryCachingCount--; $db->queryCachingCount--;
$cacheKey = 'yii:dbquery' . $db->connectionString . ':' . $db->username; $cacheKey = __CLASS__ . "/{$db->dsn}/{$db->username}/$sql/$paramLog";
$cacheKey .= ':' . $sql . ':' . $paramLog;
if (($result = $cache->get($cacheKey)) !== false) { if (($result = $cache->get($cacheKey)) !== false) {
\Yii::trace('Query result found in cache', 'system.db.Command'); \Yii::trace('Query result found in cache', __CLASS__);
return $result; return $result;
} }
} }
@ -422,13 +436,15 @@ class Command extends \yii\base\Component
$this->prepare(); $this->prepare();
if ($params === array()) { if ($params === array()) {
$this->pdoStatement->execute(); $this->pdoStatement->execute();
} else { }
else {
$this->pdoStatement->execute($params); $this->pdoStatement->execute($params);
} }
if ($method === '') { if ($method === '') {
$result = new DataReader($this); $result = new DataReader($this);
} else { }
else {
if ($fetchMode === null) { if ($fetchMode === null) {
$fetchMode = $this->fetchMode; $fetchMode = $this->fetchMode;
} }
@ -440,58 +456,46 @@ class Command extends \yii\base\Component
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
} }
if ($cachingEnabled) { if (isset($cache)) {
$cache->set($cacheKey, $result, $db->queryCachingDuration, $db->queryCachingDependency); $cache->set($cacheKey, $result, $db->queryCachingDuration, $db->queryCachingDependency);
\Yii::trace('Saved query result in cache', __CLASS__);
} }
return $result; return $result;
} catch (Exception $e) { }
catch (Exception $e) {
if ($db->enableProfiling) { if ($db->enableProfiling) {
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__); \Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
} }
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
$message = $e->getMessage(); $message = $e->getMessage();
\Yii::log(\Yii::t('yii', 'Command::{method}() failed: {error}. The SQL statement executed was: {sql}.', \Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__);
array('{method}' => $method, '{error}' => $message, '{sql}' => $this->getSql() . $par)), CLogger::LEVEL_ERROR, 'system.db.Command'); $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
if (YII_DEBUG) { throw new Exception($message, (int)$e->getCode(), $errorInfo);
$message .= '. The SQL statement executed was: ' . $this->getSql() . $par;
}
throw new Exception(\Yii::t('yii', 'Command failed to execute the SQL statement: {error}',
array('{error}' => $message)), (int)$e->getCode(), $errorInfo);
} }
} }
/** /**
/**
* Sets the SELECT part of the query. * Sets the SELECT part of the query.
* @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns. * @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')). * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id"). * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
* The method will automatically quote the column names unless a column contains some parenthesis * The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression). * (which means the column contains a DB expression).
* @param boolean $distinct whether to use 'SELECT DISTINCT'.
* @param string $option additional option that should be appended to the 'SELECT' keyword. For example, * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
* in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. This parameter is supported since version 1.1.8. * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
* @return Command the command object itself * @return Command the command object itself
*/ */
public function select($columns = '*', $option = '') public function select($columns = '*', $distinct = false, $option = '')
{ {
$this->query->select = $columns; $this->query->select = $columns;
$this->query->distinct = $distinct;
$this->query->selectOption = $option; $this->query->selectOption = $option;
return $this; return $this;
} }
/** /**
* Sets the SELECT part of the query with the DISTINCT flag turned on.
* This is the same as {@link select} except that the DISTINCT flag is turned on.
* @param mixed $columns the columns to be selected. See {@link select} for more details.
* @return Command the command object itself
*/
public function selectDistinct($columns = '*', $option = '')
{
$this->query->distinct = true;
return $this->select($columns, $option);
}
/**
* Sets the FROM part of the query. * Sets the FROM part of the query.
* @param mixed $tables the table(s) to be selected from. This can be either a string (e.g. 'tbl_user') * @param mixed $tables the table(s) to be selected from. This can be either a string (e.g. 'tbl_user')
* or an array (e.g. array('tbl_user', 'tbl_profile')) specifying one or several table names. * or an array (e.g. array('tbl_user', 'tbl_profile')) specifying one or several table names.
@ -513,32 +517,42 @@ class Command extends \yii\base\Component
* specifying the values to be bound to the query. * specifying the values to be bound to the query.
* *
* The $conditions parameter should be either a string (e.g. 'id=1') or an array. * The $conditions parameter should be either a string (e.g. 'id=1') or an array.
* If the latter, it must be of the format <code>array(operator, operand1, operand2, ...)</code>, * If the latter, it must be in the format `array(operator, operand1, operand2, ...)`,
* where the operator can be one of the followings, and the possible operands depend on the corresponding * where the operator can be one of the followings, and the possible operands depend on the corresponding
* operator: * operator:
* <ul> *
* <li><code>and</code>: the operands should be concatenated together using AND. For example, * - `and`: the operands should be concatenated together using `AND`. For example,
* array('and', 'id=1', 'id=2') will generate 'id=1 AND id=2'. If an operand is an array, * `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array,
* it will be converted into a string using the same rules described here. For example, * it will be converted into a string using the rules described here. For example,
* array('and', 'type=1', array('or', 'id=1', 'id=2')) will generate 'type=1 AND (id=1 OR id=2)'. * `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`.
* The method will NOT do any quoting or escaping.</li> * The method will NOT do any quoting or escaping.
* <li><code>or</code>: similar as the <code>and</code> operator except that the operands are concatenated using OR.</li> *
* <li><code>in</code>: operand 1 should be a column or DB expression, and operand 2 be an array representing * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
*
* - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
* the range of the values that the column or DB expression should be in. For example, * the range of the values that the column or DB expression should be in. For example,
* array('in', 'id', array(1,2,3)) will generate 'id IN (1,2,3)'. * `array('in', 'id', array(1,2,3))` will generate `id IN (1,2,3)`.
* The method will properly quote the column name and escape values in the range.</li> * The method will properly quote the column name and escape values in the range.
* <li><code>not in</code>: similar as the <code>in</code> operator except that IN is replaced with NOT IN in the generated condition.</li> *
* <li><code>like</code>: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
*
* - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
* the values that the column or DB expression should be like. * the values that the column or DB expression should be like.
* For example, array('like', 'name', '%tester%') will generate "name LIKE '%tester%'". * For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`.
* When the value range is given as an array, multiple LIKE predicates will be generated and concatenated using AND. * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
* For example, array('like', 'name', array('%test%', '%sample%')) will generate * using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate
* "name LIKE '%test%' AND name LIKE '%sample%'". * `name LIKE '%test%' AND name LIKE '%sample%'`.
* The method will properly quote the column name and escape values in the range.</li> * The method will properly quote the column name and escape values in the range.
* <li><code>not like</code>: similar as the <code>like</code> operator except that LIKE is replaced with NOT LIKE in the generated condition.</li> *
* <li><code>or like</code>: similar as the <code>like</code> operator except that OR is used to concatenated the LIKE predicates.</li> * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* <li><code>or not like</code>: similar as the <code>not like</code> operator except that OR is used to concatenated the NOT LIKE predicates.</li> * predicates when operand 2 is an array.
* </ul> *
* - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
* in the generated condition.
*
* - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
* the `NOT LIKE` predicates.
*
* @param mixed $conditions the conditions that should be put in the WHERE part. * @param mixed $conditions the conditions that should be put in the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query * @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself * @return Command the command object itself
@ -557,7 +571,7 @@ class Command extends \yii\base\Component
* The method will automatically quote the table name unless it contains some parenthesis * The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression). * (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part. * @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions. * Please refer to [[where]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query * @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself * @return Command the command object itself
*/ */
@ -573,7 +587,7 @@ class Command extends \yii\base\Component
* The method will automatically quote the table name unless it contains some parenthesis * The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression). * (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part. * @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions. * Please refer to [[where]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query * @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself * @return Command the command object itself
*/ */
@ -589,7 +603,7 @@ class Command extends \yii\base\Component
* The method will automatically quote the table name unless it contains some parenthesis * The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression). * (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part. * @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions. * Please refer to [[where]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query * @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself * @return Command the command object itself
*/ */
@ -643,7 +657,7 @@ class Command extends \yii\base\Component
/** /**
* Sets the HAVING part of the query. * Sets the HAVING part of the query.
* @param mixed $conditions the conditions to be put after HAVING. * @param mixed $conditions the conditions to be put after HAVING.
* Please refer to {@link where} on how to specify conditions. * Please refer to [[where]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query * @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself * @return Command the command object itself
*/ */
@ -719,8 +733,8 @@ class Command extends \yii\base\Component
* The method will properly escape the column names and bind the values to be updated. * The method will properly escape the column names and bind the values to be updated.
* @param string $table the table to be updated. * @param string $table the table to be updated.
* @param array $columns the column data (name=>value) to be updated. * @param array $columns the column data (name=>value) to be updated.
* @param mixed $conditions the conditions that will be put in the WHERE part. Please * @param mixed $conditions the conditions that will be put in the WHERE part.
* refer to {@link where} on how to specify conditions. * Please refer to [[where]] on how to specify this parameter.
* @param array $params the parameters to be bound to the query. * @param array $params the parameters to be bound to the query.
* @return integer number of rows affected by the execution. * @return integer number of rows affected by the execution.
*/ */
@ -733,8 +747,8 @@ class Command extends \yii\base\Component
/** /**
* Creates and executes a DELETE SQL statement. * Creates and executes a DELETE SQL statement.
* @param string $table the table where the data will be deleted from. * @param string $table the table where the data will be deleted from.
* @param mixed $conditions the conditions that will be put in the WHERE part. Please * @param mixed $conditions the conditions that will be put in the WHERE part.
* refer to {@link where} on how to specify conditions. * Please refer to [[where]] on how to specify this parameter.
* @param array $params the parameters to be bound to the query. * @param array $params the parameters to be bound to the query.
* @return integer number of rows affected by the execution. * @return integer number of rows affected by the execution.
*/ */
@ -750,7 +764,9 @@ class Command extends \yii\base\Component
* The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'), * The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'),
* where name stands for a column name which will be properly quoted by the method, and definition * where name stands for a column name which will be properly quoted by the method, and definition
* stands for the column type which can contain an abstract DB type. * stands for the column type which can contain an abstract DB type.
* The {@link getColumnType} method will be invoked to convert any abstract type into a physical one. * The method [[\yii\db\dao\QueryBuilder::getColumnType()]] will be called
* to convert the abstract column types to physical ones. For example, `string` will be converted
* as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
* *
* If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
* inserted into the generated SQL. * inserted into the generated SQL.
@ -804,9 +820,9 @@ class Command extends \yii\base\Component
* Builds and executes a SQL statement for adding a new DB column. * Builds and executes a SQL statement for adding a new DB column.
* @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
* @param string $column the name of the new column. The name will be properly quoted by the method. * @param string $column the name of the new column. The name will be properly quoted by the method.
* @param string $type the column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any) * @param string $type the column type. [[\yii\db\dao\QueryBuilder::getColumnType()]] will be called
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. * to convert the give column type to the physical one. For example, `string` will be converted
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
* @return integer number of rows affected by the execution. * @return integer number of rows affected by the execution.
*/ */
public function addColumn($table, $column, $type) public function addColumn($table, $column, $type)
@ -844,9 +860,9 @@ class Command extends \yii\base\Component
* Builds and executes a SQL statement for changing the definition of a column. * Builds and executes a SQL statement for changing the definition of a column.
* @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
* @param string $column the name of the column to be changed. The name will be properly quoted by the method. * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
* @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any) * @param string $type the column type. [[\yii\db\dao\QueryBuilder::getColumnType()]] will be called
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. * to convert the give column type to the physical one. For example, `string` will be converted
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
* @return integer number of rows affected by the execution. * @return integer number of rows affected by the execution.
*/ */
public function alterColumn($table, $column, $type) public function alterColumn($table, $column, $type)
@ -920,7 +936,7 @@ class Command extends \yii\base\Component
* The method will automatically quote the table name unless it contains some parenthesis * The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression). * (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part. * @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions. * Please refer to [[where]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query * @param array $params the parameters (name=>value) to be bound to the query
* @return Command the command object itself * @return Command the command object itself
*/ */

70
framework/db/dao/Connection.php

@ -26,7 +26,7 @@ use yii\db\Exception;
* the DB connection: * the DB connection:
* *
* ~~~ * ~~~
* $connection = \yii\db\dao\Connection::create($dsn, $username, $password); * $connection = \yii\db\dao\Connection::newInstance($dsn, $username, $password);
* $connection->active = true; // same as: $connection->open(); * $connection->active = true; // same as: $connection->open();
* ~~~ * ~~~
* *
@ -57,13 +57,13 @@ use yii\db\Exception;
* ~~~ * ~~~
* $transaction = $connection->beginTransaction(); * $transaction = $connection->beginTransaction();
* try { * try {
* $connection->createCommand($sql1)->execute(); * $connection->createCommand($sql1)->execute();
* $connection->createCommand($sql2)->execute(); * $connection->createCommand($sql2)->execute();
* // ... executing other SQL statements ... * // ... executing other SQL statements ...
* $transaction->commit(); * $transaction->commit();
* } * }
* catch(Exception $e) { * catch(Exception $e) {
* $transaction->rollBack(); * $transaction->rollBack();
* } * }
* ~~~ * ~~~
* *
@ -72,15 +72,15 @@ use yii\db\Exception;
* *
* ~~~ * ~~~
* array( * array(
* 'components' => array( * 'components' => array(
* 'db' => array( * 'db' => array(
* 'class' => '\yii\db\dao\Connection', * 'class' => '\yii\db\dao\Connection',
* 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
* 'username' => 'root', * 'username' => 'root',
* 'password' => '', * 'password' => '',
* 'charset' => 'utf8', * 'charset' => 'utf8',
* ), * ),
* ), * ),
* ) * )
* ~~~ * ~~~
* *
@ -219,7 +219,7 @@ class Connection extends \yii\base\ApplicationComponent
/** /**
* @var array mapping between PDO driver names and [[Schema]] classes. * @var array mapping between PDO driver names and [[Schema]] classes.
* The keys of the array are PDO driver names while the values the corresponding * The keys of the array are PDO driver names while the values the corresponding
* schema class name or configuration. Please refer to [[\Yii::create]] for * schema class name or configuration. Please refer to [[\Yii::createObject]] for
* details on how to specify a configuration. * details on how to specify a configuration.
* *
* This property is mainly used by [[getSchema]] when fetching the database schema information. * This property is mainly used by [[getSchema]] when fetching the database schema information.
@ -227,15 +227,15 @@ class Connection extends \yii\base\ApplicationComponent
* [[Schema]] class to support DBMS that is not supported by Yii. * [[Schema]] class to support DBMS that is not supported by Yii.
*/ */
public $schemaMap = array( public $schemaMap = array(
'pgsql' => '\yii\db\dao\pgsql\Schema', // PostgreSQL 'pgsql' => '\yii\db\dao\pgsql\Schema', // PostgreSQL
'mysqli' => '\yii\db\dao\mysql\Schema', // MySQL 'mysqli' => '\yii\db\dao\mysql\Schema', // MySQL
'mysql' => '\yii\db\dao\mysql\Schema', // MySQL 'mysql' => '\yii\db\dao\mysql\Schema', // MySQL
'sqlite' => '\yii\db\dao\sqlite\Schema', // sqlite 3 'sqlite' => '\yii\db\dao\sqlite\Schema', // sqlite 3
'sqlite2' => '\yii\db\dao\sqlite\Schema', // sqlite 2 'sqlite2' => '\yii\db\dao\sqlite\Schema', // sqlite 2
'mssql' => '\yii\db\dao\mssql\Schema', // Mssql driver on windows hosts 'mssql' => '\yii\db\dao\mssql\Schema', // Mssql driver on windows hosts
'dblib' => '\yii\db\dao\mssql\Schema', // dblib drivers on linux (and maybe others os) hosts 'dblib' => '\yii\db\dao\mssql\Schema', // dblib drivers on linux (and maybe others os) hosts
'sqlsrv' => '\yii\db\dao\mssql\Schema', // Mssql 'sqlsrv' => '\yii\db\dao\mssql\Schema', // Mssql
'oci' => '\yii\db\dao\oci\Schema', // Oracle driver 'oci' => '\yii\db\dao\oci\Schema', // Oracle driver
); );
/** /**
@ -428,7 +428,8 @@ class Connection extends \yii\base\ApplicationComponent
{ {
if ($this->_transaction !== null && $this->_transaction->active) { if ($this->_transaction !== null && $this->_transaction->active) {
return $this->_transaction; return $this->_transaction;
} else { }
else {
return null; return null;
} }
} }
@ -453,11 +454,13 @@ class Connection extends \yii\base\ApplicationComponent
{ {
if ($this->_schema !== null) { if ($this->_schema !== null) {
return $this->_schema; return $this->_schema;
} else { }
else {
$driver = $this->getDriverName(); $driver = $this->getDriverName();
if (isset($this->schemaMap[$driver])) { if (isset($this->schemaMap[$driver])) {
return $this->_schema = \Yii::create($this->schemaMap[$driver], $this); return $this->_schema = \Yii::createObject($this->schemaMap[$driver], $this);
} else { }
else {
throw new Exception("Connection does not support reading schema for '$driver' database."); throw new Exception("Connection does not support reading schema for '$driver' database.");
} }
} }
@ -500,7 +503,8 @@ class Connection extends \yii\base\ApplicationComponent
$this->open(); $this->open();
if (($value = $this->pdo->quote($str)) !== false) { if (($value = $this->pdo->quote($str)) !== false) {
return $value; return $value;
} else { // the driver doesn't support quote (e.g. oci) }
else { // the driver doesn't support quote (e.g. oci)
return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
} }
} }
@ -542,7 +546,8 @@ class Connection extends \yii\base\ApplicationComponent
{ {
if ($this->tablePrefix !== null && strpos($sql, '{{') !== false) { if ($this->tablePrefix !== null && strpos($sql, '{{') !== false) {
return preg_replace('/{{(.*?)}}/', $this->tablePrefix . '\1', $sql); return preg_replace('/{{(.*?)}}/', $this->tablePrefix . '\1', $sql);
} else { }
else {
return $sql; return $sql;
} }
} }
@ -572,7 +577,8 @@ class Connection extends \yii\base\ApplicationComponent
{ {
if (($pos = strpos($this->dsn, ':')) !== false) { if (($pos = strpos($this->dsn, ':')) !== false) {
return strtolower(substr($this->dsn, 0, $pos)); return strtolower(substr($this->dsn, 0, $pos));
} else { }
else {
return strtolower($this->getAttribute(\PDO::ATTR_DRIVER_NAME)); return strtolower($this->getAttribute(\PDO::ATTR_DRIVER_NAME));
} }
} }

2
framework/db/dao/DataReader.php

@ -22,7 +22,7 @@ use yii\db\Exception;
* *
* ~~~ * ~~~
* foreach($reader as $row) { * foreach($reader as $row) {
* // $row represents a row of data * // $row represents a row of data
* } * }
* ~~~ * ~~~
* *

79
framework/db/dao/Query.php

@ -102,10 +102,10 @@ class Query extends \yii\base\Object
} }
if ($this->select !== $query->select) { if ($this->select !== $query->select) {
if($this->select === '*') { if ($this->select === '*') {
$this->select = $query->select; $this->select = $query->select;
} }
elseif($query->select!=='*') { elseif ($query->select !== '*') {
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select; $select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($query->select) ? preg_split('/\s*,\s*/', trim($query->select), -1, PREG_SPLIT_NO_EMPTY) : $query->select; $select2 = is_string($query->select) ? preg_split('/\s*,\s*/', trim($query->select), -1, PREG_SPLIT_NO_EMPTY) : $query->select;
$this->select = array_merge($select1, array_diff($select2, $select1)); $this->select = array_merge($select1, array_diff($select2, $select1));
@ -238,16 +238,19 @@ class Query extends \yii\base\Object
*/ */
public function addCondition($condition, $operator = 'AND') public function addCondition($condition, $operator = 'AND')
{ {
if (is_array($condition)) if (is_array($condition)) {
{ if ($condition === array()) {
if ($condition === array())
return $this; return $this;
}
$condition = '(' . implode(') ' . $operator . ' (', $condition) . ')'; $condition = '(' . implode(') ' . $operator . ' (', $condition) . ')';
} }
if ($this->condition === '') if ($this->condition === '') {
$this->condition = $condition; $this->condition = $condition;
}
else else
{
$this->condition = '(' . $this->condition . ') ' . $operator . ' (' . $condition . ')'; $this->condition = '(' . $this->condition . ') ' . $operator . ' (' . $condition . ')';
}
return $this; return $this;
} }
@ -271,10 +274,12 @@ class Query extends \yii\base\Object
*/ */
public function addSearchCondition($column, $keyword, $escape = true, $operator = 'AND', $like = 'LIKE') public function addSearchCondition($column, $keyword, $escape = true, $operator = 'AND', $like = 'LIKE')
{ {
if ($keyword == '') if ($keyword == '') {
return $this; return $this;
if ($escape) }
if ($escape) {
$keyword = '%' . strtr($keyword, array('%' => '\%', '_' => '\_', '\\' => '\\\\')) . '%'; $keyword = '%' . strtr($keyword, array('%' => '\%', '_' => '\_', '\\' => '\\\\')) . '%';
}
$condition = $column . " $like " . self::PARAM_PREFIX . self::$paramCount; $condition = $column . " $like " . self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $keyword; $this->params[self::PARAM_PREFIX . self::$paramCount++] = $keyword;
return $this->addCondition($condition, $operator); return $this->addCondition($condition, $operator);
@ -294,13 +299,14 @@ class Query extends \yii\base\Object
*/ */
public function addInCondition($column, $values, $operator = 'AND') public function addInCondition($column, $values, $operator = 'AND')
{ {
if (($n = count($values)) < 1) if (($n = count($values)) < 1) {
return $this->addCondition('0=1', $operator); // 0=1 is used because in MSSQL value alone can't be used in WHERE return $this->addCondition('0=1', $operator);
if ($n === 1) } // 0=1 is used because in MSSQL value alone can't be used in WHERE
{ if ($n === 1) {
$value = reset($values); $value = reset($values);
if ($value === null) if ($value === null) {
return $this->addCondition($column . ' IS NULL'); return $this->addCondition($column . ' IS NULL');
}
$condition = $column . '=' . self::PARAM_PREFIX . self::$paramCount; $condition = $column . '=' . self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value; $this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
} }
@ -331,13 +337,14 @@ class Query extends \yii\base\Object
*/ */
public function addNotInCondition($column, $values, $operator = 'AND') public function addNotInCondition($column, $values, $operator = 'AND')
{ {
if (($n = count($values)) < 1) if (($n = count($values)) < 1) {
return $this; return $this;
if ($n === 1) }
{ if ($n === 1) {
$value = reset($values); $value = reset($values);
if ($value === null) if ($value === null) {
return $this->addCondition($column . ' IS NOT NULL'); return $this->addCondition($column . ' IS NOT NULL');
}
$condition = $column . '!=' . self::PARAM_PREFIX . self::$paramCount; $condition = $column . '!=' . self::PARAM_PREFIX . self::$paramCount;
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value; $this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
} }
@ -370,8 +377,9 @@ class Query extends \yii\base\Object
$params = array(); $params = array();
foreach ($columns as $name => $value) foreach ($columns as $name => $value)
{ {
if ($value === null) if ($value === null) {
$params[] = $name . ' IS NULL'; $params[] = $name . ' IS NULL';
}
else else
{ {
$params[] = $name . '=' . self::PARAM_PREFIX . self::$paramCount; $params[] = $name . '=' . self::PARAM_PREFIX . self::$paramCount;
@ -426,35 +434,42 @@ class Query extends \yii\base\Object
*/ */
public function compare($column, $value, $partialMatch = false, $operator = 'AND', $escape = true) public function compare($column, $value, $partialMatch = false, $operator = 'AND', $escape = true)
{ {
if (is_array($value)) if (is_array($value)) {
{ if ($value === array()) {
if ($value === array())
return $this; return $this;
}
return $this->addInCondition($column, $value, $operator); return $this->addInCondition($column, $value, $operator);
} }
else else
{
$value = "$value"; $value = "$value";
}
if (preg_match('/^(?:\s*(<>|<=|>=|<|>|=))?(.*)$/', $value, $matches)) if (preg_match('/^(?:\s*(<>|<=|>=|<|>|=))?(.*)$/', $value, $matches)) {
{
$value = $matches[2]; $value = $matches[2];
$op = $matches[1]; $op = $matches[1];
} }
else else
{
$op = ''; $op = '';
}
if ($value === '') if ($value === '') {
return $this; return $this;
}
if ($partialMatch) if ($partialMatch) {
{ if ($op === '') {
if ($op === '')
return $this->addSearchCondition($column, $value, $escape, $operator); return $this->addSearchCondition($column, $value, $escape, $operator);
if ($op === '<>') }
if ($op === '<>') {
return $this->addSearchCondition($column, $value, $escape, $operator, 'NOT LIKE'); return $this->addSearchCondition($column, $value, $escape, $operator, 'NOT LIKE');
}
} }
elseif ($op === '') elseif ($op === '')
{
$op = '='; $op = '=';
}
$this->addCondition($column . $op . self::PARAM_PREFIX . self::$paramCount, $operator); $this->addCondition($column . $op . self::PARAM_PREFIX . self::$paramCount, $operator);
$this->params[self::PARAM_PREFIX . self::$paramCount++] = $value; $this->params[self::PARAM_PREFIX . self::$paramCount++] = $value;
@ -479,8 +494,9 @@ class Query extends \yii\base\Object
*/ */
public function addBetweenCondition($column, $valueStart, $valueEnd, $operator = 'AND') public function addBetweenCondition($column, $valueStart, $valueEnd, $operator = 'AND')
{ {
if ($valueStart === '' || $valueEnd === '') if ($valueStart === '' || $valueEnd === '') {
return $this; return $this;
}
$paramStart = self::PARAM_PREFIX . self::$paramCount++; $paramStart = self::PARAM_PREFIX . self::$paramCount++;
$paramEnd = self::PARAM_PREFIX . self::$paramCount++; $paramEnd = self::PARAM_PREFIX . self::$paramCount++;
@ -488,10 +504,13 @@ class Query extends \yii\base\Object
$this->params[$paramEnd] = $valueEnd; $this->params[$paramEnd] = $valueEnd;
$condition = "$column BETWEEN $paramStart AND $paramEnd"; $condition = "$column BETWEEN $paramStart AND $paramEnd";
if ($this->condition === '') if ($this->condition === '') {
$this->condition = $condition; $this->condition = $condition;
}
else else
{
$this->condition = '(' . $this->condition . ') ' . $operator . ' (' . $condition . ')'; $this->condition = '(' . $this->condition . ') ' . $operator . ' (' . $condition . ')';
}
return $this; return $this;
} }

65
framework/db/dao/QueryBuilder.php

@ -23,21 +23,21 @@ class QueryBuilder extends \yii\base\Object
/** /**
* @var array the abstract column types mapped to physical column types. * @var array the abstract column types mapped to physical column types.
*/ */
public $typeMap = array( public $typeMap = array(
'pk' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', 'pk' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
'string' => 'varchar(255)', 'string' => 'varchar(255)',
'text' => 'text', 'text' => 'text',
'integer' => 'int(11)', 'integer' => 'int(11)',
'float' => 'float', 'float' => 'float',
'decimal' => 'decimal', 'decimal' => 'decimal',
'datetime' => 'datetime', 'datetime' => 'datetime',
'timestamp' => 'timestamp', 'timestamp' => 'timestamp',
'time' => 'time', 'time' => 'time',
'date' => 'date', 'date' => 'date',
'binary' => 'blob', 'binary' => 'blob',
'boolean' => 'tinyint(1)', 'boolean' => 'tinyint(1)',
'money' => 'decimal(19,4)', 'money' => 'decimal(19,4)',
); );
/** /**
* @var Connection the database connection. * @var Connection the database connection.
*/ */
@ -177,7 +177,9 @@ class QueryBuilder extends \yii\base\Object
$cols[] = "\t" . $this->schema->quoteColumnName($name) . ' ' . $this->schema->getColumnType($type); $cols[] = "\t" . $this->schema->quoteColumnName($name) . ' ' . $this->schema->getColumnType($type);
} }
else else
{
$cols[] = "\t" . $type; $cols[] = "\t" . $type;
}
} }
$sql = "CREATE TABLE " . $this->schema->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; $sql = "CREATE TABLE " . $this->schema->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
return $options === null ? $sql : $sql . ' ' . $options; return $options === null ? $sql : $sql . ' ' . $options;
@ -288,20 +290,24 @@ class QueryBuilder extends \yii\base\Object
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
{ {
$columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY); $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $i => $col) foreach ($columns as $i => $col) {
$columns[$i] = $this->schema->quoteColumnName($col); $columns[$i] = $this->schema->quoteColumnName($col);
}
$refColumns = preg_split('/\s*,\s*/', $refColumns, -1, PREG_SPLIT_NO_EMPTY); $refColumns = preg_split('/\s*,\s*/', $refColumns, -1, PREG_SPLIT_NO_EMPTY);
foreach ($refColumns as $i => $col) foreach ($refColumns as $i => $col) {
$refColumns[$i] = $this->schema->quoteColumnName($col); $refColumns[$i] = $this->schema->quoteColumnName($col);
}
$sql = 'ALTER TABLE ' . $this->schema->quoteTableName($table) $sql = 'ALTER TABLE ' . $this->schema->quoteTableName($table)
. ' ADD CONSTRAINT ' . $this->schema->quoteColumnName($name) . ' ADD CONSTRAINT ' . $this->schema->quoteColumnName($name)
. ' FOREIGN KEY (' . implode(', ', $columns) . ')' . ' FOREIGN KEY (' . implode(', ', $columns) . ')'
. ' REFERENCES ' . $this->schema->quoteTableName($refTable) . ' REFERENCES ' . $this->schema->quoteTableName($refTable)
. ' (' . implode(', ', $refColumns) . ')'; . ' (' . implode(', ', $refColumns) . ')';
if ($delete !== null) if ($delete !== null) {
$sql .= ' ON DELETE ' . $delete; $sql .= ' ON DELETE ' . $delete;
if ($update !== null) }
if ($update !== null) {
$sql .= ' ON UPDATE ' . $update; $sql .= ' ON UPDATE ' . $update;
}
return $sql; return $sql;
} }
@ -332,10 +338,13 @@ class QueryBuilder extends \yii\base\Object
$columns = preg_split('/\s*,\s*/', $column, -1, PREG_SPLIT_NO_EMPTY); $columns = preg_split('/\s*,\s*/', $column, -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $col) foreach ($columns as $col)
{ {
if (strpos($col, '(') !== false) if (strpos($col, '(') !== false) {
$cols[] = $col; $cols[] = $col;
}
else else
{
$cols[] = $this->schema->quoteColumnName($col); $cols[] = $this->schema->quoteColumnName($col);
}
} }
return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
. $this->schema->quoteTableName($name) . ' ON ' . $this->schema->quoteTableName($name) . ' ON '
@ -400,8 +409,8 @@ class QueryBuilder extends \yii\base\Object
* @param string $type abstract column type * @param string $type abstract column type
* @return string physical column type. * @return string physical column type.
*/ */
public function getColumnType($type) public function getColumnType($type)
{ {
if (isset($this->typeMap[$type])) { if (isset($this->typeMap[$type])) {
return $this->typeMap[$type]; return $this->typeMap[$type];
} }
@ -485,19 +494,19 @@ class QueryBuilder extends \yii\base\Object
} }
foreach ($joins as $i => $join) { foreach ($joins as $i => $join) {
if (is_array($join)) { // join type, table name, on-condition if (is_array($join)) { // join type, table name, on-condition
if (isset($join[0], $join[1])) { if (isset($join[0], $join[1])) {
$table = $join[1]; $table = $join[1];
if (strpos($table,'(')===false) { if (strpos($table, '(') === false) {
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias
$table = $this->connection->quoteTableName($matches[1]).' '.$this->connection->quoteTableName($matches[2]); $table = $this->connection->quoteTableName($matches[1]) . ' ' . $this->connection->quoteTableName($matches[2]);
} }
else { else {
$table = $this->connection->quoteTableName($table); $table = $this->connection->quoteTableName($table);
} }
} }
$joins[$i] = strtoupper($join[0]) . ' ' . $table; $joins[$i] = strtoupper($join[0]) . ' ' . $table;
if (isset($join[2])) { // join condition if (isset($join[2])) { // join condition
$condition = $this->buildCondition($join[2]); $condition = $this->buildCondition($join[2]);
$joins[$i] .= ' ON ' . $condition; $joins[$i] .= ' ON ' . $condition;
} }
@ -582,8 +591,8 @@ class QueryBuilder extends \yii\base\Object
if ($query->limit !== null && $query->limit >= 0) { if ($query->limit !== null && $query->limit >= 0) {
$sql = 'LIMIT ' . (int)$query->limit; $sql = 'LIMIT ' . (int)$query->limit;
} }
if ($query->offset>0) { if ($query->offset > 0) {
$sql .= ' OFFSET '.(int)$query->offset; $sql .= ' OFFSET ' . (int)$query->offset;
} }
return ltrim($sql); return ltrim($sql);
} }

5
framework/db/dao/Schema.php

@ -20,6 +20,9 @@ use yii\db\Exception;
*/ */
abstract class Schema extends \yii\base\Object abstract class Schema extends \yii\base\Object
{ {
/**
* @var \yii\db\dao\Connection the database connection
*/
public $connection; public $connection;
private $_tableNames = array(); private $_tableNames = array();
@ -194,7 +197,9 @@ abstract class Schema extends \yii\base\Object
$name = substr($name, $pos + 1); $name = substr($name, $pos + 1);
} }
else else
{
$prefix = ''; $prefix = '';
}
return $prefix . $this->quoteSimpleColumnName($name); return $prefix . $this->quoteSimpleColumnName($name);
} }

6
framework/db/dao/TableSchema.php

@ -55,9 +55,9 @@ class TableSchema extends \yii\base\Object
* *
* ~~~ * ~~~
* array( * array(
* 'ForeignTableName', * 'ForeignTableName',
* 'fk1' => 'pk1', // pk1 is in foreign table * 'fk1' => 'pk1', // pk1 is in foreign table
* 'fk2' => 'pk2', // if composite foreign key * 'fk2' => 'pk2', // if composite foreign key
* ) * )
* ~~~ * ~~~
*/ */

16
framework/db/dao/Transaction.php

@ -23,13 +23,13 @@ use yii\db\Exception;
* ~~~ * ~~~
* $transaction = $connection->beginTransaction(); * $transaction = $connection->beginTransaction();
* try { * try {
* $connection->createCommand($sql1)->execute(); * $connection->createCommand($sql1)->execute();
* $connection->createCommand($sql2)->execute(); * $connection->createCommand($sql2)->execute();
* //.... other SQL executions * //.... other SQL executions
* $transaction->commit(); * $transaction->commit();
* } * }
* catch(Exception $e) { * catch(Exception $e) {
* $transaction->rollBack(); * $transaction->rollBack();
* } * }
* ~~~ * ~~~
* *
@ -69,7 +69,8 @@ class Transaction extends \yii\base\Object
\Yii::trace('Committing transaction', __CLASS__); \Yii::trace('Committing transaction', __CLASS__);
$this->connection->pdo->commit(); $this->connection->pdo->commit();
$this->active = false; $this->active = false;
} else { }
else {
throw new Exception('Failed to commit transaction: transaction was inactive.'); throw new Exception('Failed to commit transaction: transaction was inactive.');
} }
} }
@ -84,7 +85,8 @@ class Transaction extends \yii\base\Object
\Yii::trace('Rolling back transaction', __CLASS__); \Yii::trace('Rolling back transaction', __CLASS__);
$this->connection->pdo->rollBack(); $this->connection->pdo->rollBack();
$this->active = false; $this->active = false;
} else { }
else {
throw new Exception('Failed to roll back transaction: transaction was inactive.'); throw new Exception('Failed to roll back transaction: transaction was inactive.');
} }
} }

21
framework/logging/Logger.php

@ -22,12 +22,12 @@ namespace yii\logging;
*/ */
class Logger extends \yii\base\Component class Logger extends \yii\base\Component
{ {
const LEVEL_ERROR = 1; const LEVEL_ERROR = 'error';
const LEVEL_WARNING = 2; const LEVEL_WARNING = 'warning';
const LEVEL_INFO = 3; const LEVEL_INFO = 'info';
const LEVEL_TRACE = 4; const LEVEL_TRACE = 'trace';
const LEVEL_PROFILE_BEGIN = 5; const LEVEL_PROFILE_BEGIN = 'profile-begin';
const LEVEL_PROFILE_END = 6; const LEVEL_PROFILE_END = 'profile-end';
/** /**
* @var integer how many messages should be logged before they are flushed from memory and sent to targets. * @var integer how many messages should be logged before they are flushed from memory and sent to targets.
@ -253,13 +253,10 @@ class Logger extends \yii\base\Component
$stack = array(); $stack = array();
foreach ($this->messages as $log) { foreach ($this->messages as $log) {
if ($log[1] < self::LEVEL_PROFILE_BEGIN) { if ($log[1] === self::LEVEL_PROFILE_BEGIN) {
continue;
}
list($token, $level, $category, $timestamp) = $log;
if ($level === self::LEVEL_PROFILE_BEGIN) {
$stack[] = $log; $stack[] = $log;
} else { } elseif ($log[1] === self::LEVEL_PROFILE_END) {
list($token, $level, $category, $timestamp) = $log;
if (($last = array_pop($stack)) !== null && $last[0] === $token) { if (($last = array_pop($stack)) !== null && $last[0] === $token) {
$timings[] = array($token, $category, $timestamp - $last[3]); $timings[] = array($token, $category, $timestamp - $last[3]);
} else { } else {

4
framework/logging/Router.php

@ -101,7 +101,7 @@ class Router extends \yii\base\ApplicationComponent
* Sets the log targets. * Sets the log targets.
* @param array $config list of log target configurations. Each array element * @param array $config list of log target configurations. Each array element
* represents the configuration for creating a single log target. It will be * represents the configuration for creating a single log target. It will be
* passed to [[\Yii::create]] to create the target instance. * passed to [[\Yii::createObject]] to create the target instance.
*/ */
public function setTargets($config) public function setTargets($config)
{ {
@ -110,7 +110,7 @@ class Router extends \yii\base\ApplicationComponent
$this->_targets[$name] = $target; $this->_targets[$name] = $target;
} }
else { else {
$this->_targets[$name] = \Yii::create($target); $this->_targets[$name] = \Yii::createObject($target);
} }
} }
} }

2
framework/validators/Validator.php

@ -166,7 +166,7 @@ abstract class Validator extends \yii\base\Component
foreach ($params as $name => $value) { foreach ($params as $name => $value) {
$config[$name] = $value; $config[$name] = $value;
} }
$validator = \Yii::create($config); $validator = \Yii::createObject($config);
return $validator; return $validator;
} }

2
tests/unit/framework/base/BehaviorTest.php

@ -21,7 +21,7 @@ class BehaviorTest extends \yiiunit\TestCase
{ {
public function testAttachAndAccessing() public function testAttachAndAccessing()
{ {
$bar = BarClass::create(); $bar = BarClass::newInstance();
$behavior = new BarBehavior(); $behavior = new BarBehavior();
$bar->attachBehavior('bar', $behavior); $bar->attachBehavior('bar', $behavior);
$this->assertEquals('behavior property', $bar->behaviorProperty); $this->assertEquals('behavior property', $bar->behaviorProperty);

2
tests/unit/framework/base/ComponentTest.php

@ -206,7 +206,7 @@ class ComponentTest extends \yiiunit\TestCase
public function testCreate() public function testCreate()
{ {
$component = NewComponent2::create(1, 2, array('a'=>3)); $component = NewComponent2::newInstance(1, 2, array('a'=>3));
$this->assertEquals(1, $component->b); $this->assertEquals(1, $component->b);
$this->assertEquals(2, $component->c); $this->assertEquals(2, $component->c);
$this->assertEquals(3, $component->a); $this->assertEquals(3, $component->a);

34
tests/unit/framework/base/ObjectTest.php

@ -7,6 +7,23 @@ class Foo extends \yii\base\Object
public $prop; public $prop;
} }
class Bar extends \yii\base\Component implements \yii\base\Initable
{
public $prop1;
public $prop2;
public $prop3;
public function __construct($a, $b)
{
$this->prop1 = $a + $b;
}
public function init()
{
$this->prop3 = 3;
}
}
/** /**
* ObjectTest * ObjectTest
*/ */
@ -24,15 +41,28 @@ class ObjectTest extends \yiiunit\TestCase
$this->object = null; $this->object = null;
} }
public function testCreate() public function testNewInstance()
{ {
$foo = Foo::create(array( $foo = Foo::newInstance(array(
'prop' => array( 'prop' => array(
'test' => 'test', 'test' => 'test',
), ),
)); ));
$this->assertEquals('test', $foo->prop['test']); $this->assertEquals('test', $foo->prop['test']);
$bar = Bar::newInstance(10, 20);
$this->assertEquals(30, $bar->prop1);
$this->assertEquals(null, $bar->prop2);
$this->assertEquals(3, $bar->prop3);
$bar = Bar::newInstance(100, 200, array(
'prop2' => 'x',
'prop3' => 400,
));
$this->assertEquals(300, $bar->prop1);
$this->assertEquals('x', $bar->prop2);
$this->assertEquals(3, $bar->prop3);
} }
public function testHasProperty() public function testHasProperty()

Loading…
Cancel
Save