Browse Source

Merge branch 'master'

tags/2.0.0-beta
Alexander Makarov 12 years ago
parent
commit
6c00f6d2b4
  1. 1
      build/.htaccess
  2. 20
      build/build
  3. 23
      build/build.bat
  4. 276
      build/build.xml
  5. 112
      build/controllers/LocaleController.php
  6. 2
      docs/code_style.md
  7. 40
      framework/YiiBase.php
  8. 4
      framework/base/Application.php
  9. 5
      framework/base/Component.php
  10. 122
      framework/base/Controller.php
  11. 8
      framework/base/Dictionary.php
  12. 10
      framework/base/ErrorHandler.php
  13. 19
      framework/base/Event.php
  14. 5
      framework/base/HttpException.php
  15. 16
      framework/base/Model.php
  16. 14
      framework/base/Module.php
  17. 6
      framework/base/Vector.php
  18. 93
      framework/base/View.php
  19. 57
      framework/base/Widget.php
  20. 2
      framework/caching/ChainedDependency.php
  21. 159
      framework/caching/DbCache.php
  22. 69
      framework/caching/DbDependency.php
  23. 13
      framework/caching/ExpressionDependency.php
  24. 16
      framework/caching/FileDependency.php
  25. 7
      framework/console/Controller.php
  26. 4
      framework/console/controllers/HelpController.php
  27. 47
      framework/console/controllers/MigrateController.php
  28. 103
      framework/db/ActiveRecord.php
  29. 98
      framework/db/Command.php
  30. 67
      framework/db/Connection.php
  31. 479
      framework/db/QueryBuilder.php
  32. 34
      framework/db/Schema.php
  33. 23
      framework/db/StaleObjectException.php
  34. 6
      framework/db/Transaction.php
  35. 15
      framework/helpers/Html.php
  36. 97
      framework/i18n/I18N.php
  37. 2
      framework/i18n/PhpMessageSource.php
  38. 627
      framework/i18n/data/plurals.php
  39. 109
      framework/i18n/data/plurals.xml
  40. 68
      framework/logging/DbTarget.php
  41. 3
      framework/logging/Target.php
  42. 104
      framework/web/AccessControl.php
  43. 188
      framework/web/AccessRule.php
  44. 77
      framework/web/Application.php
  45. 58
      framework/web/CacheSession.php
  46. 25
      framework/web/Controller.php
  47. 150
      framework/web/DbSession.php
  48. 131
      framework/web/HttpCache.php
  49. 81
      framework/web/Identity.php
  50. 12
      framework/web/PageCache.php
  51. 2
      framework/web/Request.php
  52. 106
      framework/web/Response.php
  53. 177
      framework/web/Session.php
  54. 30
      framework/web/UrlManager.php
  55. 866
      framework/web/User.php
  56. 34
      framework/web/UserEvent.php
  57. 38
      framework/widgets/ActiveForm.php
  58. 8
      framework/widgets/Clip.php
  59. 21
      framework/widgets/ContentDecorator.php
  60. 75
      framework/widgets/FragmentCache.php
  61. 4
      tests/unit/MysqlTestCase.php
  62. 17
      tests/unit/data/base/InvalidRulesModel.php
  63. 21
      tests/unit/data/base/Singer.php
  64. 39
      tests/unit/data/base/Speaker.php
  65. 2
      tests/unit/framework/base/ComponentTest.php
  66. 24
      tests/unit/framework/base/DictionaryTest.php
  67. 203
      tests/unit/framework/base/ModelTest.php
  68. 14
      tests/unit/framework/base/VectorTest.php
  69. 2
      tests/unit/framework/caching/DbCacheTest.php
  70. 1
      tests/unit/framework/db/ConnectionTest.php
  71. 2
      tests/unit/framework/helpers/ArrayHelperTest.php
  72. 2
      tests/unit/framework/helpers/HtmlTest.php
  73. 73
      tests/unit/framework/helpers/StringHelperTest.php
  74. 19
      tests/unit/framework/web/UrlManagerTest.php
  75. 4
      tests/unit/framework/web/UrlRuleTest.php

1
build/.htaccess

@ -0,0 +1 @@
deny from all

20
build/build

@ -0,0 +1,20 @@
#!/usr/bin/env php
<?php
/**
* build script file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
// fcgi doesn't have STDIN defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
require(__DIR__ . '/../framework/yii.php');
$id = 'yiic-build';
$basePath = __DIR__;
$application = new yii\console\Application($id, $basePath);
$application->run();

23
build/build.bat

@ -0,0 +1,23 @@
@echo off
rem -------------------------------------------------------------
rem build script for Windows.
rem
rem This is the bootstrap script for running build on Windows.
rem
rem @author Qiang Xue <qiang.xue@gmail.com>
rem @link http://www.yiiframework.com/
rem @copyright 2008 Yii Software LLC
rem @license http://www.yiiframework.com/license/
rem @version $Id$
rem -------------------------------------------------------------
@setlocal
set BUILD_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
%PHP_COMMAND% "%BUILD_PATH%build" %*
@endlocal

276
build/build.xml

@ -0,0 +1,276 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Phing build file for Yii.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2009 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
-->
<project name="yii" basedir="." default="help">
<!-- task definitions -->
<taskdef name="yii-init-build" classname="YiiInitTask" classpath="tasks" />
<!--
<taskdef name="yii-pear" classname="YiiPearTask" classpath="tasks"/>
-->
<!-- init yii.version, yii.revision and yii.winbuild -->
<yii-init-build />
<!-- these are required external commands -->
<property name="php" value="php" /> <!-- PHP parser -->
<property name="hhc" value="hhc" /> <!-- compile phpdoc into CHM -->
<property name="pdflatex" value="pdflatex" /> <!-- generates PDF from LaTex -->
<property name="pkgname" value="${phing.project.name}-${yii.version}.${yii.revision}"/>
<property name="docname" value="${phing.project.name}-docs-${yii.version}.${yii.revision}"/>
<property name="pearname" value="${phing.project.name}-${yii.release}.tgz" />
<!-- directory definitions -->
<property name="build.base.dir" value="release"/>
<property name="build.dist.dir" value="${build.base.dir}/dist"/>
<property name="build.src.dir" value="${build.base.dir}/${pkgname}"/>
<property name="build.pear.src.dir" value="${build.src.dir}/framework" />
<property name="build.doc.dir" value="${build.base.dir}/${docname}"/>
<property name="build.web.dir" value="${build.base.dir}/web"/>
<tstamp>
<format property="DATE" pattern="%b %e %Y" />
</tstamp>
<if>
<equals arg1="${yii.winbuild}" arg2="true"/>
<then>
<property name="build" value="build"/>
</then>
<else>
<property name="build" value="php build"/>
</else>
</if>
<!-- source files in the framework -->
<fileset dir=".." id="framework">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="framework/**/*"/>
<include name="requirements/**/*"/>
<include name="demos/**/*"/>
<include name="CHANGELOG"/>
<include name="UPGRADE"/>
<include name="LICENSE"/>
<include name="README"/>
</fileset>
<!-- doc files -->
<fileset dir="../docs" id="docs">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="guide/**/*"/>
<include name="blog/**/*"/>
</fileset>
<fileset dir="../docs/guide" id="docs-guide">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="**/*"/>
</fileset>
<fileset dir="../docs/blog" id="docs-blog">
<exclude name="**/.gitignore"/>
<exclude name="**/*.bak"/>
<exclude name="**/*~"/>
<include name="**/*"/>
</fileset>
<fileset dir="." id="writables">
<include name="${build.src.dir}/**/runtime" />
<include name="${build.src.dir}/**/assets" />
<include name="${build.src.dir}/demos/**/data" />
</fileset>
<fileset dir="." id="executables">
<include name="${build.src.dir}/**/yiic" />
</fileset>
<target name="src" depends="sync">
<echo>Building package ${pkgname}...</echo>
<echo>Copying files to build directory...</echo>
<copy todir="${build.src.dir}">
<fileset refid="framework"/>
</copy>
<echo>Changing file permissions...</echo>
<chmod mode="0777">
<fileset refid="writables" />
</chmod>
<chmod mode="0755">
<fileset refid="executables" />
</chmod>
<echo>Generating source release file...</echo>
<mkdir dir="${build.dist.dir}" />
<if>
<equals arg1="${yii.winbuild}" arg2="true"/>
<then>
<tar destfile="${build.dist.dir}/${pkgname}.tar.gz" compression="gzip">
<fileset dir="${build.base.dir}">
<include name="${pkgname}/**/*"/>
</fileset>
</tar>
</then>
<else>
<exec command="tar czpf ${pkgname}.tar.gz ${pkgname}" dir="${build.base.dir}"/>
<move file="${build.base.dir}/${pkgname}.tar.gz" todir="${build.dist.dir}" />
</else>
</if>
<zip destfile="${build.dist.dir}/${pkgname}.zip">
<fileset dir="${build.base.dir}">
<include name="${pkgname}/**/*"/>
</fileset>
</zip>
</target>
<target name="doc" depends="sync">
<echo>Building documentation...</echo>
<echo>Building Guide PDF...</echo>
<exec command="${build} guideLatex" dir="." passthru="true" />
<exec command="${pdflatex} guide.tex -interaction=nonstopmode -max-print-line=120" dir="commands/guide" passthru="true"/>
<exec command="${pdflatex} guide.tex -interaction=nonstopmode -max-print-line=120" dir="commands/guide" passthru="true"/>
<exec command="${pdflatex} guide.tex -interaction=nonstopmode -max-print-line=120" dir="commands/guide" passthru="true"/>
<move file="commands/guide/guide.pdf" tofile="${build.doc.dir}/yii-guide-${yii.version}.pdf" />
<echo>Building Blog PDF...</echo>
<exec command="${build} blogLatex" dir="." passthru="true" />
<exec command="${pdflatex} blog.tex -interaction=nonstopmode -max-print-line=120" dir="commands/blog" passthru="true"/>
<exec command="${pdflatex} blog.tex -interaction=nonstopmode -max-print-line=120" dir="commands/blog" passthru="true"/>
<exec command="${pdflatex} blog.tex -interaction=nonstopmode -max-print-line=120" dir="commands/blog" passthru="true"/>
<move file="commands/blog/blog.pdf" tofile="${build.doc.dir}/yii-blog-${yii.version}.pdf" />
<echo>Building API...</echo>
<exec command="${build} api ${build.doc.dir}" dir="." passthru="true" />
<!--
<echo>Building API CHM...</echo>
<exec command="${hhc} ${build.doc.dir}/api/manual.hhp" />
<move file="${build.doc.dir}/api/manual.chm" tofile="${build.doc.dir}/yii-api-${yii.version}.chm" />
<delete>
<fileset dir="${build.doc.dir}/api">
<include name="manual.*" />
</fileset>
</delete>
-->
<echo>Generating doc release file...</echo>
<mkdir dir="${build.dist.dir}" />
<tar destfile="${build.dist.dir}/${docname}.tar.gz" compression="gzip">
<fileset dir="${build.base.dir}">
<include name="${docname}/**/*"/>
</fileset>
</tar>
<zip destfile="${build.dist.dir}/${docname}.zip">
<fileset dir="${build.base.dir}">
<include name="${docname}/**/*"/>
</fileset>
</zip>
</target>
<target name="web" depends="sync">
<echo>Building online API...</echo>
<mkdir dir="${build.web.dir}/common/data/${yii.version}" />
<exec command="${build} api ${build.web.dir}/common/data/${yii.version} online" dir="." passthru="true" />
<echo>Copying tutorials...</echo>
<copy todir="${build.web.dir}/common/data/${yii.version}/tutorials/guide">
<fileset refid="docs-guide"/>
</copy>
<copy todir="${build.web.dir}/common/data/${yii.version}/tutorials/blog">
<fileset refid="docs-blog"/>
</copy>
<echo>Copying release text files...</echo>
<mkdir dir="${build.web.dir}/frontend/www/files" />
<copy file="../CHANGELOG" tofile="${build.web.dir}/frontend/www/files/CHANGELOG-${yii.version}.txt" />
<copy file="../UPGRADE" tofile="${build.web.dir}/frontend/www/files/UPGRADE-${yii.version}.txt" />
<echo>
Finished building Web files.
Please update yiisite/common/data/versions.php file with the following code:
'1.1'=>array(
'version'=>'${yii.version}',
'revision'=>'${yii.revision}',
'date'=>'${yii.date}',
'latest'=>true,
),
</echo>
</target>
<target name="sync">
<echo>Synchronizing code changes for ${pkgname}...</echo>
<echo>Building autoload map...</echo>
<exec command="${build} autoload" dir="." passthru="true"/>
<echo>Building yiilite.php...</echo>
<exec command="${build} lite" dir="." passthru="true"/>
</target>
<target name="message">
<echo>Extracting i18n messages...</echo>
<exec command="${build} message ../framework/messages/config.php" dir="." passthru="true"/>
</target>
<!--
<target name="pear" depends="clean,build">
<echo>Generating pear package for ${phing.project.name}-${yii.release}</echo>
<mkdir dir="${build.dist.dir}" />
<yii-pear pkgdir="${build.pear.src.dir}"
channel="pear.php.net"
version="${yii.release}"
state="stable"
category="framework"
package="${phing.project.name}"
summary="Yii PHP Framework"
pkgdescription="Yii PHP Framework: Best for Web 2.0 Development"
notes="http://www.yiiframework.com/files/CHANGELOG-${yii.release}.txt"
license="BSD"
/>
<exec command="pear package" dir="${build.pear.src.dir}" passthru="true" />
<move file="${build.pear.src.dir}/${pearname}" tofile="${build.dist.dir}/${pearname}" />
</target>
-->
<target name="clean">
<echo>Cleaning up the build...</echo>
<delete dir="${build.base.dir}"/>
</target>
<target name="help">
<echo>
Welcome to use Yii build script!
--------------------------------
You may use the following command format to build a target:
phing &lt;target name&gt;
where &lt;target name&gt; can be one of the following:
- sync : synchronize yiilite.php and YiiBase.php
- message : extract i18n messages of the framework
- src : build source release
- doc : build documentation release (Windows only)
- clean : clean up the build
</echo>
</target>
</project>

112
build/controllers/LocaleController.php

@ -0,0 +1,112 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
use yii\console\Controller;
use yii\helpers\FileHelper;
/**
* http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LocaleController extends Controller
{
public $defaultAction = 'plural';
/**
* Generates the plural rules data.
*
* This command will parse the plural rule XML file from CLDR and convert them
* into appropriate PHP representation to support Yii message translation feature.
* @param string $xmlFile the original plural rule XML file (from CLDR). This file may be found in
* http://www.unicode.org/Public/cldr/latest/core.zip
* Extract the zip file and locate the file "common/supplemental/plurals.xml".
* @throws Exception
*/
public function actionPlural($xmlFile)
{
if (!is_file($xmlFile)) {
throw new Exception("The source plural rule file does not exist: $xmlFile");
}
$xml = simplexml_load_file($xmlFile);
$allRules = array();
$patterns = array(
'/n in 0..1/' => '(n==0||n==1)',
'/\s+is\s+not\s+/i' => '!=', //is not
'/\s+is\s+/i' => '==', //is
'/n\s+mod\s+(\d+)/i' => 'fmod(n,$1)', //mod (CLDR's "mod" is "fmod()", not "%")
'/^(.*?)\s+not\s+in\s+(\d+)\.\.(\d+)/i' => '!in_array($1,range($2,$3))', //not in
'/^(.*?)\s+in\s+(\d+)\.\.(\d+)/i' => 'in_array($1,range($2,$3))', //in
'/^(.*?)\s+not\s+within\s+(\d+)\.\.(\d+)/i' => '($1<$2||$1>$3)', //not within
'/^(.*?)\s+within\s+(\d+)\.\.(\d+)/i' => '($1>=$2&&$1<=$3)', //within
);
foreach ($xml->plurals->pluralRules as $node) {
$attributes = $node->attributes();
$locales = explode(' ', $attributes['locales']);
$rules = array();
if (!empty($node->pluralRule)) {
foreach ($node->pluralRule as $rule) {
$expr_or = preg_split('/\s+or\s+/i', $rule);
foreach ($expr_or as $key_or => $val_or) {
$expr_and = preg_split('/\s+and\s+/i', $val_or);
$expr_and = preg_replace(array_keys($patterns), array_values($patterns), $expr_and);
$expr_or[$key_or] = implode('&&', $expr_and);
}
$expr = preg_replace('/\\bn\\b/', '$n', implode('||', $expr_or));
$rules[] = preg_replace_callback('/range\((\d+),(\d+)\)/', function ($matches) {
if ($matches[2] - $matches[1] <= 5) {
return 'array(' . implode(',', range($matches[1], $matches[2])) . ')';
} else {
return $matches[0];
}
}, $expr);
}
foreach ($locales as $locale) {
$allRules[$locale] = $rules;
}
}
}
// hard fix for "br": the rule is too complex
$allRules['br'] = array(
0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))',
1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))',
2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))',
3 => 'fmod($n,1000000)==0&&$n!=0',
);
if (preg_match('/\d+/', $xml->version['number'], $matches)) {
$revision = $matches[0];
} else {
$revision = -1;
}
echo "<?php\n";
echo <<<EOD
/**
* Plural rules.
*
* This file is automatically generated by the "yiic locale/plural" command under the "build" folder.
* Do not modify it directly.
*
* The original plural rule data used for generating this file has the following copyright terms:
*
* Copyright © 1991-2007 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
*
* @revision $revision (of the original plural file)
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
EOD;
echo "\nreturn " . var_export($allRules, true) . ';';
}
}

2
docs/code_style.md

@ -204,7 +204,7 @@ doIt('a', array(
~~~
if ($event === null) {
return new Event($this);
return new Event();
} elseif ($event instanceof CoolEvent) {
return $event->instance();
} else {

40
framework/YiiBase.php

@ -192,6 +192,7 @@ class YiiBase
* @param boolean $throwException whether to throw an exception if the given alias is invalid.
* If this is false and an invalid alias is given, false will be returned by this method.
* @return string|boolean path corresponding to the alias, false if the root alias is not previously registered.
* @throws InvalidParamException if the alias is invalid while $throwException is true.
* @see setAlias
*/
public static function getAlias($alias, $throwException = true)
@ -231,6 +232,7 @@ class YiiBase
* - a URL (e.g. `http://www.yiiframework.com`)
* - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the
* actual path first by calling [[getAlias]].
*
* @throws Exception if $path is an invalid alias
* @see getAlias
*/
@ -238,7 +240,7 @@ class YiiBase
{
if ($path === null) {
unset(self::$aliases[$alias]);
} elseif ($path[0] !== '@') {
} elseif (strncmp($path, '@', 1)) {
self::$aliases[$alias] = rtrim($path, '\\/');
} else {
self::$aliases[$alias] = static::getAlias($path);
@ -504,20 +506,28 @@ class YiiBase
/**
* Translates a message to the specified language.
* This method supports choice format (see {@link CChoiceFormat}),
* i.e., the message returned will be chosen from a few candidates according to the given
* number value. This feature is mainly used to solve plural format issue in case
* a message has different plural forms in some languages.
* @param string $message the original message
* @param array $params parameters to be applied to the message using <code>strtr</code>.
* The first parameter can be a number without key.
* And in this case, the method will call {@link CChoiceFormat::format} to choose
* an appropriate message translation.
* You can pass parameter for {@link CChoiceFormat::format}
* or plural forms format without wrapping it with array.
* @param string $language the target language. If null (default), the {@link CApplication::getLanguage application language} will be used.
* @return string the translated message
* @see CMessageSource
*
* The translation will be conducted according to the message category and the target language.
* To specify the category of the message, prefix the message with the category name and separate it
* with "|". For example, "app|hello world". If the category is not specified, the default category "app"
* will be used. The actual message translation is done by a [[\yii\i18n\MessageSource|message source]].
*
* In case when a translated message has different plural forms (separated by "|"), this method
* will also attempt to choose an appropriate one according to a given numeric value which is
* specified as the first parameter (indexed by 0) in `$params`.
*
* For example, if a translated message is "I have an apple.|I have {n} apples.", and the first
* parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}"
* will be replaced with the given number.
*
* For more details on how plural rules are applied, please refer to:
* [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]]
*
* @param string $message the message to be translated.
* @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
* @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current
* [[\yii\base\Application::language|application language]] will be used.
* @return string the translated message.
*/
public static function t($message, $params = array(), $language = null)
{

4
framework/base/Application.php

@ -78,7 +78,7 @@ class Application extends Module
*/
public $preload = array();
/**
* @var Controller the currently active controller instance
* @var \yii\web\Controller|\yii\console\Controller the currently active controller instance
*/
public $controller;
/**
@ -355,7 +355,7 @@ class Application extends Module
/**
* Returns the request component.
* @return Request the request component
* @return \yii\web\Request|\yii\console\Request the request component
*/
public function getRequest()
{

5
framework/base/Component.php

@ -422,7 +422,10 @@ class Component extends \yii\base\Object
$this->ensureBehaviors();
if (isset($this->_e[$name]) && $this->_e[$name]->getCount()) {
if ($event === null) {
$event = new Event($this);
$event = new Event;
}
if ($event->sender === null) {
$event->sender = $this;
}
$event->handled = false;
$event->name = $name;

122
framework/base/Controller.php

@ -14,9 +14,6 @@ use yii\helpers\StringHelper;
/**
* Controller is the base class for classes containing controller logic.
*
* @property string $route the route (module ID, controller ID and action ID) of the current request.
* @property string $uniqueId the controller ID that is prefixed with the module ID (if any).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
@ -50,6 +47,11 @@ class Controller extends Component
* by [[run()]] when it is called by [[Application]] to run an action.
*/
public $action;
/**
* @var View the view object that can be used to render views or view files.
*/
private $_view;
/**
* @param string $id the ID of this controller
@ -138,7 +140,7 @@ class Controller extends Component
} elseif ($pos > 0) {
return $this->module->runAction($route, $params);
} else {
return \Yii::$app->runAction(ltrim($route, '/'), $params);
return Yii::$app->runAction(ltrim($route, '/'), $params);
}
}
@ -296,6 +298,37 @@ class Controller extends Component
/**
* Renders a view and applies layout if available.
*
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* To determine which layout should be applied, the following two steps are conducted:
*
* 1. In the first step, it determines the layout name and the context module:
*
* - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
* - If [[layout]] is null, search through all ancestor modules of this controller and find the first
* module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
* are used as the layout name and the context module, respectively. If such a module is not found
* or the corresponding layout is not a string, it will return false, meaning no applicable layout.
*
* 2. In the second step, it determines the actual layout file according to the previously found layout name
* and context module. The layout name can be
*
* - a path alias (e.g. "@app/views/layouts/main");
* - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
* [[Module::viewPath|view path]] of the context module.
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* These parameters will not be available in the layout.
@ -304,10 +337,11 @@ class Controller extends Component
*/
public function render($view, $params = array())
{
$output = Yii::$app->getView()->render($view, $params, $this);
$viewFile = $this->findViewFile($view);
$output = $this->getView()->renderFile($viewFile, $params, $this);
$layoutFile = $this->findLayoutFile();
if ($layoutFile !== false) {
return Yii::$app->getView()->renderFile($layoutFile, array('content' => $output), $this);
return $this->getView()->renderFile($layoutFile, array('content' => $output), $this);
} else {
return $output;
}
@ -316,14 +350,14 @@ class Controller extends Component
/**
* Renders a view.
* This method differs from [[render()]] in that it does not apply any layout.
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
* @throws InvalidParamException if the view file does not exist.
*/
public function renderPartial($view, $params = array())
{
return Yii::$app->getView()->render($view, $params, $this);
return $this->getView()->render($view, $params, $this);
}
/**
@ -335,7 +369,30 @@ class Controller extends Component
*/
public function renderFile($file, $params = array())
{
return Yii::$app->getView()->renderFile($file, $params, $this);
return $this->getView()->renderFile($file, $params, $this);
}
/**
* Returns the view object that can be used to render views or view files.
* The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
* this view object to implement the actual view rendering.
* @return View the view object that can be used to render views or view files.
*/
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
}
return $this->_view;
}
/**
* Sets the view object to be used by this controller.
* @param View $view the view object that can be used to render views or view files.
*/
public function setView($view)
{
$this->_view = $view;
}
/**
@ -350,30 +407,33 @@ class Controller extends Component
}
/**
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @return string the view file path. Note that the file may not exist.
*/
protected function findViewFile($view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0) {
// e.g. "/site/index"
$file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
$file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
}
return FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
}
/**
* Finds the applicable layout file.
*
* This method locates an applicable layout file via two steps.
*
* In the first step, it determines the layout name and the context module:
*
* - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
* - If [[layout]] is null, search through all ancestor modules of this controller and find the first
* module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
* are used as the layout name and the context module, respectively. If such a module is not found
* or the corresponding layout is not a string, it will return false, meaning no applicable layout.
*
* In the second step, it determines the actual layout file according to the previously found layout name
* and context module. The layout name can be
*
* - a path alias (e.g. "@app/views/layouts/main");
* - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
* looked for under the [[Application::layoutPath|layout path]] of the application;
* - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
* [[Module::viewPath|view path]] of the context module.
*
* If the layout name does not contain a file extension, it will use the default one `.php`.
*
* @return string|boolean the layout file path, or false if layout is not needed.
* Please refer to [[render()]] on how to specify this parameter.
* @throws InvalidParamException if an invalid path alias is used to specify the layout
*/
protected function findLayoutFile()

8
framework/base/Dictionary.php

@ -148,7 +148,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
* Defaults to false, meaning all items in the dictionary will be cleared directly
* without calling [[remove]].
*/
public function clear($safeClear = false)
public function removeAll($safeClear = false)
{
if ($safeClear) {
foreach (array_keys($this->_d) as $key) {
@ -164,7 +164,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
* @param mixed $key the key
* @return boolean whether the dictionary contains an item with the specified key
*/
public function contains($key)
public function has($key)
{
return isset($this->_d[$key]) || array_key_exists($key, $this->_d);
}
@ -188,7 +188,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
{
if (is_array($data) || $data instanceof \Traversable) {
if ($this->_d !== array()) {
$this->clear();
$this->removeAll();
}
if ($data instanceof self) {
$data = $data->_d;
@ -252,7 +252,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function offsetExists($offset)
{
return $this->contains($offset);
return $this->has($offset);
}
/**

10
framework/base/ErrorHandler.php

@ -16,8 +16,6 @@ namespace yii\base;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
use yii\helpers\VarDumper;
class ErrorHandler extends Component
{
/**
@ -63,10 +61,10 @@ class ErrorHandler extends Component
$this->clearOutput();
}
$this->render($exception);
$this->renderException($exception);
}
protected function render($exception)
protected function renderException($exception)
{
if ($this->errorAction !== null) {
\Yii::$app->runAction($this->errorAction);
@ -84,7 +82,7 @@ class ErrorHandler extends Component
} else {
$viewName = $this->exceptionView;
}
echo $view->render($viewName, array(
echo $view->renderFile($viewName, array(
'exception' => $exception,
), $this);
}
@ -255,7 +253,7 @@ class ErrorHandler extends Component
{
$view = new View;
$name = !YII_DEBUG || $exception instanceof HttpException ? $this->errorView : $this->exceptionView;
echo $view->render($name, array(
echo $view->renderFile($name, array(
'exception' => $exception,
), $this);
}

19
framework/base/Event.php

@ -28,7 +28,8 @@ class Event extends \yii\base\Object
*/
public $name;
/**
* @var object the sender of this event
* @var object the sender of this event. If not set, this property will be
* set as the object whose "trigger()" method is called.
*/
public $sender;
/**
@ -38,21 +39,7 @@ class Event extends \yii\base\Object
*/
public $handled = false;
/**
* @var mixed extra data associated with the event.
* @var mixed extra custom data associated with the event.
*/
public $data;
/**
* Constructor.
*
* @param mixed $sender sender of the event
* @param mixed $data extra data associated with the event
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($sender = null, $data = null, $config = array())
{
$this->sender = $sender;
$this->data = $data;
parent::__construct($config);
}
}

5
framework/base/HttpException.php

@ -29,11 +29,12 @@ class HttpException extends UserException
* @param integer $status HTTP status code, such as 404, 500, etc.
* @param string $message error message
* @param integer $code error code
* @param \Exception $previous The previous exception used for the exception chaining.
*/
public function __construct($status, $message = null, $code = 0)
public function __construct($status, $message = null, $code = 0, \Exception $previous = null)
{
$this->statusCode = $status;
parent::__construct($message, $code);
parent::__construct($message, $code, $previous);
}
/**

16
framework/base/Model.php

@ -258,7 +258,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
*/
public function beforeValidate()
{
$event = new ModelEvent($this);
$event = new ModelEvent;
$this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
return $event->isValid;
}
@ -329,7 +329,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
foreach ($this->rules() as $rule) {
if ($rule instanceof Validator) {
$validators->add($rule);
} elseif (isset($rule[0], $rule[1])) { // attributes, validator type
} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
$validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2));
$validators->add($validator);
} else {
@ -429,9 +429,9 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
return array();
} else {
$errors = array();
foreach ($this->_errors as $errors) {
if (isset($errors[0])) {
$errors[] = $errors[0];
foreach ($this->_errors as $attributeErrors) {
if (isset($attributeErrors[0])) {
$errors[] = $attributeErrors[0];
}
}
}
@ -541,7 +541,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
public function onUnsafeAttribute($name, $value)
{
if (YII_DEBUG) {
\Yii::info("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __CLASS__);
\Yii::info("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
}
}
@ -656,13 +656,13 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
}
/**
* Unsets the element at the specified offset.
* Sets the element value at the specified offset to null.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `unset($model[$offset])`.
* @param mixed $offset the offset to unset element
*/
public function offsetUnset($offset)
{
unset($this->$offset);
$this->$offset = null;
}
}

14
framework/base/Module.php

@ -346,7 +346,7 @@ abstract class Module extends Component
if ($this->_modules[$id] instanceof Module) {
return $this->_modules[$id];
} elseif ($load) {
Yii::trace("Loading module: $id", __CLASS__);
Yii::trace("Loading module: $id", __METHOD__);
return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
}
}
@ -411,7 +411,7 @@ abstract class Module extends Component
* array(
* 'comment' => array(
* 'class' => 'app\modules\CommentModule',
* 'connectionID' => 'db',
* 'db' => 'db',
* ),
* 'booking' => array(
* 'class' => 'app\modules\BookingModule',
@ -452,7 +452,7 @@ abstract class Module extends Component
if ($this->_components[$id] instanceof Component) {
return $this->_components[$id];
} elseif ($load) {
Yii::trace("Loading component: $id", __CLASS__);
Yii::trace("Loading component: $id", __METHOD__);
return $this->_components[$id] = Yii::createObject($this->_components[$id]);
}
}
@ -521,7 +521,7 @@ abstract class Module extends Component
* ),
* 'cache' => array(
* 'class' => 'yii\caching\DbCache',
* 'connectionID' => 'db',
* 'db' => 'db',
* ),
* )
* ~~~
@ -614,6 +614,12 @@ abstract class Module extends Component
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
$controller = new $className($id, $this);
} elseif (YII_DEBUG) {
if (!class_exists($className, false)) {
throw new InvalidConfigException("Class file name does not match class name: $className.");
} elseif (!is_subclass_of($className, '\yii\base\Controller')) {
throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
}
}
}
}

6
framework/base/Vector.php

@ -191,7 +191,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
* Defaults to false, meaning all items in the vector will be cleared directly
* without calling [[removeAt]].
*/
public function clear($safeClear = false)
public function removeAll($safeClear = false)
{
if ($safeClear) {
for ($i = $this->_c - 1; $i >= 0; --$i) {
@ -209,7 +209,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
* @param mixed $item the item
* @return boolean whether the vector contains the item
*/
public function contains($item)
public function has($item)
{
return $this->indexOf($item) >= 0;
}
@ -246,7 +246,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
{
if (is_array($data) || $data instanceof \Traversable) {
if ($this->_c > 0) {
$this->clear();
$this->removeAll();
}
if ($data instanceof self) {
$data = $data->_d;

93
framework/base/View.php

@ -79,22 +79,29 @@ class View extends Component
/**
* Renders a view.
*
* This method will call [[findViewFile()]] to convert the view name into the corresponding view
* file path, and it will then call [[renderFile()]] to render the view.
* This method delegates the call to the [[context]] object:
*
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify this parameter.
* - If [[context]] is a controller, the [[Controller::renderPartial()]] method will be called;
* - If [[context]] is a widget, the [[Widget::render()]] method will be called;
* - Otherwise, an InvalidCallException exception will be thrown.
*
* @param string $view the view name. Please refer to [[Controller::findViewFile()]]
* and [[Widget::findViewFile()]] on how to specify this parameter.
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
* @param object $context the context that the view should use for rendering the view. If null,
* existing [[context]] will be used.
* @return string the rendering result
* @throws InvalidCallException if [[context]] is neither a controller nor a widget.
* @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
* @see renderFile
* @see findViewFile
*/
public function render($view, $params = array(), $context = null)
public function render($view, $params = array())
{
$viewFile = $this->findViewFile($context, $view);
return $this->renderFile($viewFile, $params, $context);
if ($this->context instanceof Controller) {
return $this->context->renderPartial($view, $params);
} elseif ($this->context instanceof Widget) {
return $this->context->render($view, $params);
} else {
throw new InvalidCallException('View::render() is not supported for the current context.');
}
}
/**
@ -213,49 +220,6 @@ class View extends Component
}
/**
* Finds the view file based on the given view name.
*
* A view name can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
* active module.
* - relative path (e.g. "index"): the actual view file will be looked for under [[Controller::viewPath|viewPath]]
* of the context object, assuming the context is either a [[Controller]] or a [[Widget]].
*
* If the view name does not contain a file extension, it will use the default one `.php`.
*
* @param object $context the view context object
* @param string $view the view name or the path alias of the view file.
* @return string the view file path. Note that the file may not exist.
* @throws InvalidParamException if the view file is an invalid path alias or the context cannot be
* used to determine the actual view file corresponding to the specified view.
*/
protected function findViewFile($context, $view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0) {
// e.g. "/site/index"
$file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif ($context instanceof Controller || $context instanceof Widget) {
/** @var $context Controller|Widget */
$file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
} else {
throw new InvalidParamException("Unable to resolve the view file for '$view'.");
}
return FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
}
/**
* Creates a widget.
* This method will use [[Yii::createObject()]] to create the widget.
* @param string $class the widget class name or path alias
@ -265,7 +229,10 @@ class View extends Component
public function createWidget($class, $properties = array())
{
$properties['class'] = $class;
return Yii::createObject($properties, $this->context);
if (!isset($properties['view'])) {
$properties['view'] = $this;
}
return Yii::createObject($properties, $this);
}
/**
@ -341,7 +308,6 @@ class View extends Component
return $this->beginWidget('yii\widgets\Clip', array(
'id' => $id,
'renderInPlace' => $renderInPlace,
'view' => $this,
));
}
@ -355,17 +321,25 @@ class View extends Component
/**
* Begins the rendering of content that is to be decorated by the specified view.
* @param string $view the name of the view that will be used to decorate the content enclosed by this widget.
* Please refer to [[View::findViewFile()]] on how to set this property.
* This method can be used to implement nested layout. For example, a layout can be embedded
* in another layout file specified as '@app/view/layouts/base' like the following:
*
* ~~~
* <?php $this->beginContent('@app/view/layouts/base'); ?>
* ...layout content here...
* <?php $this->endContent(); ?>
* ~~~
*
* @param string $viewFile the view file that will be used to decorate the content enclosed by this widget.
* This can be specified as either the view file path or path alias.
* @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
* @return \yii\widgets\ContentDecorator the ContentDecorator widget instance
* @see \yii\widgets\ContentDecorator
*/
public function beginContent($view, $params = array())
public function beginContent($viewFile, $params = array())
{
return $this->beginWidget('yii\widgets\ContentDecorator', array(
'view' => $this,
'viewName' => $view,
'viewFile' => $viewFile,
'params' => $params,
));
}
@ -400,7 +374,6 @@ class View extends Component
public function beginCache($id, $properties = array())
{
$properties['id'] = $id;
$properties['view'] = $this;
/** @var $cache \yii\widgets\FragmentCache */
$cache = $this->beginWidget('yii\widgets\FragmentCache', $properties);
if ($cache->getCachedContent() !== false) {

57
framework/base/Widget.php

@ -19,9 +19,11 @@ use yii\helpers\FileHelper;
class Widget extends Component
{
/**
* @var Widget|Controller the owner/creator of this widget. It could be either a widget or a controller.
* @var View the view object that is used to create this widget.
* This property is automatically set by [[View::createWidget()]].
* This property is required by [[render()]] and [[renderFile()]].
*/
public $owner;
public $view;
/**
* @var string id of the widget.
*/
@ -32,17 +34,6 @@ class Widget extends Component
private static $_counter = 0;
/**
* Constructor.
* @param Widget|Controller $owner owner/creator of this widget.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($owner, $config = array())
{
$this->owner = $owner;
parent::__construct($config);
}
/**
* Returns the ID of the widget.
* @param boolean $autoGenerate whether to generate an ID if it is not set previously
* @return string ID of the widget.
@ -73,6 +64,18 @@ class Widget extends Component
/**
* Renders a view.
* The view to be rendered can be specified in one of the following formats:
*
* - path alias (e.g. "@app/views/site/index");
* - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
* The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
* active module.
* - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
*
* If the view name does not contain a file extension, it will use the default one `.php`.
* @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
* @param array $params the parameters (name-value pairs) that should be made available in the view.
* @return string the rendering result.
@ -80,7 +83,7 @@ class Widget extends Component
*/
public function render($view, $params = array())
{
return Yii::$app->getView()->render($view, $params, $this);
return $this->view->render($view, $params, $this);
}
/**
@ -92,7 +95,7 @@ class Widget extends Component
*/
public function renderFile($file, $params = array())
{
return Yii::$app->getView()->renderFile($file, $params, $this);
return $this->view->renderFile($file, $params, $this);
}
/**
@ -106,4 +109,28 @@ class Widget extends Component
$class = new \ReflectionClass($className);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
/**
* Finds the view file based on the given view name.
* @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
* on how to specify this parameter.
* @return string the view file path. Note that the file may not exist.
*/
protected function findViewFile($view)
{
if (strncmp($view, '@', 1) === 0) {
// e.g. "@app/views/main"
$file = Yii::getAlias($view);
} elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0 && Yii::$app->controller !== null) {
// e.g. "/site/index"
$file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} else {
$file = $this->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
}
return FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
}
}

2
framework/caching/ChainedDependency.php

@ -57,7 +57,7 @@ class ChainedDependency extends Dependency
if (!$dependency instanceof Dependency) {
$dependency = \Yii::createObject($dependency);
}
$dependency->evalulateDependency();
$dependency->evaluateDependency();
}
}

159
framework/caching/DbCache.php

@ -7,6 +7,7 @@
namespace yii\caching;
use Yii;
use yii\base\InvalidConfigException;
use yii\db\Connection;
use yii\db\Query;
@ -14,30 +15,20 @@ use yii\db\Query;
/**
* DbCache implements a cache application component by storing cached data in a database.
*
* DbCache stores cache data in a DB table whose name is specified via [[cacheTableName]].
* For MySQL database, the table should be created beforehand as follows :
*
* ~~~
* CREATE TABLE tbl_cache (
* id char(128) NOT NULL,
* expire int(11) DEFAULT NULL,
* data LONGBLOB,
* PRIMARY KEY (id),
* KEY expire (expire)
* );
* ~~~
*
* You should replace `LONGBLOB` as follows if you are using a different DBMS:
*
* - PostgreSQL: `BYTEA`
* - SQLite, SQL server, Oracle: `BLOB`
*
* DbCache connects to the database via the DB connection specified in [[connectionID]]
* which must refer to a valid DB application component.
* By default, DbCache stores session data in a DB table named 'tbl_cache'. This table
* must be pre-created. The table name can be changed by setting [[cacheTable]].
*
* Please refer to [[Cache]] for common cache operations that are supported by DbCache.
*
* @property Connection $db The DB connection instance.
* The following example shows how you can configure the application to use DbCache:
*
* ~~~
* 'cache' => array(
* 'class' => 'yii\caching\DbCache',
* // 'db' => 'mydb',
* // 'cacheTable' => 'my_cache',
* )
* ~~~
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
@ -45,50 +36,56 @@ use yii\db\Query;
class DbCache extends Cache
{
/**
* @var string the ID of the [[Connection|DB connection]] application component. Defaults to 'db'.
* @var Connection|string the DB connection object or the application component ID of the DB connection.
* After the DbCache object is created, if you want to change this property, you should only assign it
* with a DB connection object.
*/
public $connectionID = 'db';
public $db = 'db';
/**
* @var string name of the DB table to store cache content. Defaults to 'tbl_cache'.
* The table must be created before using this cache component.
* @var string name of the DB table to store cache content.
* The table should be pre-created as follows:
*
* ~~~
* CREATE TABLE tbl_cache (
* id char(128) NOT NULL PRIMARY KEY,
* expire int(11),
* data BLOB
* );
* ~~~
*
* where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
* that can be used for some popular DBMS:
*
* - MySQL: LONGBLOB
* - PostgreSQL: BYTEA
* - MSSQL: BLOB
*
* When using DbCache in a production server, we recommend you create a DB index for the 'expire'
* column in the cache table to improve the performance.
*/
public $cacheTableName = 'tbl_cache';
public $cacheTable = 'tbl_cache';
/**
* @var integer the probability (parts per million) that garbage collection (GC) should be performed
* when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance.
* when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
* This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
**/
public $gcProbability = 100;
/**
* @var Connection the DB connection instance
*/
private $_db;
/**
* Returns the DB connection instance used for caching purpose.
* @return Connection the DB connection instance
* @throws InvalidConfigException if [[connectionID]] does not point to a valid application component.
* Initializes the DbCache component.
* This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
* @throws InvalidConfigException if [[db]] is invalid.
*/
public function getDb()
public function init()
{
if ($this->_db === null) {
$db = \Yii::$app->getComponent($this->connectionID);
if ($db instanceof Connection) {
$this->_db = $db;
} else {
throw new InvalidConfigException("DbCache::connectionID must refer to the ID of a DB application component.");
}
parent::init();
if (is_string($this->db)) {
$this->db = Yii::$app->getComponent($this->db);
}
return $this->_db;
if (!$this->db instanceof Connection) {
throw new InvalidConfigException("DbCache::db must be either a DB connection instance or the application component ID of a DB connection.");
}
/**
* Sets the DB connection used by the cache component.
* @param Connection $value the DB connection instance
*/
public function setDb($value)
{
$this->_db = $value;
}
/**
@ -101,17 +98,16 @@ class DbCache extends Cache
{
$query = new Query;
$query->select(array('data'))
->from($this->cacheTableName)
->where('id = :id AND (expire = 0 OR expire >' . time() . ')', array(':id' => $key));
$db = $this->getDb();
if ($db->enableQueryCache) {
->from($this->cacheTable)
->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', array(':id' => $key));
if ($this->db->enableQueryCache) {
// temporarily disable and re-enable query caching
$db->enableQueryCache = false;
$result = $query->createCommand($db)->queryScalar();
$db->enableQueryCache = true;
$this->db->enableQueryCache = false;
$result = $query->createCommand($this->db)->queryScalar();
$this->db->enableQueryCache = true;
return $result;
} else {
return $query->createCommand($db)->queryScalar();
return $query->createCommand($this->db)->queryScalar();
}
}
@ -127,17 +123,16 @@ class DbCache extends Cache
}
$query = new Query;
$query->select(array('id', 'data'))
->from($this->cacheTableName)
->from($this->cacheTable)
->where(array('id' => $keys))
->andWhere('(expire = 0 OR expire > ' . time() . ')');
->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')');
$db = $this->getDb();
if ($db->enableQueryCache) {
$db->enableQueryCache = false;
$rows = $query->createCommand($db)->queryAll();
$db->enableQueryCache = true;
if ($this->db->enableQueryCache) {
$this->db->enableQueryCache = false;
$rows = $query->createCommand($this->db)->queryAll();
$this->db->enableQueryCache = true;
} else {
$rows = $query->createCommand($db)->queryAll();
$rows = $query->createCommand($this->db)->queryAll();
}
$results = array();
@ -161,13 +156,13 @@ class DbCache extends Cache
*/
protected function setValue($key, $value, $expire)
{
$command = $this->getDb()->createCommand();
$command->update($this->cacheTableName, array(
$command = $this->db->createCommand()
->update($this->cacheTable, array(
'expire' => $expire > 0 ? $expire + time() : 0,
'data' => array($value, \PDO::PARAM_LOB),
), array(
'id' => $key,
));;
));
if ($command->execute()) {
$this->gc();
@ -196,14 +191,13 @@ class DbCache extends Cache
$expire = 0;
}
$command = $this->getDb()->createCommand();
$command->insert($this->cacheTableName, array(
try {
$this->db->createCommand()
->insert($this->cacheTable, array(
'id' => $key,
'expire' => $expire,
'data' => array($value, \PDO::PARAM_LOB),
));
try {
$command->execute();
))->execute();
return true;
} catch (\Exception $e) {
return false;
@ -218,8 +212,9 @@ class DbCache extends Cache
*/
protected function deleteValue($key)
{
$command = $this->getDb()->createCommand();
$command->delete($this->cacheTableName, array('id' => $key))->execute();
$this->db->createCommand()
->delete($this->cacheTable, array('id' => $key))
->execute();
return true;
}
@ -231,8 +226,9 @@ class DbCache extends Cache
public function gc($force = false)
{
if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
$command = $this->getDb()->createCommand();
$command->delete($this->cacheTableName, 'expire > 0 AND expire < ' . time())->execute();
$this->db->createCommand()
->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
->execute();
}
}
@ -243,8 +239,9 @@ class DbCache extends Cache
*/
protected function flushValues()
{
$command = $this->getDb()->createCommand();
$command->delete($this->cacheTableName)->execute();
$this->db->createCommand()
->delete($this->cacheTable)
->execute();
return true;
}
}

69
framework/caching/DbDependency.php

@ -23,41 +23,28 @@ use yii\db\Connection;
class DbDependency extends Dependency
{
/**
* @var string the ID of the [[Connection|DB connection]] application component. Defaults to 'db'.
* @var string the application component ID of the DB connection.
*/
public $connectionID = 'db';
public $db = 'db';
/**
* @var string the SQL query whose result is used to determine if the dependency has been changed.
* Only the first row of the query result will be used.
* Only the first row of the query result will be used. This property must be always set, otherwise
* an exception would be raised.
*/
public $sql;
/**
* @var array the parameters (name=>value) to be bound to the SQL statement specified by [[sql]].
*/
public $params;
public $params = array();
/**
* Constructor.
* @param string $sql the SQL query whose result is used to determine if the dependency has been changed.
* @param array $params the parameters (name=>value) to be bound to the SQL statement specified by [[sql]].
* @param array $config name-value pairs that will be used to initialize the object properties
* Initializes the database dependency object.
*/
public function __construct($sql, $params = array(), $config = array())
public function init()
{
$this->sql = $sql;
$this->params = $params;
parent::__construct($config);
if ($this->sql === null) {
throw new InvalidConfigException('DbDependency::sql must be set.');
}
/**
* PHP sleep magic method.
* This method ensures that the database instance is set null because it contains resource handles.
* @return array
*/
public function __sleep()
{
$this->_db = null;
return array_keys((array)$this);
}
/**
@ -67,7 +54,11 @@ class DbDependency extends Dependency
*/
protected function generateDependencyData()
{
$db = $this->getDb();
$db = Yii::$app->getComponent($this->db);
if (!$db instanceof Connection) {
throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection.");
}
if ($db->enableQueryCache) {
// temporarily disable and re-enable query caching
$db->enableQueryCache = false;
@ -78,36 +69,4 @@ class DbDependency extends Dependency
}
return $result;
}
/**
* @var Connection the DB connection instance
*/
private $_db;
/**
* Returns the DB connection instance used for caching purpose.
* @return Connection the DB connection instance
* @throws InvalidConfigException if [[connectionID]] does not point to a valid application component.
*/
public function getDb()
{
if ($this->_db === null) {
$db = Yii::$app->getComponent($this->connectionID);
if ($db instanceof Connection) {
$this->_db = $db;
} else {
throw new InvalidConfigException("DbCacheDependency::connectionID must refer to the ID of a DB application component.");
}
}
return $this->_db;
}
/**
* Sets the DB connection used by the cache component.
* @param Connection $value the DB connection instance
*/
public function setDb($value)
{
$this->_db = $value;
}
}

13
framework/caching/ExpressionDependency.php

@ -22,18 +22,7 @@ class ExpressionDependency extends Dependency
/**
* @var string the PHP expression whose result is used to determine the dependency.
*/
public $expression;
/**
* Constructor.
* @param string $expression the PHP expression whose result is used to determine the dependency.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($expression = 'true', $config = array())
{
$this->expression = $expression;
parent::__construct($config);
}
public $expression = 'true';
/**
* Generates the data needed to determine if dependency has been changed.

16
framework/caching/FileDependency.php

@ -7,6 +7,8 @@
namespace yii\caching;
use yii\base\InvalidConfigException;
/**
* FileDependency represents a dependency based on a file's last modification time.
*
@ -20,19 +22,19 @@ class FileDependency extends Dependency
{
/**
* @var string the name of the file whose last modification time is used to
* check if the dependency has been changed.
* check if the dependency has been changed. This property must be always set,
* otherwise an exception would be raised.
*/
public $fileName;
/**
* Constructor.
* @param string $fileName name of the file whose change is to be checked.
* @param array $config name-value pairs that will be used to initialize the object properties
* Initializes the database dependency object.
*/
public function __construct($fileName = null, $config = array())
public function init()
{
$this->fileName = $fileName;
parent::__construct($config);
if ($this->file === null) {
throw new InvalidConfigException('FileDependency::fileName must be set.');
}
}
/**

7
framework/console/Controller.php

@ -24,7 +24,6 @@ use yii\base\InvalidRouteException;
* ~~~
*
* @author Qiang Xue <qiang.xue@gmail.com>
*
* @since 2.0
*/
class Controller extends \yii\base\Controller
@ -135,9 +134,13 @@ class Controller extends \yii\base\Controller
/**
* Returns the names of the global options for this command.
* A global option requires the existence of a global member variable whose
* A global option requires the existence of a public member variable whose
* name is the option name.
* Child classes may override this method to specify possible global options.
*
* Note that the values setting via global options are not available
* until [[beforeAction()]] is being called.
*
* @return array the names of the global options for this command.
*/
public function globalOptions()

4
framework/console/controllers/HelpController.php

@ -9,9 +9,9 @@ namespace yii\console\controllers;
use Yii;
use yii\base\Application;
use yii\console\Exception;
use yii\base\InlineAction;
use yii\console\Controller;
use yii\console\Exception;
use yii\console\Request;
use yii\helpers\StringHelper;
@ -128,7 +128,7 @@ class HelpController extends Controller
$files = scandir($module->getControllerPath());
foreach ($files as $file) {
if(strcmp(substr($file,-14),'Controller.php') === 0 && is_file($file)) {
if (strcmp(substr($file, -14), 'Controller.php') === 0) {
$commands[] = $prefix . lcfirst(substr(basename($file), 0, -14));
}
}

47
framework/console/controllers/MigrateController.php

@ -26,9 +26,10 @@ use yii\helpers\ArrayHelper;
* This command provides support for tracking the migration history, upgrading
* or downloading with migrations, and creating new migration skeletons.
*
* The migration history is stored in a database table named as [[migrationTable]].
* The table will be automatically created the first this command is executed.
* You may also manually create it with the following structure:
* The migration history is stored in a database table named
* as [[migrationTable]]. The table will be automatically created the first time
* this command is executed, if it does not exist. You may also manually
* create it as follows:
*
* ~~~
* CREATE TABLE tbl_migration (
@ -74,11 +75,6 @@ class MigrateController extends Controller
*/
public $migrationTable = 'tbl_migration';
/**
* @var string the component ID that specifies the database connection for
* storing migration information.
*/
public $connectionID = 'db';
/**
* @var string the template file for generating new migrations.
* This can be either a path alias (e.g. "@app/migrations/template.php")
* or a file path.
@ -89,10 +85,10 @@ class MigrateController extends Controller
*/
public $interactive = true;
/**
* @var Connection the DB connection used for storing migration history.
* @see connectionID
* @var Connection|string the DB connection object or the application
* component ID of the DB connection.
*/
public $db;
public $db = 'db';
/**
* Returns the names of the global options for this command.
@ -100,7 +96,7 @@ class MigrateController extends Controller
*/
public function globalOptions()
{
return array('migrationPath', 'migrationTable', 'connectionID', 'templateFile', 'interactive');
return array('migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive');
}
/**
@ -119,9 +115,11 @@ class MigrateController extends Controller
}
$this->migrationPath = $path;
$this->db = Yii::$app->getComponent($this->connectionID);
if (is_string($this->db)) {
$this->db = Yii::$app->getComponent($this->db);
}
if (!$this->db instanceof Connection) {
throw new Exception("Invalid DB connection \"{$this->connectionID}\".");
throw new Exception("The 'db' option must refer to the application component ID of a DB connection.");
}
$version = Yii::getVersion();
@ -277,7 +275,7 @@ class MigrateController extends Controller
}
/**
* Upgrades or downgrades till the specified version of migration.
* Upgrades or downgrades till the specified version.
*
* This command will first revert the specified migrations, and then apply
* them again. For example,
@ -564,25 +562,6 @@ class MigrateController extends Controller
));
}
/**
* @return Connection the database connection that is used to store the migration history.
* @throws Exception if the database connection ID is invalid.
*/
protected function getDb()
{
if ($this->db !== null) {
return $this->db;
} else {
$this->db = Yii::$app->getComponent($this->connectionID);
if ($this->db instanceof Connection) {
return $this->db;
} else {
throw new Exception("Invalid DB connection: {$this->connectionID}.");
}
}
}
/**
* Returns the migration history.
* @param integer $limit the maximum number of records in the history to be returned

103
framework/db/ActiveRecord.php

@ -191,15 +191,12 @@ class ActiveRecord extends Model
*/
public static function updateAllCounters($counters, $condition = '', $params = array())
{
$db = static::getDb();
$n = 0;
foreach ($counters as $name => $value) {
$quotedName = $db->quoteColumnName($name);
$counters[$name] = new Expression("$quotedName+:bp{$n}");
$params[":bp{$n}"] = $value;
$counters[$name] = new Expression("[[$name]]+:bp{$n}", array(":bp{$n}" => $value));
$n++;
}
$command = $db->createCommand();
$command = static::getDb()->createCommand();
$command->update(static::tableName(), $counters, $condition, $params);
return $command->execute();
}
@ -280,6 +277,34 @@ class ActiveRecord extends Model
}
/**
* Returns the name of the column that stores the lock version for implementing optimistic locking.
*
* Optimistic locking allows multiple users to access the same record for edits and avoids
* potential conflicts. In case when a user attempts to save the record upon some staled data
* (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
* and the update or deletion is skipped.
*
* Optimized locking is only supported by [[update()]] and [[delete()]].
*
* To use optimized locking:
*
* 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
* Override this method to return the name of this column.
* 2. In the Web form that collects the user input, add a hidden field that stores
* the lock version of the recording being updated.
* 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
* and implement necessary business logic (e.g. merging the changes, prompting stated data)
* to resolve the conflict.
*
* @return string the column name that stores the lock version of a table row.
* If null is returned (default implemented), optimistic locking will not be supported.
*/
public function optimisticLock()
{
return null;
}
/**
* PHP getter magic method.
* This method is overridden so that attributes and related objects can be accessed like properties.
* @param string $name property name
@ -530,8 +555,8 @@ class ActiveRecord extends Model
*/
public function isAttributeChanged($name)
{
if (isset($this->_attribute[$name], $this->_oldAttributes[$name])) {
return $this->_attribute[$name] !== $this->_oldAttributes[$name];
if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
return $this->_attributes[$name] !== $this->_oldAttributes[$name];
} else {
return isset($this->_attributes[$name]) || isset($this->_oldAttributes);
}
@ -590,7 +615,11 @@ class ActiveRecord extends Model
*/
public function save($runValidation = true, $attributes = null)
{
return $this->getIsNewRecord() ? $this->insert($runValidation, $attributes) : $this->update($runValidation, $attributes);
if ($this->getIsNewRecord()) {
return $this->insert($runValidation, $attributes);
} else {
return $this->update($runValidation, $attributes) !== false;
}
}
/**
@ -692,11 +721,26 @@ class ActiveRecord extends Model
* $customer->update();
* ~~~
*
* Note that it is possible the update does not affect any row in the table.
* In this case, this method will return 0. For this reason, you should use the following
* code to check if update() is successful or not:
*
* ~~~
* if ($this->update() !== false) {
* // update successful
* } else {
* // update failed
* }
* ~~~
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be inserted into the database.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the attributes are valid and the record is updated successfully.
* @return integer|boolean the number of rows affected, or false if validation fails
* or [[beforeSave()]] stops the updating process.
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
* being updated is outdated.
*/
public function update($runValidation = true, $attributes = null)
{
@ -706,15 +750,31 @@ class ActiveRecord extends Model
if ($this->beforeSave(false)) {
$values = $this->getDirtyAttributes($attributes);
if ($values !== array()) {
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
if (!isset($values[$lock])) {
$values[$lock] = $this->$lock + 1;
}
$condition[$lock] = $this->$lock;
}
// We do not check the return value of updateAll() because it's possible
// that the UPDATE statement doesn't change anything and thus returns 0.
$this->updateAll($values, $this->getOldPrimaryKey(true));
$rows = $this->updateAll($values, $condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
$this->afterSave(false);
return $rows;
} else {
return 0;
}
return true;
} else {
return false;
}
@ -763,17 +823,28 @@ class ActiveRecord extends Model
* In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
* will be raised by the corresponding methods.
*
* @return boolean whether the deletion is successful.
* @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
* Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
* being deleted is outdated.
*/
public function delete()
{
if ($this->beforeDelete()) {
// we do not check the return value of deleteAll() because it's possible
// the record is already deleted in the database and thus the method will return 0
$this->deleteAll($this->getPrimaryKey(true));
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock();
if ($lock !== null) {
$condition[$lock] = $this->$lock;
}
$rows = $this->deleteAll($condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being deleted is outdated.');
}
$this->_oldAttributes = null;
$this->afterDelete();
return true;
return $rows;
} else {
return false;
}
@ -847,7 +918,7 @@ class ActiveRecord extends Model
*/
public function beforeSave($insert)
{
$event = new ModelEvent($this);
$event = new ModelEvent;
$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
return $event->isValid;
}
@ -887,7 +958,7 @@ class ActiveRecord extends Model
*/
public function beforeDelete()
{
$event = new ModelEvent($this);
$event = new ModelEvent;
$this->trigger(self::EVENT_BEFORE_DELETE, $event);
return $event->isValid;
}

98
framework/db/Command.php

@ -7,7 +7,9 @@
namespace yii\db;
use Yii;
use yii\base\NotSupportedException;
use yii\caching\Cache;
/**
* Command represents a SQL statement to be executed against a database.
@ -82,39 +84,51 @@ class Command extends \yii\base\Component
/**
* Specifies the SQL statement to be executed.
* Any previous execution will be terminated or cancelled.
* The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well.
* @param string $sql the SQL statement to be set.
* @return Command this command instance
*/
public function setSql($sql)
{
if ($sql !== $this->_sql) {
if ($this->db->enableAutoQuoting && $sql != '') {
$sql = $this->expandSql($sql);
}
$this->cancel();
$this->_sql = $sql;
$this->_sql = $this->db->quoteSql($sql);
$this->_params = array();
}
return $this;
}
/**
* Expands a SQL statement by quoting table and column names and replacing table prefixes.
* @param string $sql the SQL to be expanded
* @return string the expanded SQL
* Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
* Note that the return value of this method should mainly be used for logging purpose.
* It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
* @return string the raw SQL
*/
protected function expandSql($sql)
public function getRawSql()
{
$db = $this->db;
return preg_replace_callback('/(\\{\\{(.*?)\\}\\}|\\[\\[(.*?)\\]\\])/', function($matches) use($db) {
if (isset($matches[3])) {
return $db->quoteColumnName($matches[3]);
if ($this->_params === array()) {
return $this->_sql;
} else {
$name = str_replace('%', $db->tablePrefix, $matches[2]);
return $db->quoteTableName($name);
$params = array();
foreach ($this->_params as $name => $value) {
if (is_string($value)) {
$params[$name] = $this->db->quoteValue($value);
} elseif ($value === null) {
$params[$name] = 'NULL';
} else {
$params[$name] = $value;
}
}
if (isset($params[1])) {
$sql = '';
foreach (explode('?', $this->_sql) as $i => $part) {
$sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
}
return $sql;
} else {
return strtr($this->_sql, $params);
}
}
}, $sql);
}
/**
@ -132,7 +146,7 @@ class Command extends \yii\base\Component
try {
$this->pdoStatement = $this->db->pdo->prepare($sql);
} catch (\Exception $e) {
\Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__);
Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($e->getMessage(), $errorInfo, (int)$e->getCode());
}
@ -241,6 +255,7 @@ class Command extends \yii\base\Component
'boolean' => \PDO::PARAM_BOOL,
'integer' => \PDO::PARAM_INT,
'string' => \PDO::PARAM_STR,
'resource' => \PDO::PARAM_LOB,
'NULL' => \PDO::PARAM_NULL,
);
$type = gettype($data);
@ -258,21 +273,18 @@ class Command extends \yii\base\Component
{
$sql = $this->getSql();
if ($this->_params === array()) {
$paramLog = '';
} else {
$paramLog = "\nParameters: " . var_export($this->_params, true);
}
$rawSql = $this->getRawSql();
\Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__);
Yii::trace("Executing SQL: $rawSql", __METHOD__);
if ($sql == '') {
return 0;
}
try {
$token = "SQL: $sql";
if ($this->db->enableProfiling) {
\Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::beginProfile($token, __METHOD__);
}
$this->prepare();
@ -280,16 +292,16 @@ class Command extends \yii\base\Component
$n = $this->pdoStatement->rowCount();
if ($this->db->enableProfiling) {
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
return $n;
} catch (\Exception $e) {
if ($this->db->enableProfiling) {
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
$message = $e->getMessage();
\Yii::error("$message\nFailed to execute SQL: {$sql}{$paramLog}", __CLASS__);
Yii::error("$message\nFailed to execute SQL: $rawSql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int)$e->getCode());
@ -375,36 +387,32 @@ class Command extends \yii\base\Component
{
$db = $this->db;
$sql = $this->getSql();
if ($this->_params === array()) {
$paramLog = '';
} else {
$paramLog = "\nParameters: " . var_export($this->_params, true);
}
$rawSql = $this->getRawSql();
\Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
Yii::trace("Querying SQL: $rawSql", __METHOD__);
/** @var $cache \yii\caching\Cache */
if ($db->enableQueryCache && $method !== '') {
$cache = \Yii::$app->getComponent($db->queryCacheID);
$cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache;
}
if (isset($cache)) {
if (isset($cache) && $cache instanceof Cache) {
$cacheKey = $cache->buildKey(array(
__CLASS__,
$db->dsn,
$db->username,
$sql,
$paramLog,
$rawSql,
));
if (($result = $cache->get($cacheKey)) !== false) {
\Yii::trace('Query result found in cache', __CLASS__);
Yii::trace('Query result served from cache', __METHOD__);
return $result;
}
}
try {
$token = "SQL: $sql";
if ($db->enableProfiling) {
\Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::beginProfile($token, __METHOD__);
}
$this->prepare();
@ -421,21 +429,21 @@ class Command extends \yii\base\Component
}
if ($db->enableProfiling) {
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
if (isset($cache, $cacheKey)) {
if (isset($cache, $cacheKey) && $cache instanceof Cache) {
$cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
\Yii::trace('Saved query result in cache', __CLASS__);
Yii::trace('Saved query result in cache', __METHOD__);
}
return $result;
} catch (\Exception $e) {
if ($db->enableProfiling) {
\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
Yii::endProfile($token, __METHOD__);
}
$message = $e->getMessage();
\Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__);
Yii::error("$message\nCommand::$method() failed: $rawSql", __METHOD__);
$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
throw new Exception($message, $errorInfo, (int)$e->getCode());
}
@ -539,7 +547,7 @@ class Command extends \yii\base\Component
*/
public function delete($table, $condition = '', $params = array())
{
$sql = $this->db->getQueryBuilder()->delete($table, $condition);
$sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
return $this->setSql($sql)->bindValues($params);
}

67
framework/db/Connection.php

@ -10,6 +10,7 @@ namespace yii\db;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\caching\Cache;
/**
* Connection represents a connection to a database via [PDO](http://www.php.net/manual/en/ref.pdo.php).
@ -136,10 +137,10 @@ class Connection extends Component
/**
* @var boolean whether to enable schema caching.
* Note that in order to enable truly schema caching, a valid cache component as specified
* by [[schemaCacheID]] must be enabled and [[enableSchemaCache]] must be set true.
* by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
* @see schemaCacheDuration
* @see schemaCacheExclude
* @see schemaCacheID
* @see schemaCache
*/
public $enableSchemaCache = false;
/**
@ -155,20 +156,20 @@ class Connection extends Component
*/
public $schemaCacheExclude = array();
/**
* @var string the ID of the cache application component that is used to cache the table metadata.
* Defaults to 'cache'.
* @var Cache|string the cache object or the ID of the cache application component that
* is used to cache the table metadata.
* @see enableSchemaCache
*/
public $schemaCacheID = 'cache';
public $schemaCache = 'cache';
/**
* @var boolean whether to enable query caching.
* Note that in order to enable query caching, a valid cache component as specified
* by [[queryCacheID]] must be enabled and [[enableQueryCache]] must be set true.
* by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
*
* Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on
* and off query caching on the fly.
* @see queryCacheDuration
* @see queryCacheID
* @see queryCache
* @see queryCacheDependency
* @see beginCache()
* @see endCache()
@ -176,7 +177,7 @@ class Connection extends Component
public $enableQueryCache = false;
/**
* @var integer number of seconds that query results can remain valid in cache.
* Defaults to 3600, meaning one hour.
* Defaults to 3600, meaning 3600 seconds, or one hour.
* Use 0 to indicate that the cached data will never expire.
* @see enableQueryCache
*/
@ -188,11 +189,11 @@ class Connection extends Component
*/
public $queryCacheDependency;
/**
* @var string the ID of the cache application component that is used for query caching.
* Defaults to 'cache'.
* @var Cache|string the cache object or the ID of the cache application component
* that is used for query caching.
* @see enableQueryCache
*/
public $queryCacheID = 'cache';
public $queryCache = 'cache';
/**
* @var string the charset used for database connection. The property is only used
* for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset
@ -222,21 +223,10 @@ class Connection extends Component
* @var string the common prefix or suffix for table names. If a table name is given
* as `{{%TableName}}`, then the percentage character `%` will be replaced with this
* property value. For example, `{{%post}}` becomes `{{tbl_post}}` if this property is
* set as `"tbl_"`. Note that this property is only effective when [[enableAutoQuoting]]
* is true.
* @see enableAutoQuoting
* set as `"tbl_"`.
*/
public $tablePrefix;
/**
* @var boolean whether to enable automatic quoting of table names and column names.
* Defaults to true. When this property is true, any token enclosed within double curly brackets
* (e.g. `{{post}}`) in a SQL statement will be treated as a table name and will be quoted
* accordingly when the SQL statement is executed; and any token enclosed within double square
* brackets (e.g. `[[name]]`) will be treated as a column name and quoted accordingly.
* @see tablePrefix
*/
public $enableAutoQuoting = true;
/**
* @var array mapping between PDO driver names and [[Schema]] classes.
* The keys of the array are PDO driver names while the values the corresponding
* schema class name or configuration. Please refer to [[\Yii::createObject()]] for
@ -253,9 +243,9 @@ class Connection extends Component
'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
'mssql' => 'yi\db\dao\mssql\Schema', // Mssql driver on windows hosts
'dblib' => 'yii\db\mssql\Schema', // dblib drivers on linux (and maybe others os) hosts
'sqlsrv' => 'yii\db\mssql\Schema', // Mssql
'oci' => 'yii\db\oci\Schema', // Oracle driver
'dblib' => 'yii\db\mssql\Schema', // dblib drivers on linux (and maybe others os) hosts
);
/**
* @var Transaction the currently active transaction
@ -290,7 +280,7 @@ class Connection extends Component
* This method is provided as a shortcut to setting two properties that are related
* with query caching: [[queryCacheDuration]] and [[queryCacheDependency]].
* @param integer $duration the number of seconds that query results may remain valid in cache.
* See [[queryCacheDuration]] for more details.
* If not set, it will use the value of [[queryCacheDuration]]. See [[queryCacheDuration]] for more details.
* @param \yii\caching\Dependency $dependency the dependency for the cached query result.
* See [[queryCacheDependency]] for more details.
*/
@ -323,12 +313,12 @@ class Connection extends Component
throw new InvalidConfigException('Connection::dsn cannot be empty.');
}
try {
\Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__);
\Yii::trace('Opening DB connection: ' . $this->dsn, __METHOD__);
$this->pdo = $this->createPdoInstance();
$this->initConnection();
}
catch (\PDOException $e) {
\Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __CLASS__);
\Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __METHOD__);
$message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.';
throw new Exception($message, $e->errorInfo, (int)$e->getCode());
}
@ -342,7 +332,7 @@ class Connection extends Component
public function close()
{
if ($this->pdo !== null) {
\Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__);
\Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
$this->pdo = null;
$this->_schema = null;
$this->_transaction = null;
@ -517,6 +507,27 @@ class Connection extends Component
}
/**
* Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
* Tokens enclosed within double curly brackets are treated as table names, while
* tokens enclosed within double square brackets are column names. They will be quoted accordingly.
* Also, the percentage character "%" in a table name will be replaced with [[tablePrefix]].
* @param string $sql the SQL to be quoted
* @return string the quoted SQL
*/
public function quoteSql($sql)
{
$db = $this;
return preg_replace_callback('/(\\{\\{([\w\-\. ]+)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
function($matches) use($db) {
if (isset($matches[3])) {
return $db->quoteColumnName($matches[3]);
} else {
return str_replace('%', $this->tablePrefix, $db->quoteTableName($matches[2]));
}
}, $sql);
}
/**
* Returns the name of the DB driver for the current [[dsn]].
* @return string name of the DB driver
*/

479
framework/db/QueryBuilder.php

@ -22,6 +22,11 @@ use yii\base\NotSupportedException;
class QueryBuilder extends \yii\base\Object
{
/**
* The prefix for automatically generated query binding parameters.
*/
const PARAM_PREFIX = ':qp';
/**
* @var Connection the database connection.
*/
public $db;
@ -58,11 +63,11 @@ class QueryBuilder extends \yii\base\Object
$clauses = array(
$this->buildSelect($query->select, $query->distinct, $query->selectOption),
$this->buildFrom($query->from),
$this->buildJoin($query->join),
$this->buildWhere($query->where),
$this->buildJoin($query->join, $query->params),
$this->buildWhere($query->where, $query->params),
$this->buildGroupBy($query->groupBy),
$this->buildHaving($query->having),
$this->buildUnion($query->union),
$this->buildHaving($query->having, $query->params),
$this->buildUnion($query->union, $query->params),
$this->buildOrderBy($query->orderBy),
$this->buildLimit($query->limit, $query->offset),
);
@ -92,7 +97,6 @@ class QueryBuilder extends \yii\base\Object
{
$names = array();
$placeholders = array();
$count = 0;
foreach ($columns as $name => $value) {
$names[] = $this->db->quoteColumnName($name);
if ($value instanceof Expression) {
@ -101,9 +105,9 @@ class QueryBuilder extends \yii\base\Object
$params[$n] = $v;
}
} else {
$placeholders[] = ':p' . $count;
$params[':p' . $count] = $value;
$count++;
$phName = self::PARAM_PREFIX . count($params);
$placeholders[] = $phName;
$params[$phName] = $value;
}
}
@ -159,10 +163,9 @@ class QueryBuilder extends \yii\base\Object
* so that they can be bound to the DB command later.
* @return string the UPDATE SQL
*/
public function update($table, $columns, $condition = '', &$params)
public function update($table, $columns, $condition, &$params)
{
$lines = array();
$count = 0;
foreach ($columns as $name => $value) {
if ($value instanceof Expression) {
$lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
@ -170,17 +173,15 @@ class QueryBuilder extends \yii\base\Object
$params[$n] = $v;
}
} else {
$lines[] = $this->db->quoteColumnName($name) . '=:p' . $count;
$params[':p' . $count] = $value;
$count++;
$phName = self::PARAM_PREFIX . count($params);
$lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
$params[$phName] = $value;
}
}
$sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
if (($where = $this->buildCondition($condition)) !== '') {
$sql .= ' WHERE ' . $where;
}
return $sql;
$sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
$where = $this->buildWhere($condition, $params);
return $where === '' ? $sql : $sql . ' ' . $where;
}
/**
@ -196,15 +197,15 @@ class QueryBuilder extends \yii\base\Object
* @param string $table the table where the data will be deleted from.
* @param mixed $condition the condition that will be put in the WHERE part. Please
* refer to [[Query::where()]] on how to specify condition.
* @param array $params the binding parameters that will be modified by this method
* so that they can be bound to the DB command later.
* @return string the DELETE SQL
*/
public function delete($table, $condition = '')
public function delete($table, $condition, &$params)
{
$sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
if (($where = $this->buildCondition($condition)) !== '') {
$sql .= ' WHERE ' . $where;
}
return $sql;
$where = $this->buildWhere($condition, $params);
return $where === '' ? $sql : $sql . ' ' . $where;
}
/**
@ -479,200 +480,6 @@ class QueryBuilder extends \yii\base\Object
}
/**
* Parses the condition specification and generates the corresponding SQL expression.
* @param string|array $condition the condition specification. Please refer to [[Query::where()]]
* on how to specify a condition.
* @return string the generated SQL expression
* @throws \yii\db\Exception if the condition is in bad format
*/
public function buildCondition($condition)
{
static $builders = array(
'AND' => 'buildAndCondition',
'OR' => 'buildAndCondition',
'BETWEEN' => 'buildBetweenCondition',
'NOT BETWEEN' => 'buildBetweenCondition',
'IN' => 'buildInCondition',
'NOT IN' => 'buildInCondition',
'LIKE' => 'buildLikeCondition',
'NOT LIKE' => 'buildLikeCondition',
'OR LIKE' => 'buildLikeCondition',
'OR NOT LIKE' => 'buildLikeCondition',
);
if (!is_array($condition)) {
return (string)$condition;
} elseif ($condition === array()) {
return '';
}
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
$operator = strtoupper($condition[0]);
if (isset($builders[$operator])) {
$method = $builders[$operator];
array_shift($condition);
return $this->$method($operator, $condition);
} else {
throw new Exception('Found unknown operator in query: ' . $operator);
}
} else { // hash format: 'column1'=>'value1', 'column2'=>'value2', ...
return $this->buildHashCondition($condition);
}
}
private function buildHashCondition($condition)
{
$parts = array();
foreach ($condition as $column => $value) {
if (is_array($value)) { // IN condition
$parts[] = $this->buildInCondition('in', array($column, $value));
} else {
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
if ($value === null) {
$parts[] = "$column IS NULL";
} elseif (is_string($value)) {
$parts[] = "$column=" . $this->db->quoteValue($value);
} else {
$parts[] = "$column=$value";
}
}
}
return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
}
private function buildAndCondition($operator, $operands)
{
$parts = array();
foreach ($operands as $operand) {
if (is_array($operand)) {
$operand = $this->buildCondition($operand);
}
if ($operand !== '') {
$parts[] = $operand;
}
}
if ($parts !== array()) {
return '(' . implode(") $operator (", $parts) . ')';
} else {
return '';
}
}
private function buildBetweenCondition($operator, $operands)
{
if (!isset($operands[0], $operands[1], $operands[2])) {
throw new Exception("Operator '$operator' requires three operands.");
}
list($column, $value1, $value2) = $operands;
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
$value1 = is_string($value1) ? $this->db->quoteValue($value1) : (string)$value1;
$value2 = is_string($value2) ? $this->db->quoteValue($value2) : (string)$value2;
return "$column $operator $value1 AND $value2";
}
private function buildInCondition($operator, $operands)
{
if (!isset($operands[0], $operands[1])) {
throw new Exception("Operator '$operator' requires two operands.");
}
list($column, $values) = $operands;
$values = (array)$values;
if ($values === array() || $column === array()) {
return $operator === 'IN' ? '0=1' : '';
}
if (count($column) > 1) {
return $this->buildCompositeInCondition($operator, $column, $values);
} elseif (is_array($column)) {
$column = reset($column);
}
foreach ($values as $i => $value) {
if (is_array($value)) {
$value = isset($value[$column]) ? $value[$column] : null;
}
if ($value === null) {
$values[$i] = 'NULL';
} else {
$values[$i] = is_string($value) ? $this->db->quoteValue($value) : (string)$value;
}
}
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
if (count($values) > 1) {
return "$column $operator (" . implode(', ', $values) . ')';
} else {
$operator = $operator === 'IN' ? '=' : '<>';
return "$column$operator{$values[0]}";
}
}
protected function buildCompositeInCondition($operator, $columns, $values)
{
foreach ($columns as $i => $column) {
if (strpos($column, '(') === false) {
$columns[$i] = $this->db->quoteColumnName($column);
}
}
$vss = array();
foreach ($values as $value) {
$vs = array();
foreach ($columns as $column) {
if (isset($value[$column])) {
$vs[] = is_string($value[$column]) ? $this->db->quoteValue($value[$column]) : (string)$value[$column];
} else {
$vs[] = 'NULL';
}
}
$vss[] = '(' . implode(', ', $vs) . ')';
}
return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
}
private function buildLikeCondition($operator, $operands)
{
if (!isset($operands[0], $operands[1])) {
throw new Exception("Operator '$operator' requires two operands.");
}
list($column, $values) = $operands;
$values = (array)$values;
if ($values === array()) {
return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
}
if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
$andor = ' AND ';
} else {
$andor = ' OR ';
$operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
}
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
$parts = array();
foreach ($values as $value) {
$parts[] = "$column $operator " . $this->db->quoteValue($value);
}
return implode($andor, $parts);
}
/**
* @param array $columns
* @param boolean $distinct
* @param string $selectOption
@ -737,10 +544,11 @@ class QueryBuilder extends \yii\base\Object
/**
* @param string|array $joins
* @param array $params the binding parameters to be populated
* @return string the JOIN clause built from [[query]].
* @throws Exception if the $joins parameter is not in proper format
*/
public function buildJoin($joins)
public function buildJoin($joins, &$params)
{
if (empty($joins)) {
return '';
@ -761,9 +569,9 @@ class QueryBuilder extends \yii\base\Object
}
$joins[$i] = $join[0] . ' ' . $table;
if (isset($join[2])) {
$condition = $this->buildCondition($join[2]);
$condition = $this->buildCondition($join[2], $params);
if ($condition !== '') {
$joins[$i] .= ' ON ' . $this->buildCondition($join[2]);
$joins[$i] .= ' ON ' . $condition;
}
}
} else {
@ -776,11 +584,12 @@ class QueryBuilder extends \yii\base\Object
/**
* @param string|array $condition
* @param array $params the binding parameters to be populated
* @return string the WHERE clause built from [[query]].
*/
public function buildWhere($condition)
public function buildWhere($condition, &$params)
{
$where = $this->buildCondition($condition);
$where = $this->buildCondition($condition, $params);
return $where === '' ? '' : 'WHERE ' . $where;
}
@ -795,11 +604,12 @@ class QueryBuilder extends \yii\base\Object
/**
* @param string|array $condition
* @param array $params the binding parameters to be populated
* @return string the HAVING clause built from [[query]].
*/
public function buildHaving($condition)
public function buildHaving($condition, &$params)
{
$having = $this->buildCondition($condition);
$having = $this->buildCondition($condition, $params);
return $having === '' ? '' : 'HAVING ' . $having;
}
@ -843,16 +653,19 @@ class QueryBuilder extends \yii\base\Object
/**
* @param array $unions
* @param array $params the binding parameters to be populated
* @return string the UNION clause built from [[query]].
*/
public function buildUnion($unions)
public function buildUnion($unions, &$params)
{
if (empty($unions)) {
return '';
}
foreach ($unions as $i => $union) {
if ($union instanceof Query) {
$union->addParams($params);
$unions[$i] = $this->build($union);
$params = $union->params;
}
}
return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)";
@ -864,7 +677,7 @@ class QueryBuilder extends \yii\base\Object
* @param string|array $columns the columns to be processed
* @return string the processing result
*/
protected function buildColumns($columns)
public function buildColumns($columns)
{
if (!is_array($columns)) {
if (strpos($columns, '(') !== false) {
@ -882,4 +695,218 @@ class QueryBuilder extends \yii\base\Object
}
return is_array($columns) ? implode(', ', $columns) : $columns;
}
/**
* Parses the condition specification and generates the corresponding SQL expression.
* @param string|array $condition the condition specification. Please refer to [[Query::where()]]
* on how to specify a condition.
* @param array $params the binding parameters to be populated
* @return string the generated SQL expression
* @throws \yii\db\Exception if the condition is in bad format
*/
public function buildCondition($condition, &$params)
{
static $builders = array(
'AND' => 'buildAndCondition',
'OR' => 'buildAndCondition',
'BETWEEN' => 'buildBetweenCondition',
'NOT BETWEEN' => 'buildBetweenCondition',
'IN' => 'buildInCondition',
'NOT IN' => 'buildInCondition',
'LIKE' => 'buildLikeCondition',
'NOT LIKE' => 'buildLikeCondition',
'OR LIKE' => 'buildLikeCondition',
'OR NOT LIKE' => 'buildLikeCondition',
);
if (!is_array($condition)) {
return (string)$condition;
} elseif ($condition === array()) {
return '';
}
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
$operator = strtoupper($condition[0]);
if (isset($builders[$operator])) {
$method = $builders[$operator];
array_shift($condition);
return $this->$method($operator, $condition, $params);
} else {
throw new Exception('Found unknown operator in query: ' . $operator);
}
} else { // hash format: 'column1'=>'value1', 'column2'=>'value2', ...
return $this->buildHashCondition($condition, $params);
}
}
private function buildHashCondition($condition, &$params)
{
$parts = array();
foreach ($condition as $column => $value) {
if (is_array($value)) { // IN condition
$parts[] = $this->buildInCondition('in', array($column, $value), $query);
} else {
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
if ($value === null) {
$parts[] = "$column IS NULL";
} elseif ($value instanceof Expression) {
$parts[] = "$column=" . $value->expression;
foreach ($value->params as $n => $v) {
$params[$n] = $v;
}
} else {
$phName = self::PARAM_PREFIX . count($params);
$parts[] = "$column=$phName";
$params[$phName] = $value;
}
}
}
return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
}
private function buildAndCondition($operator, $operands, &$params)
{
$parts = array();
foreach ($operands as $operand) {
if (is_array($operand)) {
$operand = $this->buildCondition($operand, $params);
}
if ($operand !== '') {
$parts[] = $operand;
}
}
if ($parts !== array()) {
return '(' . implode(") $operator (", $parts) . ')';
} else {
return '';
}
}
private function buildBetweenCondition($operator, $operands, &$params)
{
if (!isset($operands[0], $operands[1], $operands[2])) {
throw new Exception("Operator '$operator' requires three operands.");
}
list($column, $value1, $value2) = $operands;
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
$phName1 = self::PARAM_PREFIX . count($params);
$phName2 = self::PARAM_PREFIX . count($params);
$params[$phName1] = $value1;
$params[$phName2] = $value2;
return "$column $operator $phName1 AND $phName2";
}
private function buildInCondition($operator, $operands, &$params)
{
if (!isset($operands[0], $operands[1])) {
throw new Exception("Operator '$operator' requires two operands.");
}
list($column, $values) = $operands;
$values = (array)$values;
if ($values === array() || $column === array()) {
return $operator === 'IN' ? '0=1' : '';
}
if (count($column) > 1) {
return $this->buildCompositeInCondition($operator, $column, $values, $params);
} elseif (is_array($column)) {
$column = reset($column);
}
foreach ($values as $i => $value) {
if (is_array($value)) {
$value = isset($value[$column]) ? $value[$column] : null;
}
if ($value === null) {
$values[$i] = 'NULL';
} elseif ($value instanceof Expression) {
$values[$i] = $value->expression;
foreach ($value->params as $n => $v) {
$params[$n] = $v;
}
} else {
$phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value;
$values[$i] = $phName;
}
}
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
if (count($values) > 1) {
return "$column $operator (" . implode(', ', $values) . ')';
} else {
$operator = $operator === 'IN' ? '=' : '<>';
return "$column$operator{$values[0]}";
}
}
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
{
foreach ($columns as $i => $column) {
if (strpos($column, '(') === false) {
$columns[$i] = $this->db->quoteColumnName($column);
}
}
$vss = array();
foreach ($values as $value) {
$vs = array();
foreach ($columns as $column) {
if (isset($value[$column])) {
$phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value[$column];
$vs[] = $phName;
} else {
$vs[] = 'NULL';
}
}
$vss[] = '(' . implode(', ', $vs) . ')';
}
return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
}
private function buildLikeCondition($operator, $operands, &$params)
{
if (!isset($operands[0], $operands[1])) {
throw new Exception("Operator '$operator' requires two operands.");
}
list($column, $values) = $operands;
$values = (array)$values;
if ($values === array()) {
return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
}
if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
$andor = ' AND ';
} else {
$andor = ' OR ';
$operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
}
if (strpos($column, '(') === false) {
$column = $this->db->quoteColumnName($column);
}
$parts = array();
foreach ($values as $value) {
$phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value;
$parts[] = "$column $operator $phName";
}
return implode($andor, $parts);
}
}

34
framework/db/Schema.php

@ -7,6 +7,7 @@
namespace yii\db;
use Yii;
use yii\base\NotSupportedException;
use yii\base\InvalidCallException;
use yii\caching\Cache;
@ -82,10 +83,12 @@ abstract class Schema extends \yii\base\Object
}
$db = $this->db;
$realName = $this->getRealTableName($name);
$realName = $this->getRawTableName($name);
if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
/** @var $cache Cache */
if ($db->enableSchemaCache && ($cache = \Yii::$app->getComponent($db->schemaCacheID)) !== null && !in_array($name, $db->schemaCacheExclude, true)) {
$cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
if ($cache instanceof Cache) {
$key = $this->getCacheKey($cache, $name);
if ($refresh || ($table = $cache->get($key)) === false) {
$table = $this->loadTableSchema($realName);
@ -93,12 +96,10 @@ abstract class Schema extends \yii\base\Object
$cache->set($key, $table, $db->schemaCacheDuration);
}
}
$this->_tables[$name] = $table;
} else {
$this->_tables[$name] = $table = $this->loadTableSchema($realName);
return $this->_tables[$name] = $table;
}
return $table;
}
return $this->_tables[$name] = $table = $this->loadTableSchema($realName);
}
/**
@ -173,8 +174,9 @@ abstract class Schema extends \yii\base\Object
*/
public function refresh()
{
/** @var $cache \yii\caching\Cache */
if ($this->db->enableSchemaCache && ($cache = \Yii::$app->getComponent($this->db->schemaCacheID)) !== null) {
/** @var $cache Cache */
$cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
if ($this->db->enableSchemaCache && $cache instanceof Cache) {
foreach ($this->_tables as $name => $table) {
$cache->delete($this->getCacheKey($cache, $name));
}
@ -246,7 +248,7 @@ abstract class Schema extends \yii\base\Object
/**
* Quotes a table name for use in a query.
* If the table name contains schema prefix, the prefix will also be properly quoted.
* If the table name is already quoted or contains special characters including '(', '[[' and '{{',
* If the table name is already quoted or contains '(' or '{{',
* then this method will do nothing.
* @param string $name table name
* @return string the properly quoted table name
@ -254,7 +256,7 @@ abstract class Schema extends \yii\base\Object
*/
public function quoteTableName($name)
{
if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
return $name;
}
if (strpos($name, '.') === false) {
@ -271,7 +273,7 @@ abstract class Schema extends \yii\base\Object
/**
* Quotes a column name for use in a query.
* If the column name contains prefix, the prefix will also be properly quoted.
* If the column name is already quoted or contains special characters including '(', '[[' and '{{',
* If the column name is already quoted or contains '(', '[[' or '{{',
* then this method will do nothing.
* @param string $name column name
* @return string the properly quoted column name
@ -316,15 +318,15 @@ abstract class Schema extends \yii\base\Object
}
/**
* Returns the real name of a table name.
* Returns the actual name of a given table name.
* This method will strip off curly brackets from the given table name
* and replace the percentage character in the name with [[Connection::tablePrefix]].
* and replace the percentage character '%' with [[Connection::tablePrefix]].
* @param string $name the table name to be converted
* @return string the real name of the given table name
*/
public function getRealTableName($name)
public function getRawTableName($name)
{
if ($this->db->enableAutoQuoting && strpos($name, '{{') !== false) {
if (strpos($name, '{{') !== false) {
$name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
return str_replace('%', $this->db->tablePrefix, $name);
} else {

23
framework/db/StaleObjectException.php

@ -0,0 +1,23 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class StaleObjectException extends Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return \Yii::t('yii|Stale Object Exception');
}
}

6
framework/db/Transaction.php

@ -66,7 +66,7 @@ class Transaction extends \yii\base\Object
if ($this->db === null) {
throw new InvalidConfigException('Transaction::db must be set.');
}
\Yii::trace('Starting transaction', __CLASS__);
\Yii::trace('Starting transaction', __METHOD__);
$this->db->open();
$this->db->pdo->beginTransaction();
$this->_active = true;
@ -80,7 +80,7 @@ class Transaction extends \yii\base\Object
public function commit()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Committing transaction', __CLASS__);
\Yii::trace('Committing transaction', __METHOD__);
$this->db->pdo->commit();
$this->_active = false;
} else {
@ -95,7 +95,7 @@ class Transaction extends \yii\base\Object
public function rollback()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Rolling back transaction', __CLASS__);
\Yii::trace('Rolling back transaction', __METHOD__);
$this->db->pdo->rollBack();
$this->_active = false;
} else {

15
framework/helpers/Html.php

@ -949,11 +949,10 @@ class Html
* If the input parameter
*
* - is an empty string: the currently requested URL will be returned;
* - is a non-empty string: it will be processed by [[Yii::getAlias()]] which, if the string is an alias,
* will be resolved into a URL;
* - is a non-empty string: it will be processed by [[Yii::getAlias()]] and returned;
* - is an array: the first array element is considered a route, while the rest of the name-value
* pairs are considered as the parameters to be used for URL creation using [[\yii\base\Application::createUrl()]].
* Here are some examples: `array('post/index', 'page' => 2)`, `array('index')`.
* pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]].
* For example: `array('post/index', 'page' => 2)`, `array('index')`.
*
* @param array|string $url the parameter to be used to generate a valid URL
* @return string the normalized URL
@ -963,7 +962,13 @@ class Html
{
if (is_array($url)) {
if (isset($url[0])) {
return Yii::$app->createUrl($url[0], array_splice($url, 1));
$route = $url[0];
$params = array_splice($url, 1);
if (Yii::$app->controller !== null) {
return Yii::$app->controller->createUrl($route, $params);
} else {
return Yii::$app->getUrlManager()->createUrl($route, $params);
}
} else {
throw new InvalidParamException('The array specifying a URL must contain at least one element.');
}

97
framework/i18n/I18N.php

@ -1,11 +1,23 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\i18n;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
/**
* I18N provides features related with internationalization (I18N) and localization (L10N).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class I18N extends Component
{
/**
@ -13,11 +25,36 @@ class I18N extends Component
* categories, and the array values are the corresponding [[MessageSource]] objects or the configurations
* for creating the [[MessageSource]] objects. The message categories can contain the wildcard '*' at the end
* to match multiple categories with the same prefix. For example, 'app\*' matches both 'app\cat1' and 'app\cat2'.
*
* This property may be modified on the fly by extensions who want to have their own message sources
* registered under their own namespaces.
*
* The category "yii" and "app" are always defined. The former refers to the messages used in the Yii core
* framework code, while the latter refers to the default message category for custom application code.
* By default, both of these categories use [[PhpMessageSource]] and the corresponding message files are
* stored under "@yii/messages" and "@app/messages", respectively.
*
* You may override the configuration of both categories.
*/
public $translations;
/**
* @var string the path or path alias of the file that contains the plural rules.
* By default, this refers to a file shipped with the Yii distribution. The file is obtained
* by converting from the data file in the CLDR project.
*
* If the default rule file does not contain the expected rules, you may copy and modify it
* for your application, and then configure this property to point to your modified copy.
*
* @see http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html
*/
public $pluralRuleFile = '@yii/i18n/data/plurals.php';
/**
* Initializes the component by configuring the default message categories.
*/
public function init()
{
parent::init();
if (!isset($this->translations['yii'])) {
$this->translations['yii'] = array(
'class' => 'yii\i18n\PhpMessageSource',
@ -34,6 +71,16 @@ class I18N extends Component
}
}
/**
* Translates a message to the specified language.
* If the first parameter in `$params` is a number and it is indexed by 0, appropriate plural rules
* will be applied to the translated message.
* @param string $message the message to be translated.
* @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
* @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current
* [[\yii\base\Application::language|application language]] will be used.
* @return string the translated message.
*/
public function translate($message, $params = array(), $language = null)
{
if ($language === null) {
@ -55,7 +102,7 @@ class I18N extends Component
}
if (isset($params[0])) {
$message = $this->getPluralForm($message, $params[0], $language);
$message = $this->applyPluralRules($message, $params[0], $language);
if (!isset($params['{n}'])) {
$params['{n}'] = $params[0];
}
@ -65,6 +112,12 @@ class I18N extends Component
return $params === array() ? $message : strtr($message, $params);
}
/**
* Returns the message source for the given category.
* @param string $category the category name.
* @return MessageSource the message source for the given category.
* @throws InvalidConfigException if there is no message source available for the specified category.
*/
public function getMessageSource($category)
{
if (isset($this->translations[$category])) {
@ -85,18 +138,21 @@ class I18N extends Component
}
}
public function getLocale($language)
{
}
protected function getPluralForm($message, $number, $language)
/**
* Applies appropriate plural rules to the given message.
* @param string $message the message to be applied with plural rules
* @param mixed $number the number by which plural rules will be applied
* @param string $language the language code that determines which set of plural rules to be applied.
* @return string the message that has applied plural rules
*/
protected function applyPluralRules($message, $number, $language)
{
if (strpos($message, '|') === false) {
return $message;
}
$chunks = explode('|', $message);
$rules = $this->getLocale($language)->getPluralRules();
$rules = $this->getPluralRules($language);
foreach ($rules as $i => $rule) {
if (isset($chunks[$i]) && $this->evaluate($rule, $number)) {
return $chunks[$i];
@ -106,6 +162,29 @@ class I18N extends Component
return isset($chunks[$n]) ? $chunks[$n] : $chunks[0];
}
private $_pluralRules = array(); // language => rule set
/**
* Returns the plural rules for the given language code.
* @param string $language the language code (e.g. `en_US`, `en`).
* @return array the plural rules
* @throws InvalidParamException if the language code is invalid.
*/
protected function getPluralRules($language)
{
if (isset($this->_pluralRules[$language])) {
return $this->_pluralRules;
}
$allRules = require(Yii::getAlias($this->pluralRuleFile));
if (isset($allRules[$language])) {
return $this->_pluralRules[$language] = $allRules[$language];
} elseif (preg_match('/^[a-z]+/', strtolower($language), $matches)) {
return $this->_pluralRules[$language] = isset($allRules[$matches[0]]) ? $allRules[$matches[0]] : array();
} else {
throw new InvalidParamException("Invalid language code: $language");
}
}
/**
* Evaluates a PHP expression with the given number value.
* @param string $expression the PHP expression
@ -114,6 +193,6 @@ class I18N extends Component
*/
protected function evaluate($expression, $n)
{
return @eval("return $expression;");
return eval("return $expression;");
}
}

2
framework/i18n/PhpMessageSource.php

@ -72,7 +72,7 @@ class PhpMessageSource extends MessageSource
}
return $messages;
} else {
Yii::error("The message file for category '$category' does not exist: $messageFile", __CLASS__);
Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__);
return array();
}
}

627
framework/i18n/data/plurals.php

@ -0,0 +1,627 @@
<?php
/**
* Plural rules.
*
* This file is automatically generated by the "yiic locale/plural" command under the "build" folder.
* Do not modify it directly.
*
* The original plural rule data used for generating this file has the following copyright terms:
*
* Copyright © 1991-2007 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
*
* @revision 6008 (of the original plural file)
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
return array (
'ar' =>
array (
0 => '$n==0',
1 => '$n==1',
2 => '$n==2',
3 => 'in_array(fmod($n,100),range(3,10))',
4 => 'in_array(fmod($n,100),range(11,99))',
),
'asa' =>
array (
0 => '$n==1',
),
'af' =>
array (
0 => '$n==1',
),
'bem' =>
array (
0 => '$n==1',
),
'bez' =>
array (
0 => '$n==1',
),
'bg' =>
array (
0 => '$n==1',
),
'bn' =>
array (
0 => '$n==1',
),
'brx' =>
array (
0 => '$n==1',
),
'ca' =>
array (
0 => '$n==1',
),
'cgg' =>
array (
0 => '$n==1',
),
'chr' =>
array (
0 => '$n==1',
),
'da' =>
array (
0 => '$n==1',
),
'de' =>
array (
0 => '$n==1',
),
'dv' =>
array (
0 => '$n==1',
),
'ee' =>
array (
0 => '$n==1',
),
'el' =>
array (
0 => '$n==1',
),
'en' =>
array (
0 => '$n==1',
),
'eo' =>
array (
0 => '$n==1',
),
'es' =>
array (
0 => '$n==1',
),
'et' =>
array (
0 => '$n==1',
),
'eu' =>
array (
0 => '$n==1',
),
'fi' =>
array (
0 => '$n==1',
),
'fo' =>
array (
0 => '$n==1',
),
'fur' =>
array (
0 => '$n==1',
),
'fy' =>
array (
0 => '$n==1',
),
'gl' =>
array (
0 => '$n==1',
),
'gsw' =>
array (
0 => '$n==1',
),
'gu' =>
array (
0 => '$n==1',
),
'ha' =>
array (
0 => '$n==1',
),
'haw' =>
array (
0 => '$n==1',
),
'he' =>
array (
0 => '$n==1',
),
'is' =>
array (
0 => '$n==1',
),
'it' =>
array (
0 => '$n==1',
),
'jmc' =>
array (
0 => '$n==1',
),
'kaj' =>
array (
0 => '$n==1',
),
'kcg' =>
array (
0 => '$n==1',
),
'kk' =>
array (
0 => '$n==1',
),
'kl' =>
array (
0 => '$n==1',
),
'ksb' =>
array (
0 => '$n==1',
),
'ku' =>
array (
0 => '$n==1',
),
'lb' =>
array (
0 => '$n==1',
),
'lg' =>
array (
0 => '$n==1',
),
'mas' =>
array (
0 => '$n==1',
),
'ml' =>
array (
0 => '$n==1',
),
'mn' =>
array (
0 => '$n==1',
),
'mr' =>
array (
0 => '$n==1',
),
'nah' =>
array (
0 => '$n==1',
),
'nb' =>
array (
0 => '$n==1',
),
'nd' =>
array (
0 => '$n==1',
),
'ne' =>
array (
0 => '$n==1',
),
'nl' =>
array (
0 => '$n==1',
),
'nn' =>
array (
0 => '$n==1',
),
'no' =>
array (
0 => '$n==1',
),
'nr' =>
array (
0 => '$n==1',
),
'ny' =>
array (
0 => '$n==1',
),
'nyn' =>
array (
0 => '$n==1',
),
'om' =>
array (
0 => '$n==1',
),
'or' =>
array (
0 => '$n==1',
),
'pa' =>
array (
0 => '$n==1',
),
'pap' =>
array (
0 => '$n==1',
),
'ps' =>
array (
0 => '$n==1',
),
'pt' =>
array (
0 => '$n==1',
),
'rof' =>
array (
0 => '$n==1',
),
'rm' =>
array (
0 => '$n==1',
),
'rwk' =>
array (
0 => '$n==1',
),
'saq' =>
array (
0 => '$n==1',
),
'seh' =>
array (
0 => '$n==1',
),
'sn' =>
array (
0 => '$n==1',
),
'so' =>
array (
0 => '$n==1',
),
'sq' =>
array (
0 => '$n==1',
),
'ss' =>
array (
0 => '$n==1',
),
'ssy' =>
array (
0 => '$n==1',
),
'st' =>
array (
0 => '$n==1',
),
'sv' =>
array (
0 => '$n==1',
),
'sw' =>
array (
0 => '$n==1',
),
'syr' =>
array (
0 => '$n==1',
),
'ta' =>
array (
0 => '$n==1',
),
'te' =>
array (
0 => '$n==1',
),
'teo' =>
array (
0 => '$n==1',
),
'tig' =>
array (
0 => '$n==1',
),
'tk' =>
array (
0 => '$n==1',
),
'tn' =>
array (
0 => '$n==1',
),
'ts' =>
array (
0 => '$n==1',
),
'ur' =>
array (
0 => '$n==1',
),
'wae' =>
array (
0 => '$n==1',
),
've' =>
array (
0 => '$n==1',
),
'vun' =>
array (
0 => '$n==1',
),
'xh' =>
array (
0 => '$n==1',
),
'xog' =>
array (
0 => '$n==1',
),
'zu' =>
array (
0 => '$n==1',
),
'ak' =>
array (
0 => '($n==0||$n==1)',
),
'am' =>
array (
0 => '($n==0||$n==1)',
),
'bh' =>
array (
0 => '($n==0||$n==1)',
),
'fil' =>
array (
0 => '($n==0||$n==1)',
),
'tl' =>
array (
0 => '($n==0||$n==1)',
),
'guw' =>
array (
0 => '($n==0||$n==1)',
),
'hi' =>
array (
0 => '($n==0||$n==1)',
),
'ln' =>
array (
0 => '($n==0||$n==1)',
),
'mg' =>
array (
0 => '($n==0||$n==1)',
),
'nso' =>
array (
0 => '($n==0||$n==1)',
),
'ti' =>
array (
0 => '($n==0||$n==1)',
),
'wa' =>
array (
0 => '($n==0||$n==1)',
),
'ff' =>
array (
0 => '($n>=0&&$n<=2)&&$n!=2',
),
'fr' =>
array (
0 => '($n>=0&&$n<=2)&&$n!=2',
),
'kab' =>
array (
0 => '($n>=0&&$n<=2)&&$n!=2',
),
'lv' =>
array (
0 => '$n==0',
1 => 'fmod($n,10)==1&&fmod($n,100)!=11',
),
'iu' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'kw' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'naq' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'se' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'sma' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'smi' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'smj' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'smn' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'sms' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'ga' =>
array (
0 => '$n==1',
1 => '$n==2',
2 => 'in_array($n,array(3,4,5,6))',
3 => 'in_array($n,array(7,8,9,10))',
),
'ro' =>
array (
0 => '$n==1',
1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))',
),
'mo' =>
array (
0 => '$n==1',
1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))',
),
'lt' =>
array (
0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),range(11,19))',
1 => 'in_array(fmod($n,10),range(2,9))&&!in_array(fmod($n,100),range(11,19))',
),
'be' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'bs' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'hr' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'ru' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'sh' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'sr' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'uk' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'cs' =>
array (
0 => '$n==1',
1 => 'in_array($n,array(2,3,4))',
),
'sk' =>
array (
0 => '$n==1',
1 => 'in_array($n,array(2,3,4))',
),
'pl' =>
array (
0 => '$n==1',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => '$n!=1&&in_array(fmod($n,10),array(0,1))||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(12,13,14))',
),
'sl' =>
array (
0 => 'fmod($n,100)==1',
1 => 'fmod($n,100)==2',
2 => 'in_array(fmod($n,100),array(3,4))',
),
'mt' =>
array (
0 => '$n==1',
1 => '$n==0||in_array(fmod($n,100),range(2,10))',
2 => 'in_array(fmod($n,100),range(11,19))',
),
'mk' =>
array (
0 => 'fmod($n,10)==1&&$n!=11',
),
'cy' =>
array (
0 => '$n==0',
1 => '$n==1',
2 => '$n==2',
3 => '$n==3',
4 => '$n==6',
),
'lag' =>
array (
0 => '$n==0',
1 => '($n>=0&&$n<=2)&&$n!=0&&$n!=2',
),
'shi' =>
array (
0 => '($n>=0&&$n<=1)',
1 => 'in_array($n,range(2,10))',
),
'br' =>
array (
0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))',
1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))',
2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))',
3 => 'fmod($n,1000000)==0&&$n!=0',
),
'ksh' =>
array (
0 => '$n==0',
1 => '$n==1',
),
'tzm' =>
array (
0 => '($n==0||$n==1)||in_array($n,range(11,99))',
),
'gv' =>
array (
0 => 'in_array(fmod($n,10),array(1,2))||fmod($n,20)==0',
),
);

109
framework/i18n/data/plurals.xml

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<supplementalData>
<version number="$Revision: 6008 $"/>
<generation date="$Date: 2011-07-12 13:18:01 -0500 (Tue, 12 Jul 2011) $"/>
<plurals>
<!-- if locale is known to have no plurals, there are no rules -->
<pluralRules locales="az bm bo dz fa id ig ii hu ja jv ka kde kea km kn ko lo ms my sah ses sg th to tr vi wo yo zh"/>
<pluralRules locales="ar">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n mod 100 in 3..10</pluralRule>
<pluralRule count="many">n mod 100 in 11..99</pluralRule>
</pluralRules>
<pluralRules locales="asa af bem bez bg bn brx ca cgg chr da de dv ee el en eo es et eu fi fo fur fy gl gsw gu ha haw he is it jmc kaj kcg kk kl ksb ku lb lg mas ml mn mr nah nb nd ne nl nn no nr ny nyn om or pa pap ps pt rof rm rwk saq seh sn so sq ss ssy st sv sw syr ta te teo tig tk tn ts ur wae ve vun xh xog zu">
<pluralRule count="one">n is 1</pluralRule>
</pluralRules>
<pluralRules locales="ak am bh fil tl guw hi ln mg nso ti wa">
<pluralRule count="one">n in 0..1</pluralRule>
</pluralRules>
<pluralRules locales="ff fr kab">
<pluralRule count="one">n within 0..2 and n is not 2</pluralRule>
</pluralRules>
<pluralRules locales="lv">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule>
</pluralRules>
<pluralRules locales="iu kw naq se sma smi smj smn sms">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
</pluralRules>
<pluralRules locales="ga"> <!-- http://unicode.org/cldr/trac/ticket/3915 -->
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n in 3..6</pluralRule>
<pluralRule count="many">n in 7..10</pluralRule>
</pluralRules>
<pluralRules locales="ro mo">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n is 0 OR n is not 1 AND n mod 100 in 1..19</pluralRule>
</pluralRules>
<pluralRules locales="lt">
<pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11..19</pluralRule>
<pluralRule count="few">n mod 10 in 2..9 and n mod 100 not in 11..19</pluralRule>
</pluralRules>
<pluralRules locales="be bs hr ru sh sr uk">
<pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule>
<pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule>
<pluralRule count="many">n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14</pluralRule>
<!-- others are fractions -->
</pluralRules>
<pluralRules locales="cs sk">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n in 2..4</pluralRule>
</pluralRules>
<pluralRules locales="pl">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule>
<pluralRule count="many">n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14</pluralRule>
<!-- others are fractions -->
<!-- and n mod 100 not in 22..24 from Tamplin -->
</pluralRules>
<pluralRules locales="sl">
<pluralRule count="one">n mod 100 is 1</pluralRule>
<pluralRule count="two">n mod 100 is 2</pluralRule>
<pluralRule count="few">n mod 100 in 3..4</pluralRule>
</pluralRules>
<pluralRules locales="mt"> <!-- from Tamplin's data -->
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n is 0 or n mod 100 in 2..10</pluralRule>
<pluralRule count="many">n mod 100 in 11..19</pluralRule>
</pluralRules>
<pluralRules locales="mk"> <!-- from Tamplin's data -->
<pluralRule count="one">n mod 10 is 1 and n is not 11</pluralRule>
</pluralRules>
<pluralRules locales="cy"> <!-- from http://www.saltcymru.org/wordpress/?p=99&lang=en -->
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n is 3</pluralRule>
<pluralRule count="many">n is 6</pluralRule>
</pluralRules>
<pluralRules locales="lag">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n within 0..2 and n is not 0 and n is not 2</pluralRule>
</pluralRules>
<pluralRules locales="shi">
<pluralRule count="one">n within 0..1</pluralRule>
<pluralRule count="few">n in 2..10</pluralRule>
</pluralRules>
<pluralRules locales="br"> <!-- from http://unicode.org/cldr/trac/ticket/2886 -->
<pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11,71,91</pluralRule>
<pluralRule count="two">n mod 10 is 2 and n mod 100 not in 12,72,92</pluralRule>
<pluralRule count="few">n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99</pluralRule>
<pluralRule count="many">n mod 1000000 is 0 and n is not 0</pluralRule>
</pluralRules>
<pluralRules locales="ksh">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
</pluralRules>
<pluralRules locales="tzm">
<pluralRule count="one">n in 0..1 or n in 11..99</pluralRule>
</pluralRules>
<pluralRules locales="gv">
<pluralRule count="one">n mod 10 in 1..2 or n mod 20 is 0</pluralRule>
</pluralRules>
</plurals>
</supplementalData>

68
framework/logging/DbTarget.php

@ -7,16 +7,15 @@
namespace yii\logging;
use Yii;
use yii\db\Connection;
use yii\base\InvalidConfigException;
/**
* DbTarget stores log messages in a database table.
*
* By default, DbTarget will use the database specified by [[connectionID]] and save
* messages into a table named by [[tableName]]. Please refer to [[tableName]] for the required
* table structure. Note that this table must be created beforehand. Otherwise an exception
* will be thrown when DbTarget is saving messages into DB.
* By default, DbTarget stores the log messages in a DB table named 'tbl_log'. This table
* must be pre-created. The table name can be changed by setting [[logTable]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
@ -24,20 +23,18 @@ use yii\base\InvalidConfigException;
class DbTarget extends Target
{
/**
* @var string the ID of [[Connection]] application component.
* Defaults to 'db'. Please make sure that your database contains a table
* whose name is as specified in [[tableName]] and has the required table structure.
* @see tableName
* @var Connection|string the DB connection object or the application component ID of the DB connection.
* After the DbTarget object is created, if you want to change this property, you should only assign it
* with a DB connection object.
*/
public $connectionID = 'db';
public $db = 'db';
/**
* @var string the name of the DB table that stores log messages. Defaults to 'tbl_log'.
*
* The DB table should have the following structure:
* @var string name of the DB table to store cache content.
* The table should be pre-created as follows:
*
* ~~~
* CREATE TABLE tbl_log (
* id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
* id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
* level INTEGER,
* category VARCHAR(255),
* log_time INTEGER,
@ -48,42 +45,29 @@ class DbTarget extends Target
* ~~~
*
* Note that the 'id' column must be created as an auto-incremental column.
* The above SQL shows the syntax of MySQL. If you are using other DBMS, you need
* The above SQL uses the MySQL syntax. If you are using other DBMS, you need
* to adjust it accordingly. For example, in PostgreSQL, it should be `id SERIAL PRIMARY KEY`.
*
* The indexes declared above are not required. They are mainly used to improve the performance
* of some queries about message levels and categories. Depending on your actual needs, you may
* want to create additional indexes (e.g. index on log_time).
* want to create additional indexes (e.g. index on `log_time`).
*/
public $tableName = 'tbl_log';
private $_db;
public $logTable = 'tbl_log';
/**
* Returns the DB connection used for saving log messages.
* @return Connection the DB connection instance
* @throws InvalidConfigException if [[connectionID]] does not point to a valid application component.
* Initializes the DbTarget component.
* This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
* @throws InvalidConfigException if [[db]] is invalid.
*/
public function getDb()
public function init()
{
if ($this->_db === null) {
$db = \Yii::$app->getComponent($this->connectionID);
if ($db instanceof Connection) {
$this->_db = $db;
} else {
throw new InvalidConfigException("DbTarget::connectionID must refer to the ID of a DB application component.");
parent::init();
if (is_string($this->db)) {
$this->db = Yii::$app->getComponent($this->db);
}
if (!$this->db instanceof Connection) {
throw new InvalidConfigException("DbTarget::db must be either a DB connection instance or the application component ID of a DB connection.");
}
return $this->_db;
}
/**
* Sets the DB connection used by the cache component.
* @param Connection $value the DB connection instance
*/
public function setDb($value)
{
$this->_db = $value;
}
/**
@ -93,10 +77,10 @@ class DbTarget extends Target
*/
public function export($messages)
{
$db = $this->getDb();
$tableName = $db->quoteTableName($this->tableName);
$sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)";
$command = $db->createCommand($sql);
$tableName = $this->db->quoteTableName($this->logTable);
$sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[message]])
VALUES (:level, :category, :log_time, :message)";
$command = $this->db->createCommand($sql);
foreach ($messages as $message) {
$command->bindValues(array(
':level' => $message[1],

3
framework/logging/Target.php

@ -238,6 +238,7 @@ abstract class Target extends \yii\base\Component
if (!is_string($text)) {
$text = var_export($text, true);
}
return date('Y/m/d H:i:s', $timestamp) . " [$level] [$category] $text\n";
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
return date('Y/m/d H:i:s', $timestamp) . " [$ip] [$level] [$category] $text\n";
}
}

104
framework/web/AccessControl.php

@ -0,0 +1,104 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\Action;
use yii\base\ActionFilter;
use yii\base\HttpException;
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class AccessControl extends ActionFilter
{
/**
* @var callback a callback that will be called if the access should be denied
* to the current user. If not set, [[denyAccess()]] will be called.
*
* The signature of the callback should be as follows:
*
* ~~~
* function ($rule, $action)
* ~~~
*
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
*/
public $denyCallback;
/**
* @var string the default class of the access rules. This is used when
* a rule is configured without specifying a class in [[rules]].
*/
public $defaultRuleClass = 'yii\web\AccessRule';
/**
* @var array a list of access rule objects or configurations for creating the rule objects.
*/
public $rules = array();
/**
* Initializes the [[rules]] array by instantiating rule objects from configurations.
*/
public function init()
{
parent::init();
foreach ($this->rules as $i => $rule) {
if (is_array($rule)) {
if (!isset($rule['class'])) {
$rule['class'] = $this->defaultRuleClass;
}
$this->rules[$i] = Yii::createObject($rule);
}
}
}
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$user = Yii::$app->getUser();
$request = Yii::$app->getRequest();
/** @var $rule AccessRule */
foreach ($this->rules as $rule) {
if ($allow = $rule->allows($action, $user, $request)) {
break;
} elseif ($allow === false) {
if (isset($rule->denyCallback)) {
call_user_func($rule->denyCallback, $rule);
} elseif (isset($this->denyCallback)) {
call_user_func($this->denyCallback, $rule);
} else {
$this->denyAccess($user);
}
return false;
}
}
return true;
}
/**
* Denies the access of the user.
* The default implementation will redirect the user to the login page if he is a guest;
* if the user is already logged, a 403 HTTP exception will be thrown.
* @param User $user the current user
* @throws HttpException if the user is already logged in.
*/
protected function denyAccess($user)
{
if ($user->getIsGuest()) {
$user->loginRequired();
} else {
throw new HttpException(403, Yii::t('yii|You are not allowed to perform this action.'));
}
}
}

188
framework/web/AccessRule.php

@ -0,0 +1,188 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use yii\base\Component;
use yii\base\Action;
use yii\base\Controller;
use yii\web\User;
use yii\web\Request;
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class AccessRule extends Component
{
/**
* @var boolean whether this is an 'allow' rule or 'deny' rule.
*/
public $allow;
/**
* @var array list of action IDs that this rule applies to. The comparison is case-sensitive.
* If not set or empty, it means this rule applies to all actions.
*/
public $actions;
/**
* @var array list of controller IDs that this rule applies to. The comparison is case-sensitive.
* If not set or empty, it means this rule applies to all controllers.
*/
public $controllers;
/**
* @var array list of roles that this rule applies to. Two special roles are recognized, and
* they are checked via [[User::isGuest]]:
*
* - `?`: matches a guest user (not authenticated yet)
* - `@`: matches an authenticated user
*
* Using additional role names requires RBAC (Role-Based Access Control), and
* [[User::hasAccess()]] will be called.
*
* If this property is not set or empty, it means this rule applies to all roles.
*/
public $roles;
/**
* @var array list of user IP addresses that this rule applies to. An IP address
* can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
* For example, '192.168.*' matches all IP addresses in the segment '192.168.'.
* If not set or empty, it means this rule applies to all IP addresses.
* @see Request::userIP
*/
public $ips;
/**
* @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to.
* The request methods must be specified in uppercase.
* If not set or empty, it means this rule applies to all request methods.
* @see Request::requestMethod
*/
public $verbs;
/**
* @var callback a callback that will be called to determine if the rule should be applied.
* The signature of the callback should be as follows:
*
* ~~~
* function ($rule, $action)
* ~~~
*
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
* The callback should return a boolean value indicating whether this rule should be applied.
*/
public $matchCallback;
/**
* @var callback a callback that will be called if this rule determines the access to
* the current action should be denied. If not set, the behavior will be determined by
* [[AccessControl]].
*
* The signature of the callback should be as follows:
*
* ~~~
* function ($rule, $action)
* ~~~
*
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
*/
public $denyCallback;
/**
* Checks whether the Web user is allowed to perform the specified action.
* @param Action $action the action to be performed
* @param User $user the user object
* @param Request $request
* @return boolean|null true if the user is allowed, false if the user is denied, null if the rule does not apply to the user
*/
public function allows($action, $user, $request)
{
if ($this->matchAction($action)
&& $this->matchRole($user)
&& $this->matchIP($request->getUserIP())
&& $this->matchVerb($request->getRequestMethod())
&& $this->matchController($action->controller)
&& $this->matchCustom($action)
) {
return $this->allow ? true : false;
} else {
return null;
}
}
/**
* @param Action $action the action
* @return boolean whether the rule applies to the action
*/
protected function matchAction($action)
{
return empty($this->actions) || in_array($action->id, $this->actions, true);
}
/**
* @param Controller $controller the controller
* @return boolean whether the rule applies to the controller
*/
protected function matchController($controller)
{
return empty($this->controllers) || in_array($controller->id, $this->controllers, true);
}
/**
* @param User $user the user object
* @return boolean whether the rule applies to the role
*/
protected function matchRole($user)
{
if (empty($this->roles)) {
return true;
}
foreach ($this->roles as $role) {
if ($role === '?' && $user->getIsGuest()) {
return true;
} elseif ($role === '@' && !$user->getIsGuest()) {
return true;
} elseif ($user->hasAccess($role)) {
return true;
}
}
return false;
}
/**
* @param string $ip the IP address
* @return boolean whether the rule applies to the IP address
*/
protected function matchIP($ip)
{
if (empty($this->ips)) {
return true;
}
foreach ($this->ips as $rule) {
if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) {
return true;
}
}
return false;
}
/**
* @param string $verb the request method
* @return boolean whether the rule applies to the request
*/
protected function matchVerb($verb)
{
return empty($this->verbs) || in_array($verb, $this->verbs, true);
}
/**
* @param Action $action the action to be performed
* @return boolean whether the rule should be applied
*/
protected function matchCustom($action)
{
return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action);
}
}

77
framework/web/Application.php

@ -7,7 +7,7 @@
namespace yii\web;
use yii\base\InvalidParamException;
use Yii;
/**
* Application is the base class for all application classes.
@ -28,7 +28,7 @@ class Application extends \yii\base\Application
public function registerDefaultAliases()
{
parent::registerDefaultAliases();
\Yii::$aliases['@webroot'] = dirname($_SERVER['SCRIPT_FILENAME']);
Yii::$aliases['@webroot'] = dirname($_SERVER['SCRIPT_FILENAME']);
}
/**
@ -41,6 +41,32 @@ class Application extends \yii\base\Application
return $this->runAction($route, $params);
}
private $_homeUrl;
/**
* @return string the homepage URL
*/
public function getHomeUrl()
{
if ($this->_homeUrl === null) {
if ($this->getUrlManager()->showScriptName) {
return $this->getRequest()->getScriptUrl();
} else {
return $this->getRequest()->getBaseUrl() . '/';
}
} else {
return $this->_homeUrl;
}
}
/**
* @param string $value the homepage URL
*/
public function setHomeUrl($value)
{
$this->_homeUrl = $value;
}
/**
* Returns the request component.
* @return Request the request component
@ -69,48 +95,12 @@ class Application extends \yii\base\Application
}
/**
* Creates a URL using the given route and parameters.
*
* This method first normalizes the given route by converting a relative route into an absolute one.
* A relative route is a route without a leading slash. It is considered to be relative to the currently
* requested route. If the route is an empty string, it stands for the route of the currently active
* [[controller]]. Otherwise, the [[Controller::uniqueId]] will be prepended to the route.
*
* After normalizing the route, this method calls [[\yii\web\UrlManager::createUrl()]]
* to create a relative URL.
*
* @param string $route the route. This can be either an absolute or a relative route.
* @param array $params the parameters (name-value pairs) to be included in the generated URL
* @return string the created URL
* @throws InvalidParamException if a relative route is given and there is no active controller.
* @see createAbsoluteUrl
* Returns the user component.
* @return User the user component
*/
public function createUrl($route, $params = array())
public function getUser()
{
if (strncmp($route, '/', 1) !== 0) {
// a relative route
if ($this->controller !== null) {
$route = $route === '' ? $this->controller->route : $this->controller->uniqueId . '/' . $route;
} else {
throw new InvalidParamException('Relative route cannot be handled because there is no active controller.');
}
}
return $this->getUrlManager()->createUrl($route, $params);
}
/**
* Creates an absolute URL using the given route and parameters.
* This method first calls [[createUrl()]] to create a relative URL.
* It then prepends [[\yii\web\UrlManager::hostInfo]] to the URL to form an absolute one.
* @param string $route the route. This can be either an absolute or a relative route.
* See [[createUrl()]] for more details.
* @param array $params the parameters (name-value pairs)
* @return string the created URL
* @see createUrl
*/
public function createAbsoluteUrl($route, $params = array())
{
return $this->getUrlManager()->getHostInfo() . $this->createUrl($route, $params);
return $this->getComponent('user');
}
/**
@ -130,6 +120,9 @@ class Application extends \yii\base\Application
'session' => array(
'class' => 'yii\web\Session',
),
'user' => array(
'class' => 'yii\web\User',
),
));
}
}

58
framework/web/CacheSession.php

@ -15,7 +15,7 @@ use yii\base\InvalidConfigException;
* CacheSession implements a session component using cache as storage medium.
*
* The cache being used can be any cache application component.
* The ID of the cache application component is specified via [[cacheID]], which defaults to 'cache'.
* The ID of the cache application component is specified via [[cache]], which defaults to 'cache'.
*
* Beware, by definition cache storage are volatile, which means the data stored on them
* may be swapped out and get lost. Therefore, you must make sure the cache used by this component
@ -27,14 +27,27 @@ use yii\base\InvalidConfigException;
class CacheSession extends Session
{
/**
* @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
* @var Cache|string the cache object or the application component ID of the cache object.
* The session data will be stored using this cache object.
*
* After the CacheSession object is created, if you want to change this property,
* you should only assign it with a cache object.
*/
public $cacheID = 'cache';
public $cache = 'cache';
/**
* @var Cache the cache component
* Initializes the application component.
*/
private $_cache;
public function init()
{
parent::init();
if (is_string($this->cache)) {
$this->cache = Yii::$app->getComponent($this->cache);
}
if (!$this->cache instanceof Cache) {
throw new InvalidConfigException('CacheSession::cache must refer to the application component ID of a cache object.');
}
}
/**
* Returns a value indicating whether to use custom session storage.
@ -47,33 +60,6 @@ class CacheSession extends Session
}
/**
* Returns the cache instance used for storing session data.
* @return Cache the cache instance
* @throws InvalidConfigException if [[cacheID]] does not point to a valid application component.
*/
public function getCache()
{
if ($this->_cache === null) {
$cache = Yii::$app->getComponent($this->cacheID);
if ($cache instanceof Cache) {
$this->_cache = $cache;
} else {
throw new InvalidConfigException('CacheSession::cacheID must refer to the ID of a cache application component.');
}
}
return $this->_cache;
}
/**
* Sets the cache instance used by the session component.
* @param Cache $value the cache instance
*/
public function setCache($value)
{
$this->_cache = $value;
}
/**
* Session read handler.
* Do not call this method directly.
* @param string $id session ID
@ -81,7 +67,7 @@ class CacheSession extends Session
*/
public function readSession($id)
{
$data = $this->getCache()->get($this->calculateKey($id));
$data = $this->cache->get($this->calculateKey($id));
return $data === false ? '' : $data;
}
@ -94,7 +80,7 @@ class CacheSession extends Session
*/
public function writeSession($id, $data)
{
return $this->getCache()->set($this->calculateKey($id), $data, $this->getTimeout());
return $this->cache->set($this->calculateKey($id), $data, $this->getTimeout());
}
/**
@ -105,7 +91,7 @@ class CacheSession extends Session
*/
public function destroySession($id)
{
return $this->getCache()->delete($this->calculateKey($id));
return $this->cache->delete($this->calculateKey($id));
}
/**
@ -115,6 +101,6 @@ class CacheSession extends Session
*/
protected function calculateKey($id)
{
return $this->getCache()->buildKey(array(__CLASS__, $id));
return $this->cache->buildKey(array(__CLASS__, $id));
}
}

25
framework/web/Controller.php

@ -7,6 +7,9 @@
namespace yii\web;
use Yii;
use yii\helpers\Html;
/**
* Controller is the base class of Web controllers.
*
@ -16,4 +19,26 @@ namespace yii\web;
*/
class Controller extends \yii\base\Controller
{
/**
* Creates a URL using the given route and parameters.
*
* This method enhances [[UrlManager::createUrl()]] by supporting relative routes.
* A relative route is a route without a slash, such as "view". If the route is an empty
* string, [[route]] will be used; Otherwise, [[uniqueId]] will be prepended to a relative route.
*
* After this route conversion, the method This method calls [[UrlManager::createUrl()]]
* to create a URL.
*
* @param string $route the route. This can be either an absolute route or a relative route.
* @param array $params the parameters (name-value pairs) to be included in the generated URL
* @return string the created URL
*/
public function createUrl($route, $params = array())
{
if (strpos($route, '/') === false) {
// a relative route
$route = $route === '' ? $this->getRoute() : $this->getUniqueId() . '/' . $route;
}
return Yii::$app->getUrlManager()->createUrl($route, $params);
}
}

150
framework/web/DbSession.php

@ -15,24 +15,45 @@ use yii\base\InvalidConfigException;
/**
* DbSession extends [[Session]] by using database as session data storage.
*
* DbSession uses a DB application component to perform DB operations. The ID of the DB application
* component is specified via [[connectionID]] which defaults to 'db'.
*
* By default, DbSession stores session data in a DB table named 'tbl_session'. This table
* must be pre-created. The table name can be changed by setting [[sessionTableName]].
* The table should have the following structure:
* must be pre-created. The table name can be changed by setting [[sessionTable]].
*
* The following example shows how you can configure the application to use DbSession:
*
* ~~~
* 'session' => array(
* 'class' => 'yii\web\DbSession',
* // 'db' => 'mydb',
* // 'sessionTable' => 'my_session',
* )
* ~~~
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DbSession extends Session
{
/**
* @var Connection|string the DB connection object or the application component ID of the DB connection.
* After the DbSession object is created, if you want to change this property, you should only assign it
* with a DB connection object.
*/
public $db = 'db';
/**
* @var string the name of the DB table that stores the session data.
* The table should be pre-created as follows:
*
* ~~~
* CREATE TABLE tbl_session
* (
* id CHAR(32) PRIMARY KEY,
* id CHAR(40) NOT NULL PRIMARY KEY,
* expire INTEGER,
* data BLOB
* )
* ~~~
*
* where 'BLOB' refers to the BLOB-type of your preferred database. Below are the BLOB type
* that can be used for some popular databases:
* where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
* that can be used for some popular DBMS:
*
* - MySQL: LONGBLOB
* - PostgreSQL: BYTEA
@ -40,33 +61,24 @@ use yii\base\InvalidConfigException;
*
* When using DbSession in a production server, we recommend you create a DB index for the 'expire'
* column in the session table to improve the performance.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DbSession extends Session
{
/**
* @var string the ID of a {@link CDbConnection} application component. If not set, a SQLite database
* will be automatically created and used. The SQLite database file is
* is <code>protected/runtime/session-YiiVersion.db</code>.
*/
public $connectionID;
/**
* @var string the name of the DB table to store session content.
* Note, if {@link autoCreateSessionTable} is false and you want to create the DB table manually by yourself,
* you need to make sure the DB table is of the following structure:
* <pre>
* (id CHAR(32) PRIMARY KEY, expire INTEGER, data BLOB)
* </pre>
* @see autoCreateSessionTable
*/
public $sessionTableName = 'tbl_session';
public $sessionTable = 'tbl_session';
/**
* @var Connection the DB connection instance
* Initializes the DbSession component.
* This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
* @throws InvalidConfigException if [[db]] is invalid.
*/
private $_db;
public function init()
{
parent::init();
if (is_string($this->db)) {
$this->db = Yii::$app->getComponent($this->db);
}
if (!$this->db instanceof Connection) {
throw new InvalidConfigException("DbSession::db must be either a DB connection instance or the application component ID of a DB connection.");
}
}
/**
* Returns a value indicating whether to use custom session storage.
@ -94,25 +106,27 @@ class DbSession extends Session
parent::regenerateID(false);
$newID = session_id();
$db = $this->getDb();
$query = new Query;
$row = $query->from($this->sessionTableName)
$row = $query->from($this->sessionTable)
->where(array('id' => $oldID))
->createCommand($db)
->createCommand($this->db)
->queryRow();
if ($row !== false) {
if ($deleteOldSession) {
$db->createCommand()->update($this->sessionTableName, array(
'id' => $newID
), array('id' => $oldID))->execute();
$this->db->createCommand()
->update($this->sessionTable, array('id' => $newID), array('id' => $oldID))
->execute();
} else {
$row['id'] = $newID;
$db->createCommand()->insert($this->sessionTableName, $row)->execute();
$this->db->createCommand()
->insert($this->sessionTable, $row)
->execute();
}
} else {
// shouldn't reach here normally
$db->createCommand()->insert($this->sessionTableName, array(
$this->db->createCommand()
->insert($this->sessionTable, array(
'id' => $newID,
'expire' => time() + $this->getTimeout(),
))->execute();
@ -120,33 +134,6 @@ class DbSession extends Session
}
/**
* Returns the DB connection instance used for storing session data.
* @return Connection the DB connection instance
* @throws InvalidConfigException if [[connectionID]] does not point to a valid application component.
*/
public function getDb()
{
if ($this->_db === null) {
$db = Yii::$app->getComponent($this->connectionID);
if ($db instanceof Connection) {
$this->_db = $db;
} else {
throw new InvalidConfigException("DbSession::connectionID must refer to the ID of a DB application component.");
}
}
return $this->_db;
}
/**
* Sets the DB connection used by the session component.
* @param Connection $value the DB connection instance
*/
public function setDb($value)
{
$this->_db = $value;
}
/**
* Session read handler.
* Do not call this method directly.
* @param string $id session ID
@ -156,9 +143,9 @@ class DbSession extends Session
{
$query = new Query;
$data = $query->select(array('data'))
->from($this->sessionTableName)
->where('expire>:expire AND id=:id', array(':expire' => time(), ':id' => $id))
->createCommand($this->getDb())
->from($this->sessionTable)
->where('[[expire]]>:expire AND [[id]]=:id', array(':expire' => time(), ':id' => $id))
->createCommand($this->db)
->queryScalar();
return $data === false ? '' : $data;
}
@ -176,24 +163,23 @@ class DbSession extends Session
// http://us.php.net/manual/en/function.session-set-save-handler.php
try {
$expire = time() + $this->getTimeout();
$db = $this->getDb();
$query = new Query;
$exists = $query->select(array('id'))
->from($this->sessionTableName)
->from($this->sessionTable)
->where(array('id' => $id))
->createCommand($db)
->createCommand($this->db)
->queryScalar();
if ($exists === false) {
$db->createCommand()->insert($this->sessionTableName, array(
$this->db->createCommand()
->insert($this->sessionTable, array(
'id' => $id,
'data' => $data,
'expire' => $expire,
))->execute();
} else {
$db->createCommand()->update($this->sessionTableName, array(
'data' => $data,
'expire' => $expire
), array('id' => $id))->execute();
$this->db->createCommand()
->update($this->sessionTable, array('data' => $data, 'expire' => $expire), array('id' => $id))
->execute();
}
} catch (\Exception $e) {
if (YII_DEBUG) {
@ -213,8 +199,8 @@ class DbSession extends Session
*/
public function destroySession($id)
{
$this->getDb()->createCommand()
->delete($this->sessionTableName, array('id' => $id))
$this->db->createCommand()
->delete($this->sessionTable, array('id' => $id))
->execute();
return true;
}
@ -227,8 +213,8 @@ class DbSession extends Session
*/
public function gcSession($maxLifetime)
{
$this->getDb()->createCommand()
->delete($this->sessionTableName, 'expire<:expire', array(':expire' => time()))
$this->db->createCommand()
->delete($this->sessionTable, '[[expire]]<:expire', array(':expire' => time()))
->execute();
return true;
}

131
framework/web/HttpCache.php

@ -0,0 +1,131 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\ActionFilter;
use yii\base\Action;
/**
* @author Da:Sourcerer <webmaster@dasourcerer.net>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class HttpCache extends ActionFilter
{
/**
* @var callback a PHP callback that returns the UNIX timestamp of the last modification time.
* The callback's signature should be:
*
* ~~~
* function ($action, $params)
* ~~~
*
* where `$action` is the [[Action]] object that this filter is currently handling;
* `$params` takes the value of [[params]]. The callback should return a UNIX timestamp.
*/
public $lastModified;
/**
* @var callback a PHP callback that generates the Etag seed string.
* The callback's signature should be:
*
* ~~~
* function ($action, $params)
* ~~~
*
* where `$action` is the [[Action]] object that this filter is currently handling;
* `$params` takes the value of [[params]]. The callback should return a string serving
* as the seed for generating an Etag.
*/
public $etagSeed;
/**
* @var mixed additional parameters that should be passed to the [[lastModified]] and [[etagSeed]] callbacks.
*/
public $params;
/**
* @var string HTTP cache control header. If null, the header will not be sent.
*/
public $cacheControlHeader = 'Cache-Control: max-age=3600, public';
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$verb = Yii::$app->request->getRequestMethod();
if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
return true;
}
$lastModified = $etag = null;
if ($this->lastModified !== null) {
$lastModified = call_user_func($this->lastModified, $action, $this->params);
}
if ($this->etagSeed !== null) {
$seed = call_user_func($this->etagSeed, $action, $this->params);
$etag = $this->generateEtag($seed);
}
$this->sendCacheControlHeader();
if ($etag !== null) {
header("ETag: $etag");
}
if ($this->validateCache($lastModified, $etag)) {
header('HTTP/1.1 304 Not Modified');
return false;
}
if ($lastModified !== null) {
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
}
return true;
}
/**
* Validates if the HTTP cache contains valid content.
* @param integer $lastModified the calculated Last-Modified value in terms of a UNIX timestamp.
* If null, the Last-Modified header will not be validated.
* @param string $etag the calculated ETag value. If null, the ETag header will not be validated.
* @return boolean whether the HTTP cache is still valid.
*/
protected function validateCache($lastModified, $etag)
{
if ($lastModified !== null && (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < $lastModified)) {
return false;
} else {
return $etag === null || isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag;
}
}
/**
* Sends the cache control header to the client
* @see cacheControl
*/
protected function sendCacheControlHeader()
{
session_cache_limiter('public');
header('Pragma:', true);
if ($this->cacheControlHeader !== null) {
header($this->cacheControlHeader, true);
}
}
/**
* Generates an Etag from the given seed string.
* @param string $seed Seed for the ETag
* @return string the generated Etag
*/
protected function generateEtag($seed)
{
return '"' . base64_encode(sha1($seed, true)) . '"';
}
}

81
framework/web/Identity.php

@ -0,0 +1,81 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
/**
* Identity is the interface that should be implemented by a class providing identity information.
*
* This interface can typically be implemented by a user model class. For example, the following
* code shows how to implement this interface by a User ActiveRecord class:
*
* ~~~
* class User extends ActiveRecord implements Identity
* {
* public static function findIdentity($id)
* {
* return static::find($id);
* }
*
* public function getId()
* {
* return $this->id;
* }
*
* public function getAuthKey()
* {
* return $this->authKey;
* }
*
* public function validateAuthKey($authKey)
* {
* return $this->authKey === $authKey;
* }
* }
* ~~~
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
interface Identity
{
/**
* Finds an identity by the given ID.
* @param string|integer $id the ID to be looked for
* @return Identity the identity object that matches the given ID.
* Null should be returned if such an identity cannot be found
* or the identity is not in an active state (disabled, deleted, etc.)
*/
public static function findIdentity($id);
/**
* Returns an ID that can uniquely identify a user identity.
* @return string|integer an ID that uniquely identifies a user identity.
*/
public function getId();
/**
* Returns a key that can be used to check the validity of a given identity ID.
*
* The key should be unique for each individual user, and should be persistent
* so that it can be used to check the validity of the user identity.
*
* The space of such keys should be big enough to defeat potential identity attacks.
*
* This is required if [[User::enableAutoLogin]] is enabled.
* @return string a key that is used to check the validity of a given identity ID.
* @see validateAuthKey()
*/
public function getAuthKey();
/**
* Validates the given auth key.
*
* This is required if [[User::enableAutoLogin]] is enabled.
* @param string $authKey the given auth key
* @return boolean whether the given auth key is valid.
* @see getAuthKey()
*/
public function validateAuthKey($authKey);
}

12
framework/web/PageCache.php

@ -25,15 +25,9 @@ class PageCache extends ActionFilter
*/
public $varyByRoute = true;
/**
* @var View the view object that is used to create the fragment cache widget to implement page caching.
* If not set, the view registered with the application will be used.
* @var string the application component ID of the [[\yii\caching\Cache|cache]] object.
*/
public $view;
/**
* @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
*/
public $cacheID = 'cache';
public $cache = 'cache';
/**
* @var integer number of seconds that the data can remain valid in cache.
* Use 0 to indicate that the cached data will never expire.
@ -91,7 +85,7 @@ class PageCache extends ActionFilter
public function beforeAction($action)
{
$properties = array();
foreach (array('cacheID', 'duration', 'dependency', 'variations', 'enabled') as $name) {
foreach (array('cache', 'duration', 'dependency', 'variations', 'enabled') as $name) {
$properties[$name] = $this->$name;
}
$id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;

2
framework/web/Request.php

@ -530,7 +530,7 @@ class Request extends \yii\base\Request
* Returns the user IP address.
* @return string user IP address
*/
public function getUserHostAddress()
public function getUserIP()
{
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
}

106
framework/web/Response.php

@ -7,7 +7,9 @@
namespace yii\web;
use Yii;
use yii\helpers\FileHelper;
use yii\helpers\Html;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
@ -16,6 +18,14 @@ use yii\helpers\FileHelper;
class Response extends \yii\base\Response
{
/**
* @var integer the HTTP status code that should be used when redirecting in AJAX mode.
* This is used by [[redirect()]]. A 2xx code should normally be used for this purpose
* so that the AJAX handler will treat the response as a success.
* @see redirect
*/
public $ajaxRedirectCode = 278;
/**
* Sends a file to user.
* @param string $fileName file name
* @param string $content content to be set.
@ -106,56 +116,84 @@ class Response extends \yii\base\Response
* <li>addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)</li>
* </ul>
*/
public function xSendFile($filePath, $options=array())
public function xSendFile($filePath, $options = array())
{
if(!isset($options['forceDownload']) || $options['forceDownload'])
$disposition='attachment';
else
$disposition='inline';
if (!isset($options['forceDownload']) || $options['forceDownload']) {
$disposition = 'attachment';
} else {
$disposition = 'inline';
}
if(!isset($options['saveName']))
$options['saveName']=basename($filePath);
if (!isset($options['saveName'])) {
$options['saveName'] = basename($filePath);
}
if(!isset($options['mimeType']))
{
if(($options['mimeType']=CFileHelper::getMimeTypeByExtension($filePath))===null)
$options['mimeType']='text/plain';
if (!isset($options['mimeType'])) {
if (($options['mimeType'] = CFileHelper::getMimeTypeByExtension($filePath)) === null) {
$options['mimeType'] = 'text/plain';
}
}
if(!isset($options['xHeader']))
$options['xHeader']='X-Sendfile';
if (!isset($options['xHeader'])) {
$options['xHeader'] = 'X-Sendfile';
}
if($options['mimeType'] !== null)
header('Content-type: '.$options['mimeType']);
header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"');
if(isset($options['addHeaders']))
{
foreach($options['addHeaders'] as $header=>$value)
header($header.': '.$value);
if ($options['mimeType'] !== null) {
header('Content-type: ' . $options['mimeType']);
}
header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"');
if (isset($options['addHeaders'])) {
foreach ($options['addHeaders'] as $header => $value) {
header($header . ': ' . $value);
}
header(trim($options['xHeader']).': '.$filePath);
}
header(trim($options['xHeader']) . ': ' . $filePath);
if(!isset($options['terminate']) || $options['terminate'])
Yii::app()->end();
if (!isset($options['terminate']) || $options['terminate']) {
Yii::$app->end();
}
}
/**
* Redirects the browser to the specified URL.
* @param string $url URL to be redirected to. Note that when URL is not
* absolute (not starting with "/") it will be relative to current request URL.
* This method will send out a "Location" header to achieve the redirection.
* In AJAX mode, this normally will not work as expected unless there are some
* client-side JavaScript code handling the redirection. To help achieve this goal,
* this method will use [[ajaxRedirectCode]] as the HTTP status code when performing
* redirection in AJAX mode. The following JavaScript code may be used on the client
* side to handle the redirection response:
*
* ~~~
* $(document).ajaxSuccess(function(event, xhr, settings) {
* if (xhr.status == 278) {
* window.location = xhr.getResponseHeader('Location');
* }
* });
* ~~~
*
* @param array|string $url the URL to be redirected to. [[\yii\helpers\Html::url()]]
* will be used to normalize the URL. If the resulting URL is still a relative URL
* (one without host info), the current request host info will be used.
* @param boolean $terminate whether to terminate the current application
* @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html}
* @param integer $statusCode the HTTP status code. Defaults to 302.
* See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]]
* for details about HTTP status code.
* Note that if the request is an AJAX request, [[ajaxRedirectCode]] will be used instead.
*/
public function redirect($url,$terminate=true,$statusCode=302)
public function redirect($url, $terminate = true, $statusCode = 302)
{
if(strpos($url,'/')===0 && strpos($url,'//')!==0)
$url=$this->getHostInfo().$url;
header('Location: '.$url, true, $statusCode);
if($terminate)
Yii::app()->end();
$url = Html::url($url);
if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
$url = Yii::$app->getRequest()->getHostInfo() . $url;
}
if (Yii::$app->getRequest()->getIsAjaxRequest()) {
$statusCode = $this->ajaxRedirectCode;
}
header('Location: ' . $url, true, $statusCode);
if ($terminate) {
Yii::$app->end();
}
}
/**
* Returns the cookie collection.
@ -178,6 +216,6 @@ class Response extends \yii\base\Response
*/
public function getCookies()
{
return \Yii::$app->getRequest()->getCookies();
return Yii::$app->getRequest()->getCookies();
}
}

177
framework/web/Session.php

@ -12,13 +12,15 @@ use yii\base\Component;
use yii\base\InvalidParamException;
/**
* Session provides session-level data management and the related configurations.
* Session provides session data management and the related configurations.
*
* Session is a Web application component that can be accessed via `Yii::$app->session`.
* To start the session, call [[open()]]; To complete and send out session data, call [[close()]];
* To destroy the session, call [[destroy()]].
*
* If [[autoStart]] is set true, the session will be started automatically
* when the application component is initialized by the application.
* By default, [[autoStart]] is true which means the session will be started automatically
* when the session component is accessed the first time.
*
* Session can be used like an array to set and get session data. For example,
*
@ -37,22 +39,11 @@ use yii\base\InvalidParamException;
* [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
* [[destroySession()]] and [[gcSession()]].
*
* Session is a Web application component that can be accessed via
* `Yii::$app->session`.
*
* @property boolean $useCustomStorage read-only. Whether to use custom storage.
* @property boolean $isActive Whether the session has started.
* @property string $id The current session ID.
* @property string $name The current session name.
* @property string $savePath The current session save path, defaults to '/tmp'.
* @property array $cookieParams The session cookie parameters.
* @property string $cookieMode How to use cookie to store session ID. Defaults to 'Allow'.
* @property float $gcProbability The probability (percentage) that the gc (garbage collection) process is started on every session initialization.
* @property boolean $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to false.
* @property integer $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds.
* @property SessionIterator $iterator An iterator for traversing the session variables.
* @property integer $count The number of session variables.
* @property array $keys The list of session variable names.
* Session also supports a special type of session data, called *flash messages*.
* A flash message is available only in the current request and the next request.
* After that, it will be deleted automatically. Flash messages are particularly
* useful for displaying confirmation messages. To use flash messages, simply
* call methods such as [[setFlash()]], [[getFlash()]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
@ -63,6 +54,17 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @var boolean whether the session should be automatically started when the session component is initialized.
*/
public $autoStart = true;
/**
* @var string the name of the session variable that stores the flash message data.
*/
public $flashVar = '__flash';
/**
* @var array parameter-value pairs to override default session cookie parameters
*/
public $cookieParams = array(
'httponly' => true
);
/**
* Initializes the application component.
@ -89,6 +91,8 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
return false;
}
private $_opened = false;
/**
* Starts the session.
*/
@ -97,10 +101,12 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
// this is available in PHP 5.4.0+
if (function_exists('session_status')) {
if (session_status() == PHP_SESSION_ACTIVE) {
$this->_opened = true;
return;
}
}
if (!$this->_opened) {
if ($this->getUseCustomStorage()) {
@session_set_save_handler(
array($this, 'openSession'),
@ -112,12 +118,19 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
);
}
$this->setCookieParams($this->cookieParams);
@session_start();
if (session_id() == '') {
$this->_opened = false;
$error = error_get_last();
$message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
Yii::warning($message, __CLASS__);
Yii::error($message, __METHOD__);
} else {
$this->_opened = true;
$this->updateFlashCounters();
}
}
}
@ -126,6 +139,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function close()
{
$this->_opened = false;
if (session_id() !== '') {
@session_write_close();
}
@ -152,7 +166,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
return session_status() == PHP_SESSION_ACTIVE;
} else {
// this is not very reliable
return session_id() !== '';
return $this->_opened && session_id() !== '';
}
}
@ -462,18 +476,18 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
/**
* Adds a session variable.
* Note, if the specified name already exists, the old value will be removed first.
* @param mixed $key session variable name
* If the specified name already exists, the old value will be overwritten.
* @param string $key session variable name
* @param mixed $value session variable value
*/
public function add($key, $value)
public function set($key, $value)
{
$_SESSION[$key] = $value;
}
/**
* Removes a session variable.
* @param mixed $key the name of the session variable to be removed
* @param string $key the name of the session variable to be removed
* @return mixed the removed value, null if no such session variable.
*/
public function remove($key)
@ -490,7 +504,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
/**
* Removes all session variables
*/
public function clear()
public function removeAll()
{
foreach (array_keys($_SESSION) as $key) {
unset($_SESSION[$key]);
@ -501,7 +515,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @param mixed $key session variable name
* @return boolean whether there is the named session variable
*/
public function contains($key)
public function has($key)
{
return isset($_SESSION[$key]);
}
@ -515,6 +529,115 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
}
/**
* Updates the counters for flash messages and removes outdated flash messages.
* This method should only be called once in [[init()]].
*/
protected function updateFlashCounters()
{
$counters = $this->get($this->flashVar, array());
if (is_array($counters)) {
foreach ($counters as $key => $count) {
if ($count) {
unset($counters[$key], $_SESSION[$key]);
} else {
$counters[$key]++;
}
}
$_SESSION[$this->flashVar] = $counters;
} else {
// fix the unexpected problem that flashVar doesn't return an array
unset($_SESSION[$this->flashVar]);
}
}
/**
* Returns a flash message.
* A flash message is available only in the current request and the next request.
* @param string $key the key identifying the flash message
* @param mixed $defaultValue value to be returned if the flash message does not exist.
* @return mixed the flash message
*/
public function getFlash($key, $defaultValue = null)
{
$counters = $this->get($this->flashVar, array());
return isset($counters[$key]) ? $this->get($key, $defaultValue) : $defaultValue;
}
/**
* Returns all flash messages.
* @return array flash messages (key => message).
*/
public function getAllFlashes()
{
$counters = $this->get($this->flashVar, array());
$flashes = array();
foreach (array_keys($counters) as $key) {
if (isset($_SESSION[$key])) {
$flashes[$key] = $_SESSION[$key];
}
}
return $flashes;
}
/**
* Stores a flash message.
* A flash message is available only in the current request and the next request.
* @param string $key the key identifying the flash message. Note that flash messages
* and normal session variables share the same name space. If you have a normal
* session variable using the same name, its value will be overwritten by this method.
* @param mixed $value flash message
*/
public function setFlash($key, $value)
{
$counters = $this->get($this->flashVar, array());
$counters[$key] = 0;
$_SESSION[$key] = $value;
$_SESSION[$this->flashVar] = $counters;
}
/**
* Removes a flash message.
* Note that flash messages will be automatically removed after the next request.
* @param string $key the key identifying the flash message. Note that flash messages
* and normal session variables share the same name space. If you have a normal
* session variable using the same name, it will be removed by this method.
* @return mixed the removed flash message. Null if the flash message does not exist.
*/
public function removeFlash($key)
{
$counters = $this->get($this->flashVar, array());
$value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
unset($counters[$key], $_SESSION[$key]);
$_SESSION[$this->flashVar] = $counters;
return $value;
}
/**
* Removes all flash messages.
* Note that flash messages and normal session variables share the same name space.
* If you have a normal session variable using the same name, it will be removed
* by this method.
*/
public function removeAllFlashes()
{
$counters = $this->get($this->flashVar, array());
foreach (array_keys($counters) as $key) {
unset($_SESSION[$key]);
}
unset($_SESSION[$this->flashVar]);
}
/**
* Returns a value indicating whether there is a flash message associated with the specified key.
* @param string $key key identifying the flash message
* @return boolean whether the specified flash message exists
*/
public function hasFlash($key)
{
return $this->getFlash($key) !== null;
}
/**
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean

30
framework/web/UrlManager.php

@ -9,6 +9,7 @@ namespace yii\web;
use Yii;
use yii\base\Component;
use yii\caching\Cache;
/**
* UrlManager handles HTTP request parsing and creation of URLs based on a set of rules.
@ -49,11 +50,14 @@ class UrlManager extends Component
*/
public $routeVar = 'r';
/**
* @var string the ID of the cache component that is used to cache the parsed URL rules.
* Defaults to 'cache' which refers to the primary cache component registered with the application.
* Set this property to false if you do not want to cache the URL rules.
* @var Cache|string the cache object or the application component ID of the cache object.
* Compiled URL rules will be cached through this cache object, if it is available.
*
* After the UrlManager object is created, if you want to change this property,
* you should only assign it with a cache object.
* Set this property to null if you do not want to cache the URL rules.
*/
public $cacheID = 'cache';
public $cache = 'cache';
/**
* @var string the default class name for creating URL rule instances
* when it is not specified in [[rules]].
@ -65,11 +69,14 @@ class UrlManager extends Component
/**
* Initializes the application component.
* Initializes UrlManager.
*/
public function init()
{
parent::init();
if (is_string($this->cache)) {
$this->cache = Yii::$app->getComponent($this->cache);
}
$this->compileRules();
}
@ -81,13 +88,10 @@ class UrlManager extends Component
if (!$this->enablePrettyUrl || $this->rules === array()) {
return;
}
/**
* @var $cache \yii\caching\Cache
*/
if ($this->cacheID !== false && ($cache = Yii::$app->getComponent($this->cacheID)) !== null) {
$key = $cache->buildKey(__CLASS__);
if ($this->cache instanceof Cache) {
$key = $this->cache->buildKey(__CLASS__);
$hash = md5(json_encode($this->rules));
if (($data = $cache->get($key)) !== false && isset($data[1]) && $data[1] === $hash) {
if (($data = $this->cache->get($key)) !== false && isset($data[1]) && $data[1] === $hash) {
$this->rules = $data[0];
return;
}
@ -100,8 +104,8 @@ class UrlManager extends Component
$this->rules[$i] = Yii::createObject($rule);
}
if (isset($cache)) {
$cache->set($key, array($this->rules, $hash));
if ($this->cache instanceof Cache) {
$this->cache->set($key, array($this->rules, $hash));
}
}

866
framework/web/User.php

File diff suppressed because it is too large Load Diff

34
framework/web/UserEvent.php

@ -0,0 +1,34 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use yii\base\Event;
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class UserEvent extends Event
{
/**
* @var Identity the identity object associated with this event
*/
public $identity;
/**
* @var boolean whether the login is cookie-based. This property is only meaningful
* for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_AFTER_LOGIN]] events.
*/
public $cookieBased;
/**
* @var boolean whether the login or logout should proceed.
* Event handlers may modify this property to determine whether the login or logout should proceed.
* This property is only meaningful for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_BEFORE_LOGOUT]] events.
*/
public $isValid = true;
}

38
framework/widgets/ActiveForm.php

@ -110,8 +110,7 @@ class ActiveForm extends Widget
*/
public function error($model, $attribute, $options = array())
{
$attribute = $this->normalizeAttributeName($attribute);
$this->getInputName($model, $attribute);
$attribute = $this->getAttributeName($attribute);
$tag = isset($options['tag']) ? $options['tag'] : 'div';
unset($options['tag']);
$error = $model->getFirstError($attribute);
@ -126,15 +125,19 @@ class ActiveForm extends Widget
*/
public function label($model, $attribute, $options = array())
{
$attribute = $this->normalizeAttributeName($attribute);
$label = $model->getAttributeLabel($attribute);
return Html::label(Html::encode($label), isset($options['for']) ? $options['for'] : null, $options);
$attribute = $this->getAttributeName($attribute);
$label = isset($options['label']) ? $options['label'] : Html::encode($model->getAttributeLabel($attribute));
$for = array_key_exists('for', $options) ? $options['for'] : $this->getInputId($model, $attribute);
return Html::label($label, $for, $options);
}
public function input($type, $model, $attribute, $options = array())
{
$value = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::input($type, $name, $value, $options);
}
@ -162,6 +165,9 @@ class ActiveForm extends Widget
{
$value = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::textarea($name, $value, $options);
}
@ -172,6 +178,9 @@ class ActiveForm extends Widget
if (!array_key_exists('uncheck', $options)) {
$options['unchecked'] = '0';
}
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::radio($name, $checked, $value, $options);
}
@ -182,6 +191,9 @@ class ActiveForm extends Widget
if (!array_key_exists('uncheck', $options)) {
$options['unchecked'] = '0';
}
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::checkbox($name, $checked, $value, $options);
}
@ -189,6 +201,9 @@ class ActiveForm extends Widget
{
$checked = $this->getAttributeValue($model, $attribute);
$name = $this->getInputName($model, $attribute);
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::dropDownList($name, $checked, $items, $options);
}
@ -199,6 +214,9 @@ class ActiveForm extends Widget
if (!array_key_exists('unselect', $options)) {
$options['unselect'] = '0';
}
if (!array_key_exists('id', $options)) {
$options['id'] = $this->getInputId($model, $attribute);
}
return Html::listBox($name, $checked, $items, $options);
}
@ -228,7 +246,7 @@ class ActiveForm extends Widget
if (isset($this->modelMap[$class])) {
$class = $this->modelMap[$class];
} elseif (($pos = strrpos($class, '\\')) !== false) {
$class = substr($class, $pos);
$class = substr($class, $pos + 1);
}
if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
throw new InvalidParamException('Attribute name must contain word characters only.');
@ -245,6 +263,12 @@ class ActiveForm extends Widget
}
}
public function getInputId($model, $attribute)
{
$name = $this->getInputName($model, $attribute);
return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name);
}
public function getAttributeValue($model, $attribute)
{
if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
@ -267,7 +291,7 @@ class ActiveForm extends Widget
}
}
public function normalizeAttributeName($attribute)
public function getAttributeName($attribute)
{
if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
return $matches[2];

8
framework/widgets/Clip.php

@ -22,11 +22,6 @@ class Clip extends Widget
*/
public $id;
/**
* @var View the view object for keeping the clip. If not set, the view registered with the application
* will be used.
*/
public $view;
/**
* @var boolean whether to render the clip content in place. Defaults to false,
* meaning the captured clip will not be displayed.
*/
@ -51,7 +46,6 @@ class Clip extends Widget
if ($this->renderClip) {
echo $clip;
}
$view = $this->view !== null ? $this->view : Yii::$app->getView();
$view->clips[$this->id] = $clip;
$this->view->clips[$this->id] = $clip;
}
}

21
framework/widgets/ContentDecorator.php

@ -7,10 +7,8 @@
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\base\View;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
@ -19,15 +17,10 @@ use yii\base\View;
class ContentDecorator extends Widget
{
/**
* @var View the view object for rendering [[viewName]]. If not set, the view registered with the application
* will be used.
* @var string the view file that will be used to decorate the content enclosed by this widget.
* This can be specified as either the view file path or path alias.
*/
public $view;
/**
* @var string the name of the view that will be used to decorate the content enclosed by this widget.
* Please refer to [[View::findViewFile()]] on how to set this property.
*/
public $viewName;
public $viewFile;
/**
* @var array the parameters (name=>value) to be extracted and made available in the decorative view.
*/
@ -38,8 +31,8 @@ class ContentDecorator extends Widget
*/
public function init()
{
if ($this->viewName === null) {
throw new InvalidConfigException('ContentDecorator::viewName must be set.');
if ($this->viewFile === null) {
throw new InvalidConfigException('ContentDecorator::viewFile must be set.');
}
ob_start();
ob_implicit_flush(false);
@ -53,7 +46,7 @@ class ContentDecorator extends Widget
{
$params = $this->params;
$params['content'] = ob_get_clean();
$view = $this->view !== null ? $this->view : Yii::$app->getView();
echo $view->render($this->viewName, $params);
// render under the existing context
echo $this->view->renderFile($this->viewFile, $params);
}
}

75
framework/widgets/FragmentCache.php

@ -8,7 +8,6 @@
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\caching\Cache;
use yii\caching\Dependency;
@ -20,9 +19,11 @@ use yii\caching\Dependency;
class FragmentCache extends Widget
{
/**
* @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
* @var Cache|string the cache object or the application component ID of the cache object.
* After the FragmentCache object is created, if you want to change this property,
* you should only assign it with a cache object.
*/
public $cacheID = 'cache';
public $cache = 'cache';
/**
* @var integer number of seconds that the data can remain valid in cache.
* Use 0 to indicate that the cached data will never expire.
@ -62,29 +63,25 @@ class FragmentCache extends Widget
*/
public $enabled = true;
/**
* @var \yii\base\View the view object within which this widget is used. If not set,
* the view registered with the application will be used. This is mainly used by dynamic content feature.
*/
public $view;
/**
* @var array a list of placeholders for embedding dynamic contents. This property
* is used internally to implement the content caching feature. Do not modify it.
*/
public $dynamicPlaceholders;
/**
* Marks the start of content to be cached.
* Content displayed after this method call and before {@link endCache()}
* will be captured and saved in cache.
* This method does nothing if valid content is already found in cache.
* Initializes the FragmentCache object.
*/
public function init()
{
if ($this->view === null) {
$this->view = Yii::$app->getView();
parent::init();
if (!$this->enabled) {
$this->cache = null;
} elseif (is_string($this->cache)) {
$this->cache = Yii::$app->getComponent($this->cache);
}
if ($this->getCache() !== null && $this->getCachedContent() === false) {
if ($this->getCachedContent() === false) {
$this->view->cacheStack[] = $this;
ob_start();
ob_implicit_flush(false);
@ -101,14 +98,14 @@ class FragmentCache extends Widget
{
if (($content = $this->getCachedContent()) !== false) {
echo $content;
} elseif (($cache = $this->getCache()) !== null) {
} elseif ($this->cache instanceof Cache) {
$content = ob_get_clean();
array_pop($this->view->cacheStack);
if (is_array($this->dependency)) {
$this->dependency = Yii::createObject($this->dependency);
}
$data = array($content, $this->dynamicPlaceholders);
$cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
$this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
if ($this->view->cacheStack === array() && !empty($this->dynamicPlaceholders)) {
$content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
@ -130,9 +127,9 @@ class FragmentCache extends Widget
{
if ($this->_content === null) {
$this->_content = false;
if (($cache = $this->getCache()) !== null) {
if ($this->cache instanceof Cache) {
$key = $this->calculateKey();
$data = $cache->get($key);
$data = $this->cache->get($key);
if (is_array($data) && count($data) === 2) {
list ($content, $placeholders) = $data;
if (is_array($placeholders) && count($placeholders) > 0) {
@ -172,42 +169,6 @@ class FragmentCache extends Widget
$factors[] = $factor;
}
}
return $this->getCache()->buildKey($factors);
}
/**
* @var Cache
*/
private $_cache;
/**
* Returns the cache instance used for storing content.
* @return Cache the cache instance. Null is returned if the cache component is not available
* or [[enabled]] is false.
* @throws InvalidConfigException if [[cacheID]] does not point to a valid application component.
*/
public function getCache()
{
if (!$this->enabled) {
return null;
}
if ($this->_cache === null) {
$cache = Yii::$app->getComponent($this->cacheID);
if ($cache instanceof Cache) {
$this->_cache = $cache;
} else {
throw new InvalidConfigException('FragmentCache::cacheID must refer to the ID of a cache application component.');
}
}
return $this->_cache;
}
/**
* Sets the cache instance used by the session component.
* @param Cache $value the cache instance
*/
public function setCache($value)
{
$this->_cache = $value;
return $this->cache->buildKey($factors);
}
}

4
tests/unit/MysqlTestCase.php

@ -4,7 +4,7 @@ namespace yiiunit;
class MysqlTestCase extends TestCase
{
function __construct()
protected function setUp()
{
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) {
$this->markTestSkipped('pdo and pdo_mysql extensions are required.');
@ -15,7 +15,7 @@ class MysqlTestCase extends TestCase
* @param bool $reset whether to clean up the test database
* @return \yii\db\Connection
*/
function getConnection($reset = true)
public function getConnection($reset = true)
{
$params = $this->getParam('mysql');
$db = new \yii\db\Connection;

17
tests/unit/data/base/InvalidRulesModel.php

@ -0,0 +1,17 @@
<?php
namespace yiiunit\data\base;
use yii\base\Model;
/**
* InvalidRulesModel
*/
class InvalidRulesModel extends Model
{
public function rules()
{
return array(
array('test'),
);
}
}

21
tests/unit/data/base/Singer.php

@ -0,0 +1,21 @@
<?php
namespace yiiunit\data\base;
use yii\base\Model;
/**
* Singer
*/
class Singer extends Model
{
public $fistName;
public $lastName;
public function rules()
{
return array(
array('lastName', 'default', 'value' => 'Lennon'),
array('lastName', 'required'),
array('underscore_style', 'yii\validators\CaptchaValidator'),
);
}
}

39
tests/unit/data/base/Speaker.php

@ -0,0 +1,39 @@
<?php
namespace yiiunit\data\base;
use yii\base\Model;
/**
* Speaker
*/
class Speaker extends Model
{
public $firstName;
public $lastName;
public $customLabel;
public $underscore_style;
protected $protectedProperty;
private $_privateProperty;
public function attributeLabels()
{
return array(
'customLabel' => 'This is the custom label',
);
}
public function rules()
{
return array(
);
}
public function scenarios()
{
return array(
'test' => array('firstName', 'lastName', '!underscore_style'),
);
}
}

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

@ -352,7 +352,7 @@ class NewComponent extends Component
public function raiseEvent()
{
$this->trigger('click', new Event($this));
$this->trigger('click', new Event);
}
}

24
tests/unit/framework/base/DictionaryTest.php

@ -61,7 +61,7 @@ class DictionaryTest extends \yiiunit\TestCase
{
$this->dictionary->add('key3',$this->item3);
$this->assertEquals(3,$this->dictionary->getCount());
$this->assertTrue($this->dictionary->contains('key3'));
$this->assertTrue($this->dictionary->has('key3'));
$this->dictionary[] = 'test';
}
@ -70,28 +70,28 @@ class DictionaryTest extends \yiiunit\TestCase
{
$this->dictionary->remove('key1');
$this->assertEquals(1,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key1'));
$this->assertTrue(!$this->dictionary->has('key1'));
$this->assertTrue($this->dictionary->remove('unknown key')===null);
}
public function testClear()
public function testRemoveAll()
{
$this->dictionary->add('key3',$this->item3);
$this->dictionary->clear();
$this->dictionary->removeAll();
$this->assertEquals(0,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key1') && !$this->dictionary->contains('key2'));
$this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2'));
$this->dictionary->add('key3',$this->item3);
$this->dictionary->clear(true);
$this->dictionary->removeAll(true);
$this->assertEquals(0,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key1') && !$this->dictionary->contains('key2'));
$this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2'));
}
public function testContains()
public function testHas()
{
$this->assertTrue($this->dictionary->contains('key1'));
$this->assertTrue($this->dictionary->contains('key2'));
$this->assertFalse($this->dictionary->contains('key3'));
$this->assertTrue($this->dictionary->has('key1'));
$this->assertTrue($this->dictionary->has('key2'));
$this->assertFalse($this->dictionary->has('key3'));
}
public function testFromArray()
@ -162,7 +162,7 @@ class DictionaryTest extends \yiiunit\TestCase
unset($this->dictionary['key2']);
$this->assertEquals(2,$this->dictionary->getCount());
$this->assertTrue(!$this->dictionary->contains('key2'));
$this->assertTrue(!$this->dictionary->has('key2'));
unset($this->dictionary['unknown key']);
}

203
tests/unit/framework/base/ModelTest.php

@ -0,0 +1,203 @@
<?php
namespace yiiunit\framework\base;
use yii\base\Model;
use yiiunit\TestCase;
use yiiunit\data\base\Speaker;
use yiiunit\data\base\Singer;
use yiiunit\data\base\InvalidRulesModel;
/**
* ModelTest
*/
class ModelTest extends TestCase
{
public function testGetAttributeLalel()
{
$speaker = new Speaker();
$this->assertEquals('First Name', $speaker->getAttributeLabel('firstName'));
$this->assertEquals('This is the custom label', $speaker->getAttributeLabel('customLabel'));
$this->assertEquals('Underscore Style', $speaker->getAttributeLabel('underscore_style'));
}
public function testGetAttributes()
{
$speaker = new Speaker();
$speaker->firstName = 'Qiang';
$speaker->lastName = 'Xue';
$this->assertEquals(array(
'firstName' => 'Qiang',
'lastName' => 'Xue',
'customLabel' => null,
'underscore_style' => null,
), $speaker->getAttributes());
$this->assertEquals(array(
'firstName' => 'Qiang',
'lastName' => 'Xue',
), $speaker->getAttributes(array('firstName', 'lastName')));
$this->assertEquals(array(
'firstName' => 'Qiang',
'lastName' => 'Xue',
), $speaker->getAttributes(null, array('customLabel', 'underscore_style')));
$this->assertEquals(array(
'firstName' => 'Qiang',
), $speaker->getAttributes(array('firstName', 'lastName'), array('lastName', 'customLabel', 'underscore_style')));
}
public function testSetAttributes()
{
// by default mass assignment doesn't work at all
$speaker = new Speaker();
$speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test'));
$this->assertNull($speaker->firstName);
$this->assertNull($speaker->underscore_style);
// in the test scenario
$speaker = new Speaker();
$speaker->setScenario('test');
$speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test'));
$this->assertNull($speaker->underscore_style);
$this->assertEquals('Qiang', $speaker->firstName);
$speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test'), false);
$this->assertEquals('test', $speaker->underscore_style);
$this->assertEquals('Qiang', $speaker->firstName);
}
public function testActiveAttributes()
{
// by default mass assignment doesn't work at all
$speaker = new Speaker();
$this->assertEmpty($speaker->activeAttributes());
$speaker = new Speaker();
$speaker->setScenario('test');
$this->assertEquals(array('firstName', 'lastName', 'underscore_style'), $speaker->activeAttributes());
}
public function testIsAttributeSafe()
{
// by default mass assignment doesn't work at all
$speaker = new Speaker();
$this->assertFalse($speaker->isAttributeSafe('firstName'));
$speaker = new Speaker();
$speaker->setScenario('test');
$this->assertTrue($speaker->isAttributeSafe('firstName'));
}
public function testErrors()
{
$speaker = new Speaker();
$this->assertEmpty($speaker->getErrors());
$this->assertEmpty($speaker->getErrors('firstName'));
$this->assertEmpty($speaker->getFirstErrors());
$this->assertFalse($speaker->hasErrors());
$this->assertFalse($speaker->hasErrors('firstName'));
$speaker->addError('firstName', 'Something is wrong!');
$this->assertEquals(array('firstName' => array('Something is wrong!')), $speaker->getErrors());
$this->assertEquals(array('Something is wrong!'), $speaker->getErrors('firstName'));
$speaker->addError('firstName', 'Totally wrong!');
$this->assertEquals(array('firstName' => array('Something is wrong!', 'Totally wrong!')), $speaker->getErrors());
$this->assertEquals(array('Something is wrong!', 'Totally wrong!'), $speaker->getErrors('firstName'));
$this->assertTrue($speaker->hasErrors());
$this->assertTrue($speaker->hasErrors('firstName'));
$this->assertFalse($speaker->hasErrors('lastName'));
$this->assertEquals(array('Something is wrong!'), $speaker->getFirstErrors());
$this->assertEquals('Something is wrong!', $speaker->getFirstError('firstName'));
$this->assertNull($speaker->getFirstError('lastName'));
$speaker->addError('lastName', 'Another one!');
$this->assertEquals(array(
'firstName' => array(
'Something is wrong!',
'Totally wrong!',
),
'lastName' => array('Another one!'),
), $speaker->getErrors());
$speaker->clearErrors('firstName');
$this->assertEquals(array(
'lastName' => array('Another one!'),
), $speaker->getErrors());
$speaker->clearErrors();
$this->assertEmpty($speaker->getErrors());
$this->assertFalse($speaker->hasErrors());
}
public function testArraySyntax()
{
$speaker = new Speaker();
// get
$this->assertNull($speaker['firstName']);
// isset
$this->assertFalse(isset($speaker['firstName']));
// set
$speaker['firstName'] = 'Qiang';
$this->assertEquals('Qiang', $speaker['firstName']);
$this->assertTrue(isset($speaker['firstName']));
// iteration
$attributes = array();
foreach($speaker as $key => $attribute) {
$attributes[$key] = $attribute;
}
$this->assertEquals(array(
'firstName' => 'Qiang',
'lastName' => null,
'customLabel' => null,
'underscore_style' => null,
), $attributes);
// unset
unset($speaker['firstName']);
// exception isn't expected here
$this->assertNull($speaker['firstName']);
$this->assertFalse(isset($speaker['firstName']));
}
public function testDefaults()
{
$singer = new Model();
$this->assertEquals(array(), $singer->rules());
$this->assertEquals(array(), $singer->attributeLabels());
}
public function testDefaultScenarios()
{
$singer = new Singer();
$this->assertEquals(array('default' => array('lastName', 'underscore_style')), $singer->scenarios());
}
public function testIsAttributeRequired()
{
$singer = new Singer();
$this->assertFalse($singer->isAttributeRequired('firstName'));
$this->assertTrue($singer->isAttributeRequired('lastName'));
}
public function testCreateValidators()
{
$this->setExpectedException('yii\base\InvalidConfigException', 'Invalid validation rule: a rule must be an array specifying both attribute names and validator type.');
$invalid = new InvalidRulesModel();
$invalid->createValidators();
}
}

14
tests/unit/framework/base/VectorTest.php

@ -101,26 +101,26 @@ class VectorTest extends \yiiunit\TestCase
$this->vector->removeAt(2);
}
public function testClear()
public function testRemoveAll()
{
$this->vector->add($this->item3);
$this->vector->clear();
$this->vector->removeAll();
$this->assertEquals(0,$this->vector->getCount());
$this->assertEquals(-1,$this->vector->indexOf($this->item1));
$this->assertEquals(-1,$this->vector->indexOf($this->item2));
$this->vector->add($this->item3);
$this->vector->clear(true);
$this->vector->removeAll(true);
$this->assertEquals(0,$this->vector->getCount());
$this->assertEquals(-1,$this->vector->indexOf($this->item1));
$this->assertEquals(-1,$this->vector->indexOf($this->item2));
}
public function testContains()
public function testHas()
{
$this->assertTrue($this->vector->contains($this->item1));
$this->assertTrue($this->vector->contains($this->item2));
$this->assertFalse($this->vector->contains($this->item3));
$this->assertTrue($this->vector->has($this->item1));
$this->assertTrue($this->vector->has($this->item2));
$this->assertFalse($this->vector->has($this->item3));
}
public function testIndexOf()

2
tests/unit/framework/caching/DbCacheTest.php

@ -11,7 +11,7 @@ class DbCacheTest extends CacheTest
private $_cacheInstance;
private $_connection;
function __construct()
protected function setUp()
{
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) {
$this->markTestSkipped('pdo and pdo_mysql extensions are required.');

1
tests/unit/framework/db/ConnectionTest.php

@ -59,7 +59,6 @@ class ConnectionTest extends \yiiunit\MysqlTestCase
$this->assertEquals('`table`', $connection->quoteTableName('`table`'));
$this->assertEquals('`schema`.`table`', $connection->quoteTableName('schema.table'));
$this->assertEquals('`schema`.`table`', $connection->quoteTableName('schema.`table`'));
$this->assertEquals('[[table]]', $connection->quoteTableName('[[table]]'));
$this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}'));
$this->assertEquals('(table)', $connection->quoteTableName('(table)'));
}

2
tests/unit/framework/util/ArrayHelperTest.php → tests/unit/framework/helpers/ArrayHelperTest.php

@ -1,6 +1,6 @@
<?php
namespace yiiunit\framework\util;
namespace yiiunit\framework\helpers;
use yii\helpers\ArrayHelper;

2
tests/unit/framework/util/HtmlTest.php → tests/unit/framework/helpers/HtmlTest.php

@ -1,6 +1,6 @@
<?php
namespace yiiunit\framework\util;
namespace yiiunit\framework\helpers;
use Yii;
use yii\helpers\Html;

73
tests/unit/framework/helpers/StringHelperTest.php

@ -0,0 +1,73 @@
<?php
namespace yiiunit\framework\helpers;
use \yii\helpers\StringHelper as StringHelper;
/**
* StringHelperTest
*/
class StringHelperTest extends \yii\test\TestCase
{
public function testStrlen()
{
$this->assertEquals(4, StringHelper::strlen('this'));
$this->assertEquals(6, StringHelper::strlen('это'));
}
public function testSubstr()
{
$this->assertEquals('th', StringHelper::substr('this', 0, 2));
$this->assertEquals('э', StringHelper::substr('это', 0, 2));
}
public function testPluralize()
{
$testData = array(
'move' => 'moves',
'foot' => 'feet',
'child' => 'children',
'human' => 'humans',
'man' => 'men',
'staff' => 'staff',
'tooth' => 'teeth',
'person' => 'people',
'mouse' => 'mice',
'touch' => 'touches',
'hash' => 'hashes',
'shelf' => 'shelves',
'potato' => 'potatoes',
'bus' => 'buses',
'test' => 'tests',
'car' => 'cars',
);
foreach($testData as $testIn => $testOut) {
$this->assertEquals($testOut, StringHelper::pluralize($testIn));
$this->assertEquals(ucfirst($testOut), ucfirst(StringHelper::pluralize($testIn)));
}
}
public function testCamel2words()
{
$this->assertEquals('Camel Case', StringHelper::camel2words('camelCase'));
$this->assertEquals('Lower Case', StringHelper::camel2words('lower_case'));
$this->assertEquals('Tricky Stuff It Is Testing', StringHelper::camel2words(' tricky_stuff.it-is testing... '));
}
public function testCamel2id()
{
$this->assertEquals('post-tag', StringHelper::camel2id('PostTag'));
$this->assertEquals('post_tag', StringHelper::camel2id('PostTag', '_'));
$this->assertEquals('post-tag', StringHelper::camel2id('postTag'));
$this->assertEquals('post_tag', StringHelper::camel2id('postTag', '_'));
}
public function testId2camel()
{
$this->assertEquals('PostTag', StringHelper::id2camel('post-tag'));
$this->assertEquals('PostTag', StringHelper::id2camel('post_tag', '_'));
$this->assertEquals('PostTag', StringHelper::id2camel('post-tag'));
$this->assertEquals('PostTag', StringHelper::id2camel('post_tag', '_'));
}
}

19
tests/unit/framework/web/UrlManagerTest.php

@ -11,6 +11,7 @@ class UrlManagerTest extends \yiiunit\TestCase
// default setting with '/' as base url
$manager = new UrlManager(array(
'baseUrl' => '/',
'cache' => null,
));
$url = $manager->createUrl('post/view');
$this->assertEquals('/?r=post/view', $url);
@ -20,6 +21,7 @@ class UrlManagerTest extends \yiiunit\TestCase
// default setting with '/test/' as base url
$manager = new UrlManager(array(
'baseUrl' => '/test/',
'cache' => null,
));
$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
$this->assertEquals('/test/?r=post/view&id=1&title=sample+post', $url);
@ -28,18 +30,21 @@ class UrlManagerTest extends \yiiunit\TestCase
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'baseUrl' => '/',
'cache' => null,
));
$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
$this->assertEquals('/post/view?id=1&title=sample+post', $url);
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'baseUrl' => '/test/',
'cache' => null,
));
$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
$this->assertEquals('/test/post/view?id=1&title=sample+post', $url);
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'baseUrl' => '/test/index.php',
'cache' => null,
));
$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
$this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url);
@ -49,7 +54,7 @@ class UrlManagerTest extends \yiiunit\TestCase
// pretty URL with rules
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'cacheID' => false,
'cache' => null,
'rules' => array(
array(
'pattern' => 'post/<id>/<title>',
@ -66,7 +71,7 @@ class UrlManagerTest extends \yiiunit\TestCase
// pretty URL with rules and suffix
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'cacheID' => false,
'cache' => null,
'rules' => array(
array(
'pattern' => 'post/<id>/<title>',
@ -87,6 +92,7 @@ class UrlManagerTest extends \yiiunit\TestCase
$manager = new UrlManager(array(
'baseUrl' => '/',
'hostInfo' => 'http://www.example.com',
'cache' => null,
));
$url = $manager->createAbsoluteUrl('post/view', array('id' => 1, 'title' => 'sample post'));
$this->assertEquals('http://www.example.com/?r=post/view&id=1&title=sample+post', $url);
@ -94,7 +100,9 @@ class UrlManagerTest extends \yiiunit\TestCase
public function testParseRequest()
{
$manager = new UrlManager;
$manager = new UrlManager(array(
'cache' => null,
));
$request = new Request;
// default setting without 'r' param
@ -115,6 +123,7 @@ class UrlManagerTest extends \yiiunit\TestCase
// pretty URL without rules
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'cache' => null,
));
// empty pathinfo
$request->pathInfo = '';
@ -136,7 +145,7 @@ class UrlManagerTest extends \yiiunit\TestCase
// pretty URL rules
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'cacheID' => false,
'cache' => null,
'rules' => array(
array(
'pattern' => 'post/<id>/<title>',
@ -169,7 +178,7 @@ class UrlManagerTest extends \yiiunit\TestCase
$manager = new UrlManager(array(
'enablePrettyUrl' => true,
'suffix' => '.html',
'cacheID' => false,
'cache' => null,
'rules' => array(
array(
'pattern' => 'post/<id>/<title>',

4
tests/unit/framework/web/UrlRuleTest.php

@ -10,7 +10,7 @@ class UrlRuleTest extends \yiiunit\TestCase
{
public function testCreateUrl()
{
$manager = new UrlManager;
$manager = new UrlManager(array('cache' => null));
$suites = $this->getTestsForCreateUrl();
foreach ($suites as $i => $suite) {
list ($name, $config, $tests) = $suite;
@ -25,7 +25,7 @@ class UrlRuleTest extends \yiiunit\TestCase
public function testParseRequest()
{
$manager = new UrlManager;
$manager = new UrlManager(array('cache' => null));
$request = new Request;
$suites = $this->getTestsForParseRequest();
foreach ($suites as $i => $suite) {

Loading…
Cancel
Save