Browse Source

Merge branch 'master' of github.com:yiisoft/yii2 into mssql

Conflicts:
	yii/db/Connection.php
tags/2.0.0-beta
resurtm 12 years ago
parent
commit
9873442d70
  1. 3
      .gitignore
  2. 14
      .travis.yml
  3. 9
      app/index.php
  4. 0
      apps/bootstrap/assets/.gitignore
  5. 0
      apps/bootstrap/css/bootstrap-responsive.css
  6. 0
      apps/bootstrap/css/bootstrap-responsive.min.css
  7. 0
      apps/bootstrap/css/bootstrap.css
  8. 0
      apps/bootstrap/css/bootstrap.min.css
  9. 0
      apps/bootstrap/css/site.css
  10. 0
      apps/bootstrap/img/glyphicons-halflings-white.png
  11. 0
      apps/bootstrap/img/glyphicons-halflings.png
  12. 14
      apps/bootstrap/index.php
  13. 0
      apps/bootstrap/js/bootstrap.js
  14. 0
      apps/bootstrap/js/bootstrap.min.js
  15. 0
      apps/bootstrap/protected/.htaccess
  16. 0
      apps/bootstrap/protected/config/assets.php
  17. 15
      apps/bootstrap/protected/config/main.php
  18. 9
      apps/bootstrap/protected/controllers/SiteController.php
  19. 2
      apps/bootstrap/protected/models/ContactForm.php
  20. 0
      apps/bootstrap/protected/models/LoginForm.php
  21. 0
      apps/bootstrap/protected/models/User.php
  22. 0
      apps/bootstrap/protected/runtime/.gitignore
  23. 29
      apps/bootstrap/protected/views/layouts/main.php
  24. 1
      apps/bootstrap/protected/views/site/about.php
  25. 15
      apps/bootstrap/protected/views/site/contact.php
  26. 0
      apps/bootstrap/protected/views/site/index.php
  27. 5
      apps/bootstrap/protected/views/site/login.php
  28. 4
      build/build
  29. 79
      composer.json
  30. 212
      composer.lock
  31. 4
      docs/api/db/ActiveRecord.md
  32. 0
      docs/guide/active-record.md
  33. 0
      docs/guide/authentication.md
  34. 0
      docs/guide/authorization.md
  35. 2
      docs/guide/bootstrap.md
  36. 3
      docs/guide/caching.md
  37. 0
      docs/guide/console.md
  38. 0
      docs/guide/dao.md
  39. 3
      docs/guide/error.md
  40. 0
      docs/guide/extension.md
  41. 0
      docs/guide/form.md
  42. 0
      docs/guide/gii.md
  43. 0
      docs/guide/i18n.md
  44. 2
      docs/guide/index.md
  45. 5
      docs/guide/installation.md
  46. 0
      docs/guide/logging.md
  47. 319
      docs/guide/migration.md
  48. 3
      docs/guide/mvc.md
  49. 181
      docs/guide/performance.md
  50. 0
      docs/guide/query-builder.md
  51. 0
      docs/guide/security.md
  52. 3
      docs/guide/template.md
  53. 0
      docs/guide/testing.md
  54. 0
      docs/guide/theming.md
  55. 459
      docs/guide/upgrade-from-v1.md
  56. 0
      docs/guide/upgrade.md
  57. 3
      docs/guide/url.md
  58. 0
      docs/guide/validation.md
  59. BIN
      docs/internals/exception_hierarchy.png
  60. BIN
      docs/internals/exception_hierarchy.vsd
  61. 3
      docs/view_renderers.md
  62. 17
      framework/console/webapp/config.php
  63. 10
      framework/console/webapp/default/index.php
  64. 20
      framework/console/webapp/default/protected/config/main.php
  65. 15
      framework/console/webapp/default/protected/controllers/SiteController.php
  66. 17
      framework/console/webapp/default/protected/views/layouts/main.php
  67. 1
      framework/console/webapp/default/protected/views/site/index.php
  68. 470
      framework/helpers/base/ConsoleColor.php
  69. 13
      phpunit.xml.dist
  70. 18
      readme.md
  71. 50
      tests/unit/DatabaseTestCase.php
  72. 2
      tests/unit/bootstrap.php
  73. 2
      tests/unit/data/ar/Customer.php
  74. 8
      tests/unit/data/config.php
  75. BIN
      tests/unit/data/i18n/test.mo
  76. 64
      tests/unit/data/i18n/test.po
  77. 6
      tests/unit/data/mysql.sql
  78. 348
      tests/unit/data/sqlite.sql
  79. 1
      tests/unit/framework/YiiBaseTest.php
  80. 6
      tests/unit/framework/caching/ApcCacheTest.php
  81. 3
      tests/unit/framework/caching/DbCacheTest.php
  82. 3
      tests/unit/framework/db/ActiveRecordTest.php
  83. 2
      tests/unit/framework/db/CommandTest.php
  84. 11
      tests/unit/framework/db/ConnectionTest.php
  85. 2
      tests/unit/framework/db/QueryTest.php
  86. 12
      tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php
  87. 21
      tests/unit/framework/db/sqlite/SqliteCommandTest.php
  88. 47
      tests/unit/framework/db/sqlite/SqliteConnectionTest.php
  89. 20
      tests/unit/framework/db/sqlite/SqliteQueryTest.php
  90. 21
      tests/unit/framework/helpers/ArrayHelperTest.php
  91. 2
      tests/unit/framework/helpers/JsonTest.php
  92. 44
      tests/unit/framework/helpers/StringHelperTest.php
  93. 12
      tests/unit/framework/helpers/VarDumperTest.php
  94. 14
      tests/unit/framework/i18n/GettextMessageSourceTest.php
  95. 95
      tests/unit/framework/i18n/GettextMoFileTest.php
  96. 95
      tests/unit/framework/i18n/GettextPoFileTest.php
  97. 248
      tests/unit/framework/rbac/ManagerTestBase.php
  98. 34
      tests/unit/framework/rbac/PhpManagerTest.php
  99. 7
      tests/unit/phpunit.xml
  100. 2
      tests/web/app/index.php
  101. Some files were not shown because too many files have changed in this diff Show More

3
.gitignore vendored

@ -11,3 +11,6 @@ nbproject
# windows thumbnail cache
Thumbs.db
# composer vendor dir
/yii/vendor

14
.travis.yml

@ -0,0 +1,14 @@
language: php
php:
- 5.3
- 5.4
- 5.5
env:
- DB=mysql
before_script:
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS yiitest;'; fi"
script: phpunit

9
app/index.php

@ -1,9 +0,0 @@
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
require(__DIR__ . '/../framework/yii.php');
$config = require(__DIR__ . '/protected/config/main.php');
$application = new yii\web\Application($config);
$application->run();

0
app/assets/.gitignore → apps/bootstrap/assets/.gitignore vendored

0
app/css/bootstrap-responsive.css → apps/bootstrap/css/bootstrap-responsive.css vendored

0
app/css/bootstrap-responsive.min.css → apps/bootstrap/css/bootstrap-responsive.min.css vendored

0
app/css/bootstrap.css → apps/bootstrap/css/bootstrap.css vendored

0
app/css/bootstrap.min.css → apps/bootstrap/css/bootstrap.min.css vendored

0
app/css/site.css → apps/bootstrap/css/site.css vendored

0
app/img/glyphicons-halflings-white.png → apps/bootstrap/img/glyphicons-halflings-white.png

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

0
app/img/glyphicons-halflings.png → apps/bootstrap/img/glyphicons-halflings.png

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

14
apps/bootstrap/index.php

@ -0,0 +1,14 @@
<?php
// comment out the following line to disable debug mode
defined('YII_DEBUG') or define('YII_DEBUG', true);
$frameworkPath = __DIR__ . '/../../yii';
require($frameworkPath . '/Yii.php');
// Register Composer autoloader
@include($frameworkPath . '/vendor/autoload.php');
$config = require(__DIR__ . '/protected/config/main.php');
$application = new yii\web\Application($config);
$application->run();

0
app/js/bootstrap.js → apps/bootstrap/js/bootstrap.js vendored

0
app/js/bootstrap.min.js → apps/bootstrap/js/bootstrap.min.js vendored

0
app/protected/.htaccess → apps/bootstrap/protected/.htaccess

0
app/protected/config/assets.php → apps/bootstrap/protected/config/assets.php

15
app/protected/config/main.php → apps/bootstrap/protected/config/main.php

@ -3,6 +3,12 @@
return array(
'id' => 'hello',
'basePath' => dirname(__DIR__),
'preload' => array('log'),
'modules' => array(
'debug' => array(
'class' => 'yii\debug\Module',
)
),
'components' => array(
'cache' => array(
'class' => 'yii\caching\FileCache',
@ -14,6 +20,15 @@ return array(
'assetManager' => array(
'bundles' => require(__DIR__ . '/assets.php'),
),
'log' => array(
'class' => 'yii\logging\Router',
'targets' => array(
'file' => array(
'class' => 'yii\logging\FileTarget',
'levels' => array('error', 'warning'),
),
),
),
),
'params' => array(
'adminEmail' => 'admin@example.com',

9
app/protected/controllers/SiteController.php → apps/bootstrap/protected/controllers/SiteController.php

@ -6,6 +6,15 @@ use app\models\ContactForm;
class SiteController extends Controller
{
public function actions()
{
return array(
'captcha' => array(
'class' => 'yii\web\CaptchaAction',
),
);
}
public function actionIndex()
{
echo $this->render('index');

2
app/protected/models/ContactForm.php → apps/bootstrap/protected/models/ContactForm.php

@ -26,7 +26,7 @@ class ContactForm extends Model
// email has to be a valid email address
array('email', 'email'),
// verifyCode needs to be entered correctly
//array('verifyCode', 'captcha', 'allowEmpty' => !Captcha::checkRequirements()),
array('verifyCode', 'captcha'),
);
}

0
app/protected/models/LoginForm.php → apps/bootstrap/protected/models/LoginForm.php

0
app/protected/models/User.php → apps/bootstrap/protected/models/User.php

0
app/protected/runtime/.gitignore → apps/bootstrap/protected/runtime/.gitignore vendored

29
app/protected/views/layouts/main.php → apps/bootstrap/protected/views/layouts/main.php

@ -1,9 +1,11 @@
<?php
use yii\helpers\Html;
use yii\widgets\Menu;
/**
* @var $this \yii\base\View
* @var $content string
*/
use yii\helpers\Html;
$this->registerAssetBundle('app');
?>
<?php $this->beginPage(); ?>
@ -23,22 +25,26 @@ $this->registerAssetBundle('app');
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<ul class="nav">
<li><?php echo Html::a('Home', Yii::$app->homeUrl); ?></li>
<li><?php echo Html::a('About', array('/site/about')); ?></li>
<li><?php echo Html::a('Contact', array('/site/contact')); ?></li>
<?php if (Yii::$app->user->isGuest): ?>
<li><?php echo Html::a('Login', array('/site/login')); ?></li>
<?php else: ?>
<li><?php echo Html::a('Logout (' . Html::encode(Yii::$app->user->identity->username) . ')', array('/site/logout')); ?></li>
<?php endif; ?>
</ul>
<?php $this->widget(Menu::className(), array(
'options' => array('class' => 'nav'),
'items' => array(
array('label' => 'Home', 'url' => array('/site/index')),
array('label' => 'About', 'url' => array('/site/about')),
array('label' => 'Contact', 'url' => array('/site/contact')),
Yii::$app->user->isGuest ?
array('label' => 'Login', 'url' => array('/site/login')) :
array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => array('/site/logout')),
),
)); ?>
</div>
</div>
</div>
<!-- /.navbar -->
</div>
<?php $this->widget('yii\widgets\Breadcrumbs', array(
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : array(),
)); ?>
<?php echo $content; ?>
<hr>
@ -52,6 +58,7 @@ $this->registerAssetBundle('app');
</div>
<?php $this->endBody(); ?>
</div>
<?php $this->widget('yii\debug\Toolbar'); ?>
</body>
</html>
<?php $this->endPage(); ?>

1
app/protected/views/site/about.php → apps/bootstrap/protected/views/site/about.php

@ -4,6 +4,7 @@ use yii\helpers\Html;
* @var yii\base\View $this
*/
$this->title = 'About';
$this->params['breadcrumbs'][] = $this->title;
?>
<h1><?php echo Html::encode($this->title); ?></h1>

15
app/protected/views/site/contact.php → apps/bootstrap/protected/views/site/contact.php

@ -1,11 +1,15 @@
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\widgets\Captcha;
/**
* @var yii\base\View $this
* @var yii\widgets\ActiveForm $form
* @var app\models\ContactForm $model
*/
$this->title = 'Contact';
$this->params['breadcrumbs'][] = $this->title;
?>
<h1><?php echo Html::encode($this->title); ?></h1>
@ -19,7 +23,7 @@ $this->title = 'Contact';
If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
</p>
<?php $form = $this->beginWidget('yii\widgets\ActiveForm', array(
<?php $form = $this->beginWidget(ActiveForm::className(), array(
'options' => array('class' => 'form-horizontal'),
'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')),
)); ?>
@ -27,6 +31,15 @@ $this->title = 'Contact';
<?php echo $form->field($model, 'email')->textInput(); ?>
<?php echo $form->field($model, 'subject')->textInput(); ?>
<?php echo $form->field($model, 'body')->textArea(array('rows' => 6)); ?>
<?php
$field = $form->field($model, 'verifyCode');
echo $field->begin();
echo $field->label();
$this->widget(Captcha::className());
echo Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium'));
echo $field->error();
echo $field->end();
?>
<div class="form-actions">
<?php echo Html::submitButton('Submit', null, null, array('class' => 'btn btn-primary')); ?>
</div>

0
app/protected/views/site/index.php → apps/bootstrap/protected/views/site/index.php

5
app/protected/views/site/login.php → apps/bootstrap/protected/views/site/login.php

@ -1,17 +1,20 @@
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/**
* @var yii\base\View $this
* @var yii\widgets\ActiveForm $form
* @var app\models\LoginForm $model
*/
$this->title = 'Login';
$this->params['breadcrumbs'][] = $this->title;
?>
<h1><?php echo Html::encode($this->title); ?></h1>
<p>Please fill out the following fields to login:</p>
<?php $form = $this->beginWidget('yii\widgets\ActiveForm', array('options' => array('class' => 'form-horizontal'))); ?>
<?php $form = $this->beginWidget(ActiveForm::className(), array('options' => array('class' => 'form-horizontal'))); ?>
<?php echo $form->field($model, 'username')->textInput(); ?>
<?php echo $form->field($model, 'password')->passwordInput(); ?>
<?php echo $form->field($model, 'rememberMe')->checkbox(); ?>

4
build/build

@ -11,10 +11,10 @@
// fcgi doesn't have STDIN defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
require(__DIR__ . '/../framework/yii.php');
require(__DIR__ . '/../framework/Yii.php');
$id = 'yiic-build';
$basePath = __DIR__;
$application = new yii\console\Application($id, $basePath);
$application = new yii\console\Application(array('id' => $id, 'basePath' => $basePath));
$application->run();

79
composer.json

@ -0,0 +1,79 @@
{
"name": "yiisoft/yii2",
"description": "Yii2 Web Programming Framework",
"keywords": ["yii", "framework"],
"homepage": "http://www.yiiframework.com/",
"type": "library",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com",
"homepage": "http://www.yiiframework.com/",
"role": "Founder and project lead"
},
{
"name": "Alexander Makarov",
"email": "sam@rmcreative.ru",
"homepage": "http://rmcreative.ru/",
"role": "Core framework development"
},
{
"name": "Maurizio Domba",
"homepage": "http://mdomba.info/",
"role": "Core framework development"
},
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc",
"homepage": "http://cebe.cc/",
"role": "Core framework development"
},
{
"name": "Timur Ruziev",
"email": "resurtm@gmail.com",
"homepage": "http://resurtm.com/",
"role": "Core framework development"
},
{
"name": "Paul Klimov",
"email": "klimov.paul@gmail.com",
"role": "Core framework development"
},
{
"name": "Wei Zhuo",
"email": "weizhuo@gmail.com",
"role": "Project site maintenance and development"
},
{
"name": "Sebastián Thierer",
"email": "sebas@artfos.com",
"role": "Component development"
},
{
"name": "Jeffrey Winesett",
"email": "jefftulsa@gmail.com",
"role": "Documentation and marketing"
}
],
"support": {
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2"
},
"config": {
"vendor-dir": "yii/vendor"
},
"bin": [
"yii/yiic"
],
"require": {
"php": ">=5.3.0",
"michelf/php-markdown": "1.3",
"twig/twig": "1.12.*",
"smarty/smarty": "3.1.*",
"ezyang/htmlpurifier": "v4.5.0"
}
}

212
composer.lock generated

@ -0,0 +1,212 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "7d46ce9c4d8d5f4ecae1611ea8f0b49c",
"packages": [
{
"name": "ezyang/htmlpurifier",
"version": "v4.5.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "v4.5.0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/v4.5.0",
"reference": "v4.5.0",
"shasum": ""
},
"require": {
"php": ">=5.2"
},
"type": "library",
"autoload": {
"psr-0": {
"HTMLPurifier": "library/"
},
"files": [
"library/HTMLPurifier.composer.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"time": "2013-02-18 00:04:08"
},
{
"name": "michelf/php-markdown",
"version": "1.3",
"source": {
"type": "git",
"url": "https://github.com/michelf/php-markdown.git",
"reference": "1.3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/michelf/php-markdown/zipball/1.3",
"reference": "1.3",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-lib": "1.3.x-dev"
}
},
"autoload": {
"psr-0": {
"Michelf": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Michel Fortin",
"email": "michel.fortin@michelf.ca",
"homepage": "http://michelf.ca/",
"role": "Developer"
},
{
"name": "John Gruber",
"homepage": "http://daringfireball.net/"
}
],
"description": "PHP Markdown",
"homepage": "http://michelf.ca/projects/php-markdown/",
"keywords": [
"markdown"
],
"time": "2013-04-11 18:53:11"
},
{
"name": "smarty/smarty",
"version": "v3.1.13",
"source": {
"type": "svn",
"url": "http://smarty-php.googlecode.com/svn",
"reference": "/tags/v3.1.13/@4699"
},
"require": {
"php": ">=5.2"
},
"type": "library",
"autoload": {
"classmap": [
"distribution/libs/Smarty.class.php",
"distribution/libs/SmartyBC.class.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Monte Ohrt",
"email": "monte@ohrt.com"
},
{
"name": "Uwe Tews",
"email": "uwe.tews@googlemail.com"
},
{
"name": "Rodney Rehm",
"email": "rodney.rehm@medialize.de"
}
],
"description": "Smarty - the compiling PHP template engine",
"homepage": "http://www.smarty.net",
"keywords": [
"templating"
],
"time": "2013-01-26 12:03:52"
},
{
"name": "twig/twig",
"version": "v1.12.3",
"source": {
"type": "git",
"url": "https://github.com/fabpot/Twig.git",
"reference": "v1.12.3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fabpot/Twig/zipball/v1.12.3",
"reference": "v1.12.3",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.12-dev"
}
},
"autoload": {
"psr-0": {
"Twig_": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "http://twig.sensiolabs.org",
"keywords": [
"templating"
],
"time": "2013-04-08 12:40:11"
}
],
"packages-dev": [
],
"aliases": [
],
"minimum-stability": "stable",
"stability-flags": [
],
"platform": {
"php": ">=5.3.0"
},
"platform-dev": [
]
}

4
docs/api/db/ActiveRecord.md

@ -412,7 +412,7 @@ class Customer extends \yii\db\ActiveRecord
/**
* @param ActiveQuery $query
*/
public function active($query)
public static function active($query)
{
$query->andWhere('status = 1');
}
@ -435,7 +435,7 @@ class Customer extends \yii\db\ActiveRecord
* @param ActiveQuery $query
* @param integer $age
*/
public function olderThan($query, $age = 30)
public static function olderThan($query, $age = 30)
{
$query->andWhere('age > :age', array(':age' => $age));
}

0
docs/guide/active-record.md

0
docs/guide/authentication.md

0
docs/guide/authorization.md

2
docs/guide/bootstrap.md

@ -7,7 +7,7 @@ If you have installed Yii under a Web-accessible folder, you should be able to
access this application through the following URL:
~~~
http://localhost/yii/app/index.php
http://localhost/yii/apps/bootstrap/index.php
~~~

3
docs/guide/caching.md

@ -0,0 +1,3 @@
Caching
=======

0
docs/guide/console.md

0
docs/guide/dao.md

3
docs/guide/error.md

@ -0,0 +1,3 @@
Error Handling
==============

0
docs/guide/extension.md

0
docs/guide/form.md

0
docs/guide/gii.md

0
docs/guide/i18n.md

2
docs/guide/index.md

@ -27,4 +27,4 @@
* [Performance Tuning](performance.md)
* [Testing](testing.md)
* [Automatic Code Generation](gii.md)
* [Upgrading from 1.1 to 2.0](upgrade.md)
* [Upgrading from 1.1 to 2.0](upgrade-from-v1.md)

5
docs/guide/installation.md

@ -34,7 +34,7 @@ Recommended Apache Configuration
--------------------------------
Yii is ready to work with a default Apache web server configuration.
The `.htaccess` files in Yii framework and application folders restrict
The `.htaccess` files in Yii framework and application folders deny
access to the restricted resources. To hide the bootstrap file (usually `index.php`)
in your URLs you can add `mod_rewrite` instructions to the `.htaccess` file
in your document root or to the virtual host configuration:
@ -108,4 +108,5 @@ server {
}
~~~
Using this configuration you can set `cgi.fix_pathinfo=0` in php.ini to avoid many unnecessary system stat() calls.
Using this configuration you can set `cgi.fix_pathinfo=0` in php.ini to avoid
many unnecessary system `stat()` calls.

0
docs/guide/logging.md

319
docs/guide/migration.md

@ -0,0 +1,319 @@
Database Migration
==================
Like source code, the structure of a database is evolving as we develop and maintain
a database-driven application. For example, during development, we may want to
add a new table; or after the application is put into production, we may realize
the need of adding an index on a column. It is important to keep track of these
structural database changes (called **migration**) like we do with our source
code. If the source code and the database are out of sync, it is very likely
the whole system may break. For this reason, Yii provides a database migration
tool that can keep track of database migration history, apply new migrations,
or revert existing ones.
The following steps show how we can use database migration during development:
1. Tim creates a new migration (e.g. create a new table)
2. Tim commits the new migration into source control system (e.g. GIT, Mercurial)
3. Doug updates from source control system and receives the new migration
4. Doug applies the migration to his local development database
Yii supports database migration via the `yiic migrate` command line tool. This
tool supports creating new migrations, applying/reverting/redoing migrations, and
showing migration history and new migrations.
Creating Migrations
-------------------
To create a new migration (e.g. create a news table), we run the following command:
~~~
yiic migrate/create <name>
~~~
The required `name` parameter specifies a very brief description of the migration
(e.g. `create_news_table`). As we will show in the following, the `name` parameter
is used as part of a PHP class name. Therefore, it should only contain letters,
digits and/or underscore characters.
~~~
yiic migrate/create create_news_table
~~~
The above command will create under the `protected/migrations` directory a new
file named `m101129_185401_create_news_table.php` which contains the following
initial code:
~~~
[php]
class m101129_185401_create_news_table extends \yii\db\Migration
{
public function up()
{
}
public function down()
{
echo "m101129_185401_create_news_table cannot be reverted.\n";
return false;
}
}
~~~
Notice that the class name is the same as the file name which is of the pattern
`m<timestamp>_<name>`, where `<timestamp>` refers to the UTC timestamp (in the
format of `yymmdd_hhmmss`) when the migration is created, and `<name>` is taken
from the command's `name` parameter.
The `up()` method should contain the code implementing the actual database
migration, while the `down()` method may contain the code reverting what is
done in `up()`.
Sometimes, it is impossible to implement `down()`. For example, if we delete
table rows in `up()`, we will not be able to recover them in `down()`. In this
case, the migration is called irreversible, meaning we cannot roll back to
a previous state of the database. In the above generated code, the `down()`
method returns `false` to indicate that the migration cannot be reverted.
As an example, let's show the migration about creating a news table.
~~~
[php]
class m101129_185401_create_news_table extends \yii\db\Migration
{
public function up()
{
$this->db->createCommand()->createTable('tbl_news, array(
'id' => 'pk',
'title' => 'string NOT NULL',
'content' => 'text',
))->execute();
}
public function down()
{
$this->db->createCommand()->dropTable('tbl_news')->execute();
}
}
~~~
The base class [\yii\db\Migration] exposes a database connection via `db`
property. You can use it for manipulating data and schema of a database.
Transactional Migrations
------------------------
While performing complex DB migrations, we usually want to make sure that each
migration succeed or fail as a whole so that the database maintains the
consistency and integrity. In order to achieve this goal, we can exploit
DB transactions.
We could explicitly start a DB transaction and enclose the rest of the DB-related
code within the transaction, like the following:
~~~
[php]
class m101129_185401_create_news_table extends \yii\db\Migration
{
public function up()
{
$transaction=$this->getDbConnection()->beginTransaction();
try
{
$this->db->createCommand()->createTable('tbl_news, array(
'id' => 'pk',
'title' => 'string NOT NULL',
'content' => 'text',
))->execute();
$transaction->commit();
}
catch(Exception $e)
{
echo "Exception: ".$e->getMessage()."\n";
$transaction->rollback();
return false;
}
}
// ...similar code for down()
}
~~~
> Note: Not all DBMS support transactions. And some DB queries cannot be put
> into a transaction. In this case, you will have to implement `up()` and
> `down()`, instead. And for MySQL, some SQL statements may cause
> [implicit commit](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html).
Applying Migrations
-------------------
To apply all available new migrations (i.e., make the local database up-to-date),
run the following command:
~~~
yiic migrate
~~~
The command will show the list of all new migrations. If you confirm to apply
the migrations, it will run the `up()` method in every new migration class, one
after another, in the order of the timestamp value in the class name.
After applying a migration, the migration tool will keep a record in a database
table named `tbl_migration`. This allows the tool to identify which migrations
have been applied and which are not. If the `tbl_migration` table does not exist,
the tool will automatically create it in the database specified by the `db`
application component.
Sometimes, we may only want to apply one or a few new migrations. We can use the
following command:
~~~
yiic migrate/up 3
~~~
This command will apply the 3 new migrations. Changing the value 3 will allow
us to change the number of migrations to be applied.
We can also migrate the database to a specific version with the following command:
~~~
yiic migrate/to 101129_185401
~~~
That is, we use the timestamp part of a migration name to specify the version
that we want to migrate the database to. If there are multiple migrations between
the last applied migration and the specified migration, all these migrations
will be applied. If the specified migration has been applied before, then all
migrations applied after it will be reverted (to be described in the next section).
Reverting Migrations
--------------------
To revert the last one or several applied migrations, we can use the following
command:
~~~
yiic migrate/down [step]
~~~
where the optional `step` parameter specifies how many migrations to be reverted
back. It defaults to 1, meaning reverting back the last applied migration.
As we described before, not all migrations can be reverted. Trying to revert
such migrations will throw an exception and stop the whole reverting process.
Redoing Migrations
------------------
Redoing migrations means first reverting and then applying the specified migrations.
This can be done with the following command:
~~~
yiic migrate/redo [step]
~~~
where the optional `step` parameter specifies how many migrations to be redone.
It defaults to 1, meaning redoing the last migration.
Showing Migration Information
-----------------------------
Besides applying and reverting migrations, the migration tool can also display
the migration history and the new migrations to be applied.
~~~
yiic migrate/history [limit]
yiic migrate/new [limit]
~~~
where the optional parameter `limit` specifies the number of migrations to be
displayed. If `limit` is not specified, all available migrations will be displayed.
The first command shows the migrations that have been applied, while the second
command shows the migrations that have not been applied.
Modifying Migration History
---------------------------
Sometimes, we may want to modify the migration history to a specific migration
version without actually applying or reverting the relevant migrations. This
often happens when developing a new migration. We can use the following command
to achieve this goal.
~~~
yiic migrate/mark 101129_185401
~~~
This command is very similar to `yiic migrate/to` command, except that it only
modifies the migration history table to the specified version without applying
or reverting the migrations.
Customizing Migration Command
-----------------------------
There are several ways to customize the migration command.
### Use Command Line Options
The migration command comes with four options that can be specified in command
line:
* `interactive`: boolean, specifies whether to perform migrations in an
interactive mode. Defaults to true, meaning the user will be prompted when
performing a specific migration. You may set this to false should the
migrations be done in a background process.
* `migrationPath`: string, specifies the directory storing all migration class
files. This must be specified in terms of a path alias, and the corresponding
directory must exist. If not specified, it will use the `migrations`
sub-directory under the application base path.
* `migrationTable`: string, specifies the name of the database table for storing
migration history information. It defaults to `tbl_migration`. The table
structure is `version varchar(255) primary key, apply_time integer`.
* `connectionID`: string, specifies the ID of the database application component.
Defaults to 'db'.
* `templateFile`: string, specifies the path of the file to be served as the code
template for generating the migration classes. This must be specified in terms
of a path alias (e.g. `application.migrations.template`). If not set, an
internal template will be used. Inside the template, the token `{ClassName}`
will be replaced with the actual migration class name.
To specify these options, execute the migrate command using the following format
~~~
yiic migrate/up --option1=value1 --option2=value2 ...
~~~
For example, if we want to migrate for a `forum` module whose migration files
are located within the module's `migrations` directory, we can use the following
command:
~~~
yiic migrate/up --migrationPath=ext.forum.migrations
~~~
### Configure Command Globally
While command line options allow us to configure the migration command
on-the-fly, sometimes we may want to configure the command once for all.
For example, we may want to use a different table to store the migration history,
or we may want to use a customized migration template. We can do so by modifying
the console application's configuration file like the following,
```php
TBD
```
Now if we run the `migrate` command, the above configurations will take effect
without requiring us to enter the command line options every time.

3
docs/guide/mvc.md

@ -11,7 +11,7 @@ the communication between the model and the view.
Besides implementing MVC, Yii also introduces a front-controller, called
`Application`, which encapsulates the execution context for the processing
of a request. Application collects some information about a user request and
of a request. Application collects information about a user request and
then dispatches it to an appropriate controller for further handling.
The following diagram shows the static structure of a Yii application:
@ -21,6 +21,7 @@ The following diagram shows the static structure of a Yii application:
A Typical Workflow
------------------
The following diagram shows a typical workflow of a Yii application when
it is handling a user request:

181
docs/guide/performance.md

@ -0,0 +1,181 @@
Performance Tuning
==================
Application performance consists of two parts. First is the framework performance
and the second is the application itself. Yii has a pretty low performance impact
on your application out of the box and can be fine-tuned further for production
environment. As for the application, we'll provide some of the best practices
along with examples on how to apply them to Yii.
Preparing framework for production
----------------------------------
### Disabling Debug Mode
First thing you should do before deploying your application to production environment
is to disable debug mode. A Yii application runs in debug mode if the constant
`YII_DEBUG` is defined as `true` in `index.php` so to disable debug the following
should be in your `index.php`:
```php
defined('YII_DEBUG') or define('YII_DEBUG', false);
```
Debug mode is very useful during development stage, but it would impact performance
because some components cause extra burden in debug mode. For example, the message
logger may record additional debug information for every message being logged.
### Enabling PHP opcode cache
Enabling the PHP opcode cache improves any PHP application performance and lowers
memory usage significantly. Yii is no exception. It was tested with
[APC PHP extension](http://php.net/manual/en/book.apc.php) that caches
and optimizes PHP intermediate code and avoids the time spent in parsing PHP
scripts for every incoming request.
### Turning on ActiveRecord database schema caching
If the application is using Active Record, we should turn on the schema caching
to save the time of parsing database schema. This can be done by setting the
`Connection::enableSchemaCache` property to be `true` via application configuration
`protected/config/main.php`:
```php
return array(
// ...
'components' => array(
// ...
'db' => array(
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=mydatabase',
'username' => 'root',
'password' => '',
'enableSchemaCache' => true,
// Duration of schema cache.
// 'schemaCacheDuration' => 3600,
// Name of the cache component used. Default is 'cache'.
//'schemaCache' => 'cache',
),
'cache' => array(
'class' => 'yii\caching\FileCache',
),
),
);
```
Note that `cache` application component should be configured.
### Combining and Minimizing Assets
TBD
### Using better storage for sessions
By default PHP uses files to handle sessions. It is OK for development and
small projects but when it comes to handling concurrent requests it's better to
switch to another storage such as database. You can do so by configuring your
application via `protected/config/main.php`:
```php
return array(
// ...
'components' => array(
'session' => array(
'class' => 'yii\web\DbSession',
// Set the following if want to use DB component other than
// default 'db'.
// 'db' => 'mydb',
// To override default session table set the following
// 'sessionTable' => 'my_session',
),
),
);
```
You can use `CacheSession` to store sessions using cache. Note that some
cache storages such as memcached has no guaranteee that session data will not
be lost leading to unexpected logouts.
Improving application
---------------------
### Using Serverside Caching Techniques
As described in the Caching section, Yii provides several caching solutions that
may improve the performance of a Web application significantly. If the generation
of some data takes long time, we can use the data caching approach to reduce the
data generation frequency; If a portion of page remains relatively static, we
can use the fragment caching approach to reduce its rendering frequency;
If a whole page remains relative static, we can use the page caching approach to
save the rendering cost for the whole page.
### Leveraging HTTP to save procesing time and bandwidth
TBD
### Database Optimization
Fetching data from database is often the main performance bottleneck in
a Web application. Although using caching may alleviate the performance hit,
it does not fully solve the problem. When the database contains enormous data
and the cached data is invalid, fetching the latest data could be prohibitively
expensive without proper database and query design.
Design index wisely in a database. Indexing can make SELECT queries much faster,
but it may slow down INSERT, UPDATE or DELETE queries.
For complex queries, it is recommended to create a database view for it instead
of issuing the queries inside the PHP code and asking DBMS to parse them repetitively.
Do not overuse Active Record. Although Active Record is good at modelling data
in an OOP fashion, it actually degrades performance due to the fact that it needs
to create one or several objects to represent each row of query result. For data
intensive applications, using DAO or database APIs at lower level could be
a better choice.
Last but not least, use LIMIT in your SELECT queries. This avoids fetching
overwhelming data from database and exhausting the memory allocated to PHP.
### Using asArray
A good way to save memory and processing time on read-only pages is to use
ActiveRecord's `asArray` method.
```php
class PostController extends Controller
{
public function actionIndex()
{
$posts = Post::find()->orderBy('id DESC')->limit(100)->asArray()->all();
echo $this->render('index', array(
'posts' => $posts,
));
}
}
```
In the view you should access fields of each invidual record from `$posts` as array:
```php
foreach($posts as $post) {
echo $post['title']."<br>";
}
```
Note that you can use array notation even if `asArray` wasn't specified and you're
working with AR objects.
### Processing data in background
In order to respond to user requests faster you can process heavy parts of the
request later if there's no need for immediate response.
- Cron jobs + console.
- queues + handlers.
TBD

0
docs/guide/query-builder.md

0
docs/guide/security.md

3
docs/guide/template.md

@ -0,0 +1,3 @@
Template
========

0
docs/guide/testing.md

0
docs/guide/theming.md

459
docs/guide/upgrade-from-v1.md

@ -0,0 +1,459 @@
Upgrading from Yii 1.1
======================
In this chapter, we list the major changes introduced in Yii 2.0 since version 1.1.
We hope this list will make it easier for you to upgrade from Yii 1.1 and quickly
master Yii 2.0 based on your existing Yii knowledge.
Namespace
---------
The most obvious change in Yii 2.0 is the use of namespaces. Almost every core class
is namespaced, e.g., `yii\web\Request`. The "C" prefix is no longer used in class names.
The naming of the namespaces follows the directory structure. For example, `yii\web\Request`
indicates the corresponding class file is `web/Request.php` under the Yii framework folder.
You can use any core class without explicitly include that class file, thanks to the Yii
class loader.
Component and Object
--------------------
Yii 2.0 breaks the `CComponent` class in 1.1 into two classes: `Object` and `Component`.
The `Object` class is a lightweight base class that allows defining class properties
via getters and setters. The `Component` class extends from `Object` and supports
the event feature and the behavior feature.
If your class does not need the event or behavior feature, you should consider using
`Object` as the base class. This is usually the case for classes that represent basic
data structures.
Object Configuration
--------------------
The `Object` class introduces a uniform way of configuring objects. Any descendant class
of `Object` should declare its constructor (if needed) in the following way so that
it can be properly configured:
```php
class MyClass extends \yii\Object
{
public function __construct($param1, $param2, $config = array())
{
// ... initialization before configuration is applied
parent::__construct($config);
}
public function init()
{
parent::init();
// ... initialization after configuration is applied
}
}
```
In the above, the last parameter of the constructor must take a configuration array
which contains name-value pairs for initializing the properties at the end of the constructor.
You can override the `init()` method to do initialization work that should be done after
the configuration is applied.
By following this convention, you will be able to create and configure a new object
using a configuration array like the following:
```php
$object = Yii::createObject(array(
'class' => 'MyClass',
'property1' => 'abc',
'property2' => 'cde',
), $param1, $param2);
```
Events
------
There is no longer the need to define an `on`-method in order to define an event in Yii 2.0.
Instead, you can use whatever event names. To attach a handler to an event, you should
use the `on` method now:
```php
$component->on($eventName, $handler);
// To detach the handler, use:
// $component->off($eventName, $handler);
```
When you attach a handler, you can now associate it with some parameters which can be later
accessed via the event parameter by the handler:
```php
$component->on($eventName, $handler, $params);
```
Because of this change, you can now use "global" events. Simply trigger and attach handlers to
an event of the application instance:
```php
Yii::$app->on($eventName, $handler);
....
// this will trigger the event and cause $handler to be invoked.
Yii::$app->trigger($eventName);
```
Path Alias
----------
Yii 2.0 expands the usage of path aliases to both file/directory paths and URLs. An alias
must start with a `@` character so that it can be differentiated from file/directory paths and URLs.
For example, the alias `@yii` refers to the Yii installation directory. Path aliases are
supported in most places in the Yii core code. For example, `FileCache::cachePath` can take
both a path alias and a normal directory path.
Path alias is also closely related with class namespaces. It is recommended that a path
alias defined for each root namespace so that you can use Yii class autoloader without
any further configuration. For example, because `@yii` refers to the Yii installation directory,
a class like `yii\web\Request` can be autoloaded by Yii. If you use a third party library
such as Zend Framework, you may define a path alias `@Zend` which refers to its installation directory.
And Yii will be able to autoload any class in this library.
View
----
Yii 2.0 introduces a `View` class to represent the view part in the MVC pattern.
It can be configured globally through the "view" application component. It is also
accessible in any view file via `$this`. This is one of the biggest changes compared to 1.1:
**`$this` in a view file no longer refers to the controller or widget object.**
It refers to the view object that is used to render the view file. To access the controller
or the widget object, you have to use `$this->context` now.
Because you can access the view object through the "view" application component,
you can now render a view file like the following anywhere in your code, not necessarily
in controllers or widgets:
```php
$content = Yii::$app->view->renderFile($viewFile, $params);
// You can also explicitly create a new View instance to do the rendering
// $view = new View;
// $view->renderFile($viewFile, $params);
```
Also, there is no more `CClientScript` in Yii 2.0. The `View` class has taken over its role
with significant improvements. For more details, please see the "assets" subsection.
While Yii 2.0 continues to use PHP as its main template language, it comes with built-in
support for two popular template engines: Smarty and Twig. The Prado template engine is
no longer supported. To use these template engines, you just need to use `tpl` as the file
extension for your Smarty views, or `twig` for Twig views. You may also configure the
`View::renderers` property to use other template engines.
Models
------
A model is now associated with a form name returned its `formName()` method. This is
mainly used when using HTML forms to collect user inputs for a model. Previously in 1.1,
this is usually hardcoded as the class name of the model.
Yii 2.0 introduces a new method called `scenarios()` to declare which attributes require
validation under which scenario. Child classes should overwrite `scenarios()` to return
a list of scenarios and the corresponding attributes that need to be validated when
`validate()` is called. For example,
```php
public function scenarios()
{
return array(
'backend' => array('email', 'role'),
'frontend' => array('email', '!name'),
);
}
```
This method also determines which attributes are safe and which are not. In particular,
given a scenario, if an attribute appears in the corresponding attribute list in `scenarios()`
and the name is not prefixed with `!`, it is considered *safe*.
Because of the above change, Yii 2.0 no longer has "safe" and "unsafe" validators.
If your model only has one scenario (very common), you do not have to overwrite `scenarios()`,
and everything will still work like the 1.1 way.
Controllers
-----------
The `render()` and `renderPartial()` methods now return the rendering results instead of directly
sending them out. You have to `echo` them explicitly, e.g., `echo $this->render(...);`.
A new method called `populate()` is introduced to simplify the data population from user inputs
to a model. For example,
```php
$model = new Post;
if ($this->populate($_POST, $model)) {...}
// which is equivalent to:
if (isset($_POST['Post'])) {
$model->attributes = $_POST['Post'];
}
```
Themes
------
Theme works completely different in 2.0. It is now based on a path map to "translate" a source
view into a themed view. For example, if the path map for a theme is
`array('/www/views' => '/www/themes/basic')`, then the themed version for a view file
`/www/views/site/index.php` will be `/www/themes/basic/site/index.php`.
For this reason, theme can now be applied to any view file, even if a view rendered outside
of the context of a controller or a widget.
There is no more `CThemeManager`. Instead, `theme` is a configurable property of the "view"
application component.
Console Applications
--------------------
Console applications are now composed by controllers, too, like Web applications. In fact,
console controllers and Web controllers share the same base controller class.
Each console controller is like `CConsoleCommand` in 1.1. It consists of one or several
actions. You use the `yiic <route>` command to execute a console command, where `<route>`
stands for a controller route (e.g. `sitemap/index`). Additional anonymous arguments
are passed as the parameters to the corresponding controller action method, and named arguments
are treated as global options declared in `globalOptions()`.
Yii 2.0 supports automatic generation of command help information from comment blocks.
I18N
----
Yii 2.0 removes date formatter and number formatter in favor of the PECL intl PHP module.
Message translation is still supported, but managed via the "i18n" application component.
The component manages a set of message sources, which allows you to use different message
sources based on message categories. For more information, see the class documentation for `I18N`.
The message translation method is changed by merging the message category into the message being
translated. For example, `Yii::t('yii|message to be translated')`.
Action Filters
--------------
Action filters are implemented via behaviors now. You should extend from `ActionFilter` to
define a new filter. To use a filter, you should attach the filter class to the controller
as a behavior. For example, to use the `AccessControl` filter, you should have the following
code in a controller:
```php
public function behaviors()
{
return array(
'access' => array(
'class' => 'yii\web\AccessControl',
'rules' => array(
array('allow' => true, 'actions' => array('admin'), 'roles' => array('@')),
array('allow' => false),
),
),
);
}
```
Assets
------
Yii 2.0 introduces a new concept called *asset bundle*. It is a bit similar to script
packages (managed by `CClientScript`) in 1.1, but with better support.
An asset bundle is a collection of asset files (e.g. JavaScript files, CSS files, image files, etc.)
under a directory. By registering an asset bundle via `View::registerAssetBundle()`, you
will be able to make the assets in that bundle accessible via Web, and the current page
will automatically contain references to the JavaScript and CSS files in that bundle.
Static Helpers
--------------
Yii 2.0 introduces many commonly used static helper classes, such as `Html`, `ArrayHelper`,
`StringHelper`. These classes are designed to be easily extended. Note that static classes
are usually hard to be extended because of the fixed class name references. But Yii 2.0
introduces the class map (via `Yii::$classMap`) to overcome this difficulty.
`ActiveForm`
------------
Yii 2.0 introduces the *field* concept for building a form using `ActiveForm`. A field
is a container consisting of a label, an input, and an error message. It is represented
as an `ActiveField` object. Using fields, you can build a form more cleanly than before:
```php
<?php $form = $this->beginWidget('yii\widgets\ActiveForm'); ?>
<?php echo $form->field($model, 'username')->textInput(); ?>
<?php echo $form->field($model, 'password')->passwordInput(); ?>
<div class="form-actions">
<?php echo Html::submitButton('Login'); ?>
</div>
<?php $this->endWidget(); ?>
```
Query Builder
-------------
In 1.1, query building is scattered among several classes, including `CDbCommand`,
`CDbCriteria`, and `CDbCommandBuilder`. Yii 2.0 uses `Query` to represent a DB query
and `QueryBuilder` to generate SQL statements from query objects. For example,
```php
$query = new \yii\db\Query;
$query->select('id, name')
->from('tbl_user')
->limit(10);
$command = $query->createCommand();
$sql = $command->sql;
$rows = $command->queryAll();
```
Best of all, such query building methods can be used together with `ActiveRecord`,
as explained in the next sub-section.
ActiveRecord
------------
ActiveRecord has undergone significant changes in Yii 2.0. The most important one
is about relational ActiveRecord query. In 1.1, you have to declare the relations
in the `relations()` method. In 2.0, this is done via getter methods that return
an `ActiveQuery` object. For example, the following method declares an "orders" relation:
```php
class Customer extends \yii\db\ActiveRecord
{
public function getOrders()
{
return $this->hasMany('Order', array('customer_id' => 'id'));
}
}
```
You can use `$customer->orders` to access the customer's orders. You can also
use `$customer->getOrders()->andWhere('status=1')->all()` to perform on-the-fly
relational query with customized query conditions.
When loading relational records in an eager way, Yii 2.0 does it differently from 1.1.
In particular, in 1.1 a JOIN query would be used to bring both the primary and the relational
records; while in 2.0, two SQL statements are executed without using JOIN: the first
statement brings back the primary records and the second brings back the relational records
by filtering with the primary keys of the primary records.
Yii 2.0 no longer uses the `model()` method when performing queries. Instead, you
use the `find()` method like the following:
```php
// to retrieve all *active* customers and order them by their ID:
$customers = Customer::find()
->where(array('status' => $active))
->orderBy('id')
->all();
// return the customer whose PK is 1
$customer = Customer::find(1);
```
The `find()` method returns an instance of `ActiveQuery` which is a subclass of `Query`.
Therefore, you can use all query methods of `Query`.
Instead of returning ActiveRecord objects, you may call `ActiveQuery::asArray()` to
return results in terms of arrays. This is more efficient and is especially useful
when you need to return large number of records. For example,
```php
$customers = Customer::find()->asArray()->all();
```
By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes
would be saved to database when you call `save()`, regardless they are changed or not,
unless you explicitly list the attributes to save.
Auto-quoting Table and Column Names
------------------------------------
Yii 2.0 supports automatic quoting of database table and column names. A name enclosed
within double curly brackets is treated as a table name, and a name enclosed within
double square brackets is treated as a column name. They will be quoted according to
the database driver being used. For example,
```php
$command = $connection->createCommand('SELECT [[id]] FROM {{posts}}');
echo $command->sql; // MySQL: SELECT `id` FROM `posts`
```
This feature is especially useful if you are developing an application that supports
different DBMS.
User and Identity
-----------------
The `CWebUser` class in 1.1 is now replaced by `\yii\Web\User`, and there is no more
`CUserIdentity` class. Instead, you should implement the `Identity` interface which
is much more straightforward to implement. The bootstrap application provides such an example.
URL Management
--------------
URL management is similar to 1.1. A major enhancement is that it now supports optional
parameters. For example, if you have rule declared as follows, then it will match
both `post/popular` and `post/1/popular`. In 1.1, you would have to use two rules to achieve
the same goal.
```php
array(
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => array('page' => 1),
)
```
Response
--------
Extensions
----------
Integration with Composer
-------------------------
TBD

0
docs/guide/upgrade.md

3
docs/guide/url.md

@ -0,0 +1,3 @@
URL Management
==============

0
docs/guide/validation.md

BIN
docs/internals/exception_hierarchy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
docs/internals/exception_hierarchy.vsd

Binary file not shown.

3
docs/view_renderers.md

@ -27,6 +27,9 @@ array(
)
```
Note that Smarty and Twig are not bundled with Yii and you have to download and
unpack these yourself and then specify `twigPath` and `smartyPath` respectively.
Twig
----

17
framework/console/webapp/config.php

@ -1,17 +0,0 @@
<?php
/** @var $controller \yii\console\controllers\AppController */
$controller = $this;
return array(
'default' => array(
'index.php' => array(
'handler' => function($source) use ($controller) {
return $controller->replaceRelativePath($source, realpath(YII_PATH.'/yii.php'), 'yii');
},
'permissions' => 0777,
),
'protected/runtime' => array(
'permissions' => 0755,
),
),
);

10
framework/console/webapp/default/index.php

@ -1,10 +0,0 @@
<?php
define('YII_DEBUG', true);
require __DIR__.'/../framework/yii.php';
$config = require dirname(__DIR__).'/protected/config/main.php';
$config['basePath'] = dirname(__DIR__).'/protected';
$app = new \yii\web\Application($config);
$app->run();

20
framework/console/webapp/default/protected/config/main.php

@ -1,20 +0,0 @@
<?php
return array(
'id' => 'webapp',
'name' => 'My Web Application',
'components' => array(
// uncomment the following to use a MySQL database
/*
'db' => array(
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=testdrive',
'username' => 'root',
'password' => '',
),
*/
'cache' => array(
'class' => 'yii\caching\DummyCache',
),
),
);

15
framework/console/webapp/default/protected/controllers/SiteController.php

@ -1,15 +0,0 @@
<?php
use \yii\web\Controller;
/**
* SiteController
*/
class SiteController extends Controller
{
public function actionIndex()
{
echo $this->render('index', array(
'name' => 'Qiang',
));
}
}

17
framework/console/webapp/default/protected/views/layouts/main.php

@ -1,17 +0,0 @@
<?php use yii\helpers\Html as Html; ?>
<!doctype html>
<html lang="<?php \Yii::$app->language?>">
<head>
<meta charset="utf-8" />
<title><?php echo Html::encode($this->title)?></title>
</head>
<body>
<h1><?php echo Html::encode($this->title)?></h1>
<div class="content">
<?php echo $content?>
</div>
<div class="footer">
<?php echo \Yii::powered()?>
</div>
</body>
</html>

1
framework/console/webapp/default/protected/views/site/index.php

@ -1 +0,0 @@
Hello, <?php echo $name?>!

470
framework/helpers/base/ConsoleColor.php

@ -1,470 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\helpers\base;
// todo test this on all kinds of terminals, especially windows (check out lib ncurses)
/**
* Console View is the base class for console view components
*
* A console view provides functionality to create rich console application by allowing to format output
* by adding color and font style to it.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ConsoleColor
{
const FG_BLACK = 30;
const FG_RED = 31;
const FG_GREEN = 32;
const FG_YELLOW = 33;
const FG_BLUE = 34;
const FG_PURPLE = 35;
const FG_CYAN = 36;
const FG_GREY = 37;
const BG_BLACK = 40;
const BG_RED = 41;
const BG_GREEN = 42;
const BG_YELLOW = 43;
const BG_BLUE = 44;
const BG_PURPLE = 45;
const BG_CYAN = 46;
const BG_GREY = 47;
const BOLD = 1;
const ITALIC = 3;
const UNDERLINE = 4;
const BLINK = 5;
const NEGATIVE = 7;
const CONCEALED = 8;
const CROSSED_OUT = 9;
const FRAMED = 51;
const ENCIRCLED = 52;
const OVERLINED = 53;
/**
* Moves the terminal cursor up by sending ANSI control code CUU to the terminal.
* If the cursor is already at the edge of the screen, this has no effect.
* @param integer $rows number of rows the cursor should be moved up
*/
public static function moveCursorUp($rows=1)
{
echo "\033[" . (int) $rows . 'A';
}
/**
* Moves the terminal cursor down by sending ANSI control code CUD to the terminal.
* If the cursor is already at the edge of the screen, this has no effect.
* @param integer $rows number of rows the cursor should be moved down
*/
public static function moveCursorDown($rows=1)
{
echo "\033[" . (int) $rows . 'B';
}
/**
* Moves the terminal cursor forward by sending ANSI control code CUF to the terminal.
* If the cursor is already at the edge of the screen, this has no effect.
* @param integer $steps number of steps the cursor should be moved forward
*/
public static function moveCursorForward($steps=1)
{
echo "\033[" . (int) $steps . 'C';
}
/**
* Moves the terminal cursor backward by sending ANSI control code CUB to the terminal.
* If the cursor is already at the edge of the screen, this has no effect.
* @param integer $steps number of steps the cursor should be moved backward
*/
public static function moveCursorBackward($steps=1)
{
echo "\033[" . (int) $steps . 'D';
}
/**
* Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal.
* @param integer $lines number of lines the cursor should be moved down
*/
public static function moveCursorNextLine($lines=1)
{
echo "\033[" . (int) $lines . 'E';
}
/**
* Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal.
* @param integer $lines number of lines the cursor should be moved up
*/
public static function moveCursorPrevLine($lines=1)
{
echo "\033[" . (int) $lines . 'F';
}
/**
* Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal.
* @param integer $column 1-based column number, 1 is the left edge of the screen.
* @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line.
*/
public static function moveCursorTo($column, $row=null)
{
if ($row === null) {
echo "\033[" . (int) $column . 'G';
} else {
echo "\033[" . (int) $row . ';' . (int) $column . 'H';
}
}
/**
* Scrolls whole page up by sending ANSI control code SU to the terminal.
* New lines are added at the bottom. This is not supported by ANSI.SYS used in windows.
* @param int $lines number of lines to scroll up
*/
public static function scrollUp($lines=1)
{
echo "\033[".(int)$lines."S";
}
/**
* Scrolls whole page down by sending ANSI control code SD to the terminal.
* New lines are added at the top. This is not supported by ANSI.SYS used in windows.
* @param int $lines number of lines to scroll down
*/
public static function scrollDown($lines=1)
{
echo "\033[".(int)$lines."T";
}
/**
* Saves the current cursor position by sending ANSI control code SCP to the terminal.
* Position can then be restored with {@link restoreCursorPosition}.
*/
public static function saveCursorPosition()
{
echo "\033[s";
}
/**
* Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal.
*/
public static function restoreCursorPosition()
{
echo "\033[u";
}
/**
* Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal.
* Use {@link showCursor} to bring it back.
* Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit.
*/
public static function hideCursor()
{
echo "\033[?25l";
}
/**
* Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal.
*/
public static function showCursor()
{
echo "\033[?25h";
}
/**
* Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal.
* Cursor position will not be changed.
* **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen.
*/
public static function clearScreen()
{
echo "\033[2J";
}
/**
* Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal.
* Cursor position will not be changed.
*/
public static function clearScreenBeforeCursor()
{
echo "\033[1J";
}
/**
* Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal.
* Cursor position will not be changed.
*/
public static function clearScreenAfterCursor()
{
echo "\033[0J";
}
/**
* Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal.
* Cursor position will not be changed.
*/
public static function clearLine()
{
echo "\033[2K";
}
/**
* Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal.
* Cursor position will not be changed.
*/
public static function clearLineBeforeCursor()
{
echo "\033[1K";
}
/**
* Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal.
* Cursor position will not be changed.
*/
public static function clearLineAfterCursor()
{
echo "\033[0K";
}
/**
* Will send ANSI format for following output
*
* You can pass any of the FG_*, BG_* and TEXT_* constants and also xterm256ColorBg
* TODO: documentation
*/
public static function ansiStyle()
{
echo "\033[" . implode(';', func_get_args()) . 'm';
}
/**
* Will return a string formatted with the given ANSI style
*
* See {@link ansiStyle} for possible arguments.
* @param string $string the string to be formatted
* @return string
*/
public static function ansiStyleString($string)
{
$args = func_get_args();
array_shift($args);
$code = implode(';', $args);
return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string."\033[0m";
}
//const COLOR_XTERM256 = 38;// http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
public static function xterm256ColorFg($i) // TODO naming!
{
return '38;5;'.$i;
}
public static function xterm256ColorBg($i) // TODO naming!
{
return '48;5;'.$i;
}
/**
* Usage: list($w, $h) = ConsoleHelper::getScreenSize();
*
* @return array
*/
public static function getScreenSize()
{
// TODO implement
return array(150,50);
}
/**
* resets any ansi style set by previous method {@link ansiStyle}
* Any output after this is will have default text style.
*/
public static function reset()
{
echo "\033[0m";
}
/**
* Strips ANSI control codes from a string
*
* @param string $string String to strip
* @return string
*/
public static function strip($string)
{
return preg_replace('/\033\[[\d;]+m/', '', $string); // TODO currently only strips color
}
// TODO refactor and review
public static function ansiToHtml($string)
{
$tags = 0;
return preg_replace_callback('/\033\[[\d;]+m/', function($ansi) use (&$tags) {
$styleA = array();
foreach(explode(';', $ansi) as $controlCode)
{
switch($controlCode)
{
case static::FG_BLACK: $style = array('color' => '#000000'); break;
case static::FG_BLUE: $style = array('color' => '#000078'); break;
case static::FG_CYAN: $style = array('color' => '#007878'); break;
case static::FG_GREEN: $style = array('color' => '#007800'); break;
case static::FG_GREY: $style = array('color' => '#787878'); break;
case static::FG_PURPLE: $style = array('color' => '#780078'); break;
case static::FG_RED: $style = array('color' => '#780000'); break;
case static::FG_YELLOW: $style = array('color' => '#787800'); break;
case static::BG_BLACK: $style = array('background-color' => '#000000'); break;
case static::BG_BLUE: $style = array('background-color' => '#000078'); break;
case static::BG_CYAN: $style = array('background-color' => '#007878'); break;
case static::BG_GREEN: $style = array('background-color' => '#007800'); break;
case static::BG_GREY: $style = array('background-color' => '#787878'); break;
case static::BG_PURPLE: $style = array('background-color' => '#780078'); break;
case static::BG_RED: $style = array('background-color' => '#780000'); break;
case static::BG_YELLOW: $style = array('background-color' => '#787800'); break;
case static::BOLD: $style = array('font-weight' => 'bold'); break;
case static::ITALIC: $style = array('font-style' => 'italic'); break;
case static::UNDERLINE: $style = array('text-decoration' => array('underline')); break;
case static::OVERLINED: $style = array('text-decoration' => array('overline')); break;
case static::CROSSED_OUT:$style = array('text-decoration' => array('line-through')); break;
case static::BLINK: $style = array('text-decoration' => array('blink')); break;
case static::NEGATIVE: // ???
case static::CONCEALED:
case static::ENCIRCLED:
case static::FRAMED:
// TODO allow resetting codes
break;
case 0: // ansi reset
$return = '';
for($n=$tags; $tags>0; $tags--) {
$return .= '</span>';
}
return $return;
}
$styleA = ArrayHelper::merge($styleA, $style);
}
$styleString[] = array();
foreach($styleA as $name => $content) {
if ($name === 'text-decoration') {
$content = implode(' ', $content);
}
$styleString[] = $name.':'.$content;
}
$tags++;
return '<span' . (!empty($styleString) ? 'style="' . implode(';', $styleString) : '') . '>';
}, $string);
}
/**
* TODO syntax copied from https://github.com/pear/Console_Color2/blob/master/Console/Color2.php
*
* Converts colorcodes in the format %y (for yellow) into ansi-control
* codes. The conversion table is: ('bold' meaning 'light' on some
* terminals). It's almost the same conversion table irssi uses.
* <pre>
* text text background
* ------------------------------------------------
* %k %K %0 black dark grey black
* %r %R %1 red bold red red
* %g %G %2 green bold green green
* %y %Y %3 yellow bold yellow yellow
* %b %B %4 blue bold blue blue
* %m %M %5 magenta bold magenta magenta
* %p %P magenta (think: purple)
* %c %C %6 cyan bold cyan cyan
* %w %W %7 white bold white white
*
* %F Blinking, Flashing
* %U Underline
* %8 Reverse
* %_,%9 Bold
*
* %n Resets the color
* %% A single %
* </pre>
* First param is the string to convert, second is an optional flag if
* colors should be used. It defaults to true, if set to false, the
* colorcodes will just be removed (And %% will be transformed into %)
*
* @param string $string String to convert
* @param bool $colored Should the string be colored?
*
* @return string
*/
public static function renderColoredString($string)
{
$colored = true;
static $conversions = array ( // static so the array doesn't get built
// everytime
// %y - yellow, and so on... {{{
'%y' => array('color' => 'yellow'),
'%g' => array('color' => 'green' ),
'%b' => array('color' => 'blue' ),
'%r' => array('color' => 'red' ),
'%p' => array('color' => 'purple'),
'%m' => array('color' => 'purple'),
'%c' => array('color' => 'cyan' ),
'%w' => array('color' => 'grey' ),
'%k' => array('color' => 'black' ),
'%n' => array('color' => 'reset' ),
'%Y' => array('color' => 'yellow', 'style' => 'light'),
'%G' => array('color' => 'green', 'style' => 'light'),
'%B' => array('color' => 'blue', 'style' => 'light'),
'%R' => array('color' => 'red', 'style' => 'light'),
'%P' => array('color' => 'purple', 'style' => 'light'),
'%M' => array('color' => 'purple', 'style' => 'light'),
'%C' => array('color' => 'cyan', 'style' => 'light'),
'%W' => array('color' => 'grey', 'style' => 'light'),
'%K' => array('color' => 'black', 'style' => 'light'),
'%N' => array('color' => 'reset', 'style' => 'light'),
'%3' => array('background' => 'yellow'),
'%2' => array('background' => 'green' ),
'%4' => array('background' => 'blue' ),
'%1' => array('background' => 'red' ),
'%5' => array('background' => 'purple'),
'%6' => array('background' => 'cyan' ),
'%7' => array('background' => 'grey' ),
'%0' => array('background' => 'black' ),
// Don't use this, I can't stand flashing text
'%F' => array('style' => 'blink'),
'%U' => array('style' => 'underline'),
'%8' => array('style' => 'inverse'),
'%9' => array('style' => 'bold'),
'%_' => array('style' => 'bold')
// }}}
);
if ($colored) {
$string = str_replace('%%', '% ', $string);
foreach ($conversions as $key => $value) {
$string = str_replace($key, Console_Color::color($value),
$string);
}
$string = str_replace('% ', '%', $string);
} else {
$string = preg_replace('/%((%)|.)/', '$2', $string);
}
return $string;
}
/**
* Escapes % so they don't get interpreted as color codes
*
* @param string $string String to escape
*
* @access public
* @return string
*/
public static function escape($string)
{
return str_replace('%', '%%', $string);
}
}

13
phpunit.xml.dist

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit bootstrap="./tests/unit/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnFailure="false">
<testsuites>
<testsuite name="Yii Test Suite">
<directory>./tests/unit</directory>
</testsuite>
</testsuites>
</phpunit>

18
readme.md

@ -13,7 +13,8 @@ without prior notices. **Yii 2.0 is not ready for production use yet.**
DIRECTORY STRUCTURE
-------------------
app/ a ready-to-use application built on Yii 2
apps/ ready-to-use Web apps built on Yii 2
bootstrap/ a simple app supporting user login and contact page
build/ internally used build tools
docs/ documentation
framework/ framework source files
@ -26,14 +27,25 @@ REQUIREMENTS
The minimum requirement by Yii is that your Web server supports PHP 5.3.?.
DOCUMENTATION
-------------
For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/upgrade-from-v1.md)
to have a general idea of what has changed in 2.0.
We are writing more documentation to get you started and learn more in depth.
HOW TO PARTICIPATE
------------------
You are welcome to participate in Yii 2 development in the following ways:
**Your participation to Yii 2 development is very welcome!**
You may participate in the following ways:
* [Report issues](https://github.com/yiisoft/yii2/issues)
* [Give us feedback or start a design discussion](http://www.yiiframework.com/forum/index.php/forum/42-design-discussions-for-yii-20/)
* Fix issues, develop features, write/polish documentation
- Before you start, please adopt an existing issue or start a new one to avoid duplicated efforts.
- Before you start, please adopt an existing issue (labelled with "ready for adoption") or start a new one to avoid duplicated efforts.
- Please submit a merge request after you finish development.

50
tests/unit/DatabaseTestCase.php

@ -0,0 +1,50 @@
<?php
namespace yiiunit;
class DatabaseTestCase extends TestCase
{
protected $database;
protected $driverName = 'mysql';
protected $db;
protected function setUp()
{
$databases = $this->getParam('databases');
$this->database = $databases[$this->driverName];
$pdo_database = 'pdo_'.$this->driverName;
if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) {
$this->markTestSkipped('pdo and pdo_'.$pdo_database.' extension are required.');
}
}
/**
* @param bool $reset whether to clean up the test database
* @param bool $open whether to open and populate test database
* @return \yii\db\Connection
*/
public function getConnection($reset = true, $open = true)
{
if (!$reset && $this->db) {
return $this->db;
}
$db = new \yii\db\Connection;
$db->dsn = $this->database['dsn'];
if (isset($this->database['username'])) {
$db->username = $this->database['username'];
$db->password = $this->database['password'];
}
if ($open) {
$db->open();
$lines = explode(';', file_get_contents($this->database['fixture']));
foreach ($lines as $line) {
if (trim($line) !== '') {
$db->pdo->exec($line);
}
}
}
$this->db = $db;
return $db;
}
}

2
tests/unit/bootstrap.php

@ -5,7 +5,7 @@ define('YII_DEBUG', true);
$_SERVER['SCRIPT_NAME'] = '/' . __DIR__;
$_SERVER['SCRIPT_FILENAME'] = __FILE__;
require_once(__DIR__ . '/../../framework/yii.php');
require_once(__DIR__ . '/../../yii/Yii.php');
Yii::setAlias('@yiiunit', __DIR__);

2
tests/unit/data/ar/Customer.php

@ -22,6 +22,6 @@ class Customer extends ActiveRecord
public static function active($query)
{
return $query->andWhere('status=1');
$query->andWhere('status=1');
}
}

8
tests/unit/data/config.php

@ -1,10 +1,16 @@
<?php
return array(
'databases' => array(
'mysql' => array(
'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest',
'username' => 'root',
'username' => 'travis',
'password' => '',
'fixture' => __DIR__ . '/mysql.sql',
),
'sqlite' => array(
'dsn' => 'sqlite::memory:',
'fixture' => __DIR__ . '/sqlite.sql',
),
)
);

BIN
tests/unit/data/i18n/test.mo

Binary file not shown.

64
tests/unit/data/i18n/test.po

@ -0,0 +1,64 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: resurtm <resurtm@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.5\n"
msgctxt "context1"
msgid ""
"Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\n"
"aliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel "
"malesuada.\n"
"Nunc vel sapien nunc, a pretium nulla."
msgstr ""
"Олицетворение однократно. Представленный лексико-семантический анализ "
"является\n"
"психолингвистическим в своей основе, но механизм сочленений полидисперсен. "
"Впечатление\n"
"однократно. Различное расположение выбирает сюжетный механизм сочленений."
msgctxt "context1"
msgid "String number two."
msgstr "Строка номер два."
msgctxt "context2"
msgid ""
"The other\n"
"\n"
"context.\n"
msgstr ""
"Другой\n"
"\n"
"контекст.\n"
msgctxt "context1"
msgid ""
"Missing\n"
"\r\t\"translation."
msgstr ""
msgctxt "context1"
msgid ""
"Nunc vel sapien nunc, a pretium nulla.\n"
"Pellentesque habitant morbi tristique senectus et netus et malesuada fames "
"ac turpis egestas."
msgstr "Короткий перевод."
msgid "contextless"
msgstr ""
msgctxt "context2"
msgid ""
"test1\\ntest2\n"
"\\\n"
"test3"
msgstr ""
"тест1\\nтест2\n"
"\\\n"
"тест3"

6
tests/unit/data/mysql.sql

@ -1,10 +1,6 @@
/**
* This is the database schema for testing MySQL support of Yii DAO and Active Record.
* The following database setup is required to perform then relevant tests:
* Database name: yiitest
* username: test
* password: test
* charset: utf8
* The database setup in config.php is required to perform then relevant tests:
*/
DROP TABLE IF EXISTS tbl_order_item CASCADE;

348
tests/unit/data/sqlite.sql

@ -1,262 +1,88 @@
CREATE TABLE users
(
id INTEGER NOT NULL PRIMARY KEY,
username VARCHAR(128) NOT NULL,
password VARCHAR(128) NOT NULL,
email VARCHAR(128) NOT NULL
);
INSERT INTO users(id,username,password,email) VALUES (1,'user1','pass1','email1');
INSERT INTO users(id,username,password,email) VALUES (2,'user2','pass2','email2');
INSERT INTO users(id,username,password,email) VALUES (3,'user3','pass3','email3');
INSERT INTO users(id,username,password,email) VALUES (4,'user4','pass4','email4');
CREATE TABLE groups
(
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(128) NOT NULL
);
INSERT INTO groups(id,name) VALUES (1,'group1');
INSERT INTO groups(id,name) VALUES (2,'group2');
INSERT INTO groups(id,name) VALUES (3,'group3');
INSERT INTO groups(id,name) VALUES (4,'group4');
INSERT INTO groups(id,name) VALUES (5,'group5');
INSERT INTO groups(id,name) VALUES (6,'group6');
CREATE TABLE groups_descriptions
(
group_id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(128) NOT NULL
);
INSERT INTO groups_descriptions(group_id,name) VALUES (1,'room1');
INSERT INTO groups_descriptions(group_id,name) VALUES (2,'room2');
INSERT INTO groups_descriptions(group_id,name) VALUES (3,'room3');
INSERT INTO groups_descriptions(group_id,name) VALUES (4,'room4');
CREATE TABLE roles
(
user_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
name VARCHAR(128) NOT NULL,
PRIMARY KEY(user_id,group_id)
);
INSERT INTO roles(user_id,group_id,name) VALUES (1,1,'dev');
INSERT INTO roles(user_id,group_id,name) VALUES (1,2,'user');
INSERT INTO roles(user_id,group_id,name) VALUES (2,1,'dev');
INSERT INTO roles(user_id,group_id,name) VALUES (2,3,'user');
CREATE TABLE mentorships
(
teacher_id INTEGER NOT NULL,
student_id INTEGER NOT NULL,
progress VARCHAR(128) NOT NULL,
PRIMARY KEY(teacher_id,student_id)
);
INSERT INTO mentorships(teacher_id,student_id,progress) VALUES (1,3,'good');
INSERT INTO mentorships(teacher_id,student_id,progress) VALUES (2,4,'average');
CREATE TABLE profiles
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
first_name VARCHAR(128) NOT NULL,
last_name VARCHAR(128) NOT NULL,
user_id INTEGER NOT NULL,
CONSTRAINT FK_profile_user FOREIGN KEY (user_id)
REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO profiles (first_name, last_name, user_id) VALUES ('first 1','last 1',1);
INSERT INTO profiles (first_name, last_name, user_id) VALUES ('first 2','last 2',2);
CREATE TABLE posts
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
title VARCHAR(128) NOT NULL,
create_time TIMESTAMP NOT NULL,
author_id INTEGER NOT NULL,
content TEXT,
CONSTRAINT FK_post_author FOREIGN KEY (author_id)
REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 1',100000,1,'content 1');
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 2',100001,2,'content 2');
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 3',100002,2,'content 3');
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 4',100003,2,'content 4');
INSERT INTO posts (title, create_time, author_id, content) VALUES ('post 5',100004,3,'content 5');
CREATE TABLE posts_nofk
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
title VARCHAR(128) NOT NULL,
create_time TIMESTAMP NOT NULL,
author_id INTEGER NOT NULL,
content TEXT
);
INSERT INTO posts_nofk (title, create_time, author_id, content) VALUES ('post 1',100000,1,'content 1');
INSERT INTO posts_nofk (title, create_time, author_id, content) VALUES ('post 2',100001,2,'content 2');
INSERT INTO posts_nofk (title, create_time, author_id, content) VALUES ('post 3',100002,2,'content 3');
INSERT INTO posts_nofk (title, create_time, author_id, content) VALUES ('post 4',100003,2,'content 4');
INSERT INTO posts_nofk (title, create_time, author_id, content) VALUES ('post 5',100004,3,'content 5');
CREATE TABLE comments
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
post_id INTEGER NOT NULL,
author_id INTEGER NOT NULL,
CONSTRAINT FK_post_comment FOREIGN KEY (post_id)
REFERENCES posts (id) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT FK_user_comment FOREIGN KEY (author_id)
REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 1',1, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 2',1, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 3',1, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 4',2, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 5',2, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 6',3, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 7',3, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 8',3, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 9',3, 2);
INSERT INTO comments (content, post_id, author_id) VALUES ('comment 10',5, 3);
CREATE TABLE categories
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128) NOT NULL,
parent_id INTEGER,
CONSTRAINT FK_category_category FOREIGN KEY (parent_id)
REFERENCES categories (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO categories (name, parent_id) VALUES ('cat 1',NULL);
INSERT INTO categories (name, parent_id) VALUES ('cat 2',NULL);
INSERT INTO categories (name, parent_id) VALUES ('cat 3',NULL);
INSERT INTO categories (name, parent_id) VALUES ('cat 4',1);
INSERT INTO categories (name, parent_id) VALUES ('cat 5',1);
INSERT INTO categories (name, parent_id) VALUES ('cat 6',5);
INSERT INTO categories (name, parent_id) VALUES ('cat 7',5);
CREATE TABLE post_category
(
/**
* This is the database schema for testing Sqlite support of Yii DAO and Active Record.
* The database setup in config.php is required to perform then relevant tests:
*/
DROP TABLE IF EXISTS tbl_order_item;
DROP TABLE IF EXISTS tbl_item;
DROP TABLE IF EXISTS tbl_order;
DROP TABLE IF EXISTS tbl_category;
DROP TABLE IF EXISTS tbl_customer;
DROP TABLE IF EXISTS tbl_type;
CREATE TABLE tbl_customer (
id INTEGER NOT NULL,
email varchar(128) NOT NULL,
name varchar(128) NOT NULL,
address text,
status INTEGER DEFAULT 0,
PRIMARY KEY (id)
);
CREATE TABLE tbl_category (
id INTEGER NOT NULL,
name varchar(128) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE tbl_item (
id INTEGER NOT NULL,
name varchar(128) NOT NULL,
category_id INTEGER NOT NULL,
post_id INTEGER NOT NULL,
PRIMARY KEY (category_id, post_id),
CONSTRAINT FK_post_category_post FOREIGN KEY (post_id)
REFERENCES posts (id) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT FK_post_category_category FOREIGN KEY (category_id)
REFERENCES categories (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO post_category (category_id, post_id) VALUES (1,1);
INSERT INTO post_category (category_id, post_id) VALUES (2,1);
INSERT INTO post_category (category_id, post_id) VALUES (3,1);
INSERT INTO post_category (category_id, post_id) VALUES (4,2);
INSERT INTO post_category (category_id, post_id) VALUES (1,2);
INSERT INTO post_category (category_id, post_id) VALUES (1,3);
CREATE TABLE orders
(
key1 INTEGER NOT NULL,
key2 INTEGER NOT NULL,
name VARCHAR(128),
PRIMARY KEY (key1, key2)
);
INSERT INTO orders (key1,key2,name) VALUES (1,2,'order 12');
INSERT INTO orders (key1,key2,name) VALUES (1,3,'order 13');
INSERT INTO orders (key1,key2,name) VALUES (2,1,'order 21');
INSERT INTO orders (key1,key2,name) VALUES (2,2,'order 22');
CREATE TABLE items
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128),
col1 INTEGER NOT NULL,
col2 INTEGER NOT NULL,
CONSTRAINT FK_order_item FOREIGN KEY (col1,col2)
REFERENCES orders (key1,key2) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO items (name,col1,col2) VALUES ('item 1',1,2);
INSERT INTO items (name,col1,col2) VALUES ('item 2',1,2);
INSERT INTO items (name,col1,col2) VALUES ('item 3',1,3);
INSERT INTO items (name,col1,col2) VALUES ('item 4',2,2);
INSERT INTO items (name,col1,col2) VALUES ('item 5',2,2);
CREATE TABLE types
(
int_col INT NOT NULL,
int_col2 INTEGER DEFAULT 1,
char_col CHAR(100) NOT NULL,
char_col2 VARCHAR(100) DEFAULT 'something',
char_col3 TEXT,
float_col REAL(4,3) NOT NULL,
float_col2 DOUBLE DEFAULT 1.23,
blob_col BLOB,
numeric_col NUMERIC(5,2) DEFAULT 33.22,
time TIMESTAMP DEFAULT 123,
bool_col BOOL NOT NULL,
bool_col2 BOOLEAN DEFAULT 1,
null_col INTEGER DEFAULT NULL
);
CREATE TABLE Content
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
class VARCHAR(128),
parentID INTEGER NOT NULL,
ownerID INTEGER NOT NULL,
title VARCHAR(100),
CONSTRAINT FK_content_user FOREIGN KEY (ownerID)
REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT
CONSTRAINT FK_content_parent FOREIGN KEY (parentID)
REFERENCES Content (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO Content (class,parentID,ownerID,title) VALUES ('Article',-1,1,'article 1');
INSERT INTO Content (class,parentID,ownerID,title) VALUES ('Article',-1,2,'article 2');
INSERT INTO Content (class,parentID,ownerID,title) VALUES ('Comment',1,1,'comment 1');
INSERT INTO Content (class,parentID,ownerID,title) VALUES ('Article',-1,2,'article 3');
INSERT INTO Content (class,parentID,ownerID,title) VALUES ('Comment',4,2,'comment 2');
INSERT INTO Content (class,parentID,ownerID,title) VALUES ('Comment',4,1,'comment 3');
CREATE TABLE Article
(
id INTEGER NOT NULL PRIMARY KEY,
authorID INTEGER NOT NULL,
body TEXT,
CONSTRAINT FK_article_content FOREIGN KEY (id)
REFERENCES Content (id) ON DELETE CASCADE ON UPDATE RESTRICT
CONSTRAINT FK_article_author FOREIGN KEY (authorID)
REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO Article (id,authorID,body) VALUES (1,1,'content for article 1');
INSERT INTO Article (id,authorID,body) VALUES (2,2,'content for article 2');
INSERT INTO Article (id,authorID,body) VALUES (4,1,'content for article 3');
CREATE TABLE Comment
(
id INTEGER NOT NULL PRIMARY KEY,
authorID INTEGER NOT NULL,
body TEXT,
CONSTRAINT FK_comment_content FOREIGN KEY (id)
REFERENCES Content (id) ON DELETE CASCADE ON UPDATE RESTRICT
CONSTRAINT FK_article_author FOREIGN KEY (authorID)
REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
INSERT INTO Comment (id,authorID,body) VALUES (3,1,'content for comment 1');
INSERT INTO Comment (id,authorID,body) VALUES (5,1,'content for comment 2');
INSERT INTO Comment (id,authorID,body) VALUES (6,1,'content for comment 3');
PRIMARY KEY (id)
);
CREATE TABLE tbl_order (
id INTEGER NOT NULL,
customer_id INTEGER NOT NULL,
create_time INTEGER NOT NULL,
total decimal(10,0) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE tbl_order_item (
order_id INTEGER NOT NULL,
item_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
subtotal decimal(10,0) NOT NULL,
PRIMARY KEY (order_id, item_id)
);
CREATE TABLE tbl_type (
int_col INTEGER NOT NULL,
int_col2 INTEGER DEFAULT '1',
char_col char(100) NOT NULL,
char_col2 varchar(100) DEFAULT 'something',
char_col3 text,
float_col double(4,3) NOT NULL,
float_col2 double DEFAULT '1.23',
blob_col blob,
numeric_col decimal(5,2) DEFAULT '33.22',
time timestamp NOT NULL DEFAULT '2002-01-01 00:00:00',
bool_col tinyint(1) NOT NULL,
bool_col2 tinyint(1) DEFAULT '1'
);
INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1);
INSERT INTO tbl_customer (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1);
INSERT INTO tbl_customer (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2);
INSERT INTO tbl_category (name) VALUES ('Books');
INSERT INTO tbl_category (name) VALUES ('Movies');
INSERT INTO tbl_item (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1);
INSERT INTO tbl_item (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1);
INSERT INTO tbl_item (name, category_id) VALUES ('Ice Age', 2);
INSERT INTO tbl_item (name, category_id) VALUES ('Toy Story', 2);
INSERT INTO tbl_item (name, category_id) VALUES ('Cars', 2);
INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0);
INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0);
INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325502201, 40.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0);
INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0);

1
tests/unit/framework/YiiBaseTest.php

@ -47,7 +47,6 @@ class YiiBaseTest extends TestCase
public function testGetVersion()
{
echo Yii::getVersion();
$this->assertTrue((boolean)preg_match('~\d+\.\d+(?:\.\d+)?(?:-\w+)?~', \Yii::getVersion()));
}

6
tests/unit/framework/caching/ApcCacheTest.php

@ -15,11 +15,13 @@ class ApcCacheTest extends CacheTest
*/
protected function getCacheInstance()
{
if(!extension_loaded("apc")) {
if (!extension_loaded("apc")) {
$this->markTestSkipped("APC not installed. Skipping.");
} elseif ('cli' === PHP_SAPI && !ini_get('apc.enable_cli')) {
$this->markTestSkipped("APC cli is not enabled. Skipping.");
}
if($this->_cacheInstance === null) {
if ($this->_cacheInstance === null) {
$this->_cacheInstance = new ApcCache();
}
return $this->_cacheInstance;

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

@ -35,7 +35,8 @@ class DbCacheTest extends CacheTest
function getConnection($reset = true)
{
if($this->_connection === null) {
$params = $this->getParam('mysql');
$databases = $this->getParam('databases');
$params = $databases['mysql'];
$db = new \yii\db\Connection;
$db->dsn = $params['dsn'];
$db->username = $params['username'];

3
tests/unit/framework/db/ActiveRecordTest.php

@ -10,10 +10,11 @@ use yiiunit\data\ar\OrderItem;
use yiiunit\data\ar\Order;
use yiiunit\data\ar\Item;
class ActiveRecordTest extends \yiiunit\MysqlTestCase
class ActiveRecordTest extends \yiiunit\DatabaseTestCase
{
public function setUp()
{
parent::setUp();
ActiveRecord::$db = $this->getConnection();
}

2
tests/unit/framework/db/CommandTest.php

@ -7,7 +7,7 @@ use yii\db\Command;
use yii\db\Query;
use yii\db\DataReader;
class CommandTest extends \yiiunit\MysqlTestCase
class CommandTest extends \yiiunit\DatabaseTestCase
{
function testConstruct()
{

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

@ -4,12 +4,12 @@ namespace yiiunit\framework\db;
use yii\db\Connection;
class ConnectionTest extends \yiiunit\MysqlTestCase
class ConnectionTest extends \yiiunit\DatabaseTestCase
{
function testConstruct()
{
$connection = $this->getConnection(false);
$params = $this->getParam('mysql');
$params = $this->database;
$this->assertEquals($params['dsn'], $connection->dsn);
$this->assertEquals($params['username'], $connection->username);
@ -18,7 +18,7 @@ class ConnectionTest extends \yiiunit\MysqlTestCase
function testOpenClose()
{
$connection = $this->getConnection(false);
$connection = $this->getConnection(false, false);
$this->assertFalse($connection->isActive);
$this->assertEquals(null, $connection->pdo);
@ -39,9 +39,8 @@ class ConnectionTest extends \yiiunit\MysqlTestCase
function testGetDriverName()
{
$connection = $this->getConnection(false);
$this->assertEquals('mysql', $connection->driverName);
$this->assertFalse($connection->isActive);
$connection = $this->getConnection(false, false);
$this->assertEquals($this->driverName, $connection->driverName);
}
function testQuoteValue()

2
tests/unit/framework/db/QueryTest.php

@ -7,7 +7,7 @@ use yii\db\Command;
use yii\db\Query;
use yii\db\DataReader;
class QueryTest extends \yiiunit\MysqlTestCase
class QueryTest extends \yiiunit\DatabaseTestCase
{
function testSelect()
{

12
tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php

@ -0,0 +1,12 @@
<?php
namespace yiiunit\framework\db\sqlite;
class SqliteActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest
{
public function setUp()
{
$this->driverName = 'sqlite';
parent::setUp();
}
}

21
tests/unit/framework/db/sqlite/SqliteCommandTest.php

@ -0,0 +1,21 @@
<?php
namespace yiiunit\framework\db\sqlite;
class SqliteCommandTest extends \yiiunit\framework\db\CommandTest
{
public function setUp()
{
$this->driverName = 'sqlite';
parent::setUp();
}
function testAutoQuoting()
{
$db = $this->getConnection(false);
$sql = 'SELECT [[id]], [[t.name]] FROM {{tbl_customer}} t';
$command = $db->createCommand($sql);
$this->assertEquals("SELECT \"id\", 't'.\"name\" FROM 'tbl_customer' t", $command->sql);
}
}

47
tests/unit/framework/db/sqlite/SqliteConnectionTest.php

@ -0,0 +1,47 @@
<?php
namespace yiiunit\framework\db\sqlite;
class SqliteConnectionTest extends \yiiunit\framework\db\ConnectionTest
{
public function setUp()
{
$this->driverName = 'sqlite';
parent::setUp();
}
function testConstruct()
{
$connection = $this->getConnection(false);
$params = $this->database;
$this->assertEquals($params['dsn'], $connection->dsn);
}
function testQuoteValue()
{
$connection = $this->getConnection(false);
$this->assertEquals(123, $connection->quoteValue(123));
$this->assertEquals("'string'", $connection->quoteValue('string'));
$this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting"));
}
function testQuoteTableName()
{
$connection = $this->getConnection(false);
$this->assertEquals("'table'", $connection->quoteTableName('table'));
$this->assertEquals("'schema'.'table'", $connection->quoteTableName('schema.table'));
$this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}'));
$this->assertEquals('(table)', $connection->quoteTableName('(table)'));
}
function testQuoteColumnName()
{
$connection = $this->getConnection(false);
$this->assertEquals('"column"', $connection->quoteColumnName('column'));
$this->assertEquals("'table'.\"column\"", $connection->quoteColumnName('table.column'));
$this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]'));
$this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}'));
$this->assertEquals('(column)', $connection->quoteColumnName('(column)'));
}
}

20
tests/unit/framework/db/sqlite/SqliteQueryTest.php

@ -0,0 +1,20 @@
<?php
/**
* Created by JetBrains PhpStorm.
* User: RusMaxim
* Date: 09.05.13
* Time: 21:41
* To change this template use File | Settings | File Templates.
*/
namespace yiiunit\framework\db\sqlite;
class SqliteQueryTest extends \yiiunit\framework\db\QueryTest
{
public function setUp()
{
$this->driverName = 'sqlite';
parent::setUp();
}
}

21
tests/unit/framework/helpers/ArrayHelperTest.php

@ -12,6 +12,16 @@ class ArrayHelperTest extends \yii\test\TestCase
}
public function testRemove()
{
$array = array('name' => 'b', 'age' => 3);
$name = ArrayHelper::remove($array, 'name');
$this->assertEquals($name, 'b');
$this->assertEquals($array, array('age' => 3));
}
public function testMultisort()
{
// single key
@ -40,11 +50,20 @@ class ArrayHelperTest extends \yii\test\TestCase
$array = array(
array('name' => 'a', 'age' => 3),
array('name' => 'b', 'age' => 2),
array('name' => 'B', 'age' => 4),
array('name' => 'A', 'age' => 1),
);
ArrayHelper::multisort($array, array('name', 'age'), SORT_ASC, array(SORT_STRING|SORT_FLAG_CASE, SORT_REGULAR));
ArrayHelper::multisort($array, array('name', 'age'), SORT_ASC, array(SORT_STRING, SORT_REGULAR));
$this->assertEquals(array('name' => 'A', 'age' => 1), $array[0]);
$this->assertEquals(array('name' => 'B', 'age' => 4), $array[1]);
$this->assertEquals(array('name' => 'a', 'age' => 3), $array[2]);
$this->assertEquals(array('name' => 'b', 'age' => 2), $array[3]);
ArrayHelper::multisort($array, array('name', 'age'), SORT_ASC, array(SORT_STRING, SORT_REGULAR), false);
$this->assertEquals(array('name' => 'A', 'age' => 1), $array[0]);
$this->assertEquals(array('name' => 'a', 'age' => 3), $array[1]);
$this->assertEquals(array('name' => 'b', 'age' => 2), $array[2]);
$this->assertEquals(array('name' => 'B', 'age' => 4), $array[3]);
}
}

2
tests/unit/framework/helpers/JsonTest.php

@ -4,7 +4,7 @@
namespace yiiunit\framework\helpers;
use yii\helpers\Json;
use yii\helpers\JsExpression;
use yii\web\JsExpression;
class JsonTest extends \yii\test\TestCase
{

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

@ -70,4 +70,48 @@ class StringHelperTest extends \yii\test\TestCase
$this->assertEquals('PostTag', StringHelper::id2camel('post-tag'));
$this->assertEquals('PostTag', StringHelper::id2camel('post_tag', '_'));
}
public function testBasename()
{
$this->assertEquals('', StringHelper::basename(''));
$this->assertEquals('file', StringHelper::basename('file'));
$this->assertEquals('file.test', StringHelper::basename('file.test', '.test2'));
$this->assertEquals('file', StringHelper::basename('file.test', '.test'));
$this->assertEquals('file', StringHelper::basename('/file'));
$this->assertEquals('file.test', StringHelper::basename('/file.test', '.test2'));
$this->assertEquals('file', StringHelper::basename('/file.test', '.test'));
$this->assertEquals('file', StringHelper::basename('/path/to/file'));
$this->assertEquals('file.test', StringHelper::basename('/path/to/file.test', '.test2'));
$this->assertEquals('file', StringHelper::basename('/path/to/file.test', '.test'));
$this->assertEquals('file', StringHelper::basename('\file'));
$this->assertEquals('file.test', StringHelper::basename('\file.test', '.test2'));
$this->assertEquals('file', StringHelper::basename('\file.test', '.test'));
$this->assertEquals('file', StringHelper::basename('C:\file'));
$this->assertEquals('file.test', StringHelper::basename('C:\file.test', '.test2'));
$this->assertEquals('file', StringHelper::basename('C:\file.test', '.test'));
$this->assertEquals('file', StringHelper::basename('C:\path\to\file'));
$this->assertEquals('file.test', StringHelper::basename('C:\path\to\file.test', '.test2'));
$this->assertEquals('file', StringHelper::basename('C:\path\to\file.test', '.test'));
// mixed paths
$this->assertEquals('file.test', StringHelper::basename('/path\to/file.test'));
$this->assertEquals('file.test', StringHelper::basename('/path/to\file.test'));
$this->assertEquals('file.test', StringHelper::basename('\path/to\file.test'));
// \ and / in suffix
$this->assertEquals('file', StringHelper::basename('/path/to/filete/st', 'te/st'));
$this->assertEquals('st', StringHelper::basename('/path/to/filete/st', 'te\st'));
$this->assertEquals('file', StringHelper::basename('/path/to/filete\st', 'te\st'));
$this->assertEquals('st', StringHelper::basename('/path/to/filete\st', 'te/st'));
// http://www.php.net/manual/en/function.basename.php#72254
$this->assertEquals('foo', StringHelper::basename('/bar/foo/'));
$this->assertEquals('foo', StringHelper::basename('\\bar\\foo\\'));
}
}

12
tests/unit/framework/helpers/VarDumperTest.php

@ -0,0 +1,12 @@
<?php
namespace yiiunit\framework\helpers;
use \yii\helpers\VarDumper;
class VarDumperTest extends \yii\test\TestCase
{
public function testDumpObject()
{
$obj = new \StdClass();
VarDumper::dump($obj);
}
}

14
tests/unit/framework/i18n/GettextMessageSourceTest.php

@ -0,0 +1,14 @@
<?php
namespace yiiunit\framework\i18n;
use yii\i18n\GettextMessageSource;
use yiiunit\TestCase;
class GettextMessageSourceTest extends TestCase
{
public function testLoadMessages()
{
$this->markTestSkipped();
}
}

95
tests/unit/framework/i18n/GettextMoFileTest.php

@ -0,0 +1,95 @@
<?php
namespace yiiunit\framework\i18n;
use yii\i18n\GettextMoFile;
use yiiunit\TestCase;
class GettextMoFileTest extends TestCase
{
public function testLoad()
{
$moFile = new GettextMoFile();
$moFilePath = __DIR__ . '/../../data/i18n/test.mo';
$context1 = $moFile->load($moFilePath, 'context1');
$context2 = $moFile->load($moFilePath, 'context2');
// item count
$this->assertCount(3, $context1);
$this->assertCount(2, $context2);
// original messages
$this->assertArrayNotHasKey("Missing\n\r\t\"translation.", $context1);
$this->assertArrayHasKey("Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\naliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel malesuada.\nNunc vel sapien nunc, a pretium nulla.", $context1);
$this->assertArrayHasKey("String number two.", $context1);
$this->assertArrayHasKey("Nunc vel sapien nunc, a pretium nulla.\nPellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", $context1);
$this->assertArrayHasKey("The other\n\ncontext.\n", $context2);
$this->assertArrayHasKey("test1\\ntest2\n\\\ntest3", $context2);
// translated messages
$this->assertFalse(in_array("", $context1));
$this->assertTrue(in_array("Олицетворение однократно. Представленный лексико-семантический анализ является\nпсихолингвистическим в своей основе, но механизм сочленений полидисперсен. Впечатление\nоднократно. Различное расположение выбирает сюжетный механизм сочленений.", $context1));
$this->assertTrue(in_array('Строка номер два.', $context1));
$this->assertTrue(in_array('Короткий перевод.', $context1));
$this->assertTrue(in_array("Другой\n\nконтекст.\n", $context2));
$this->assertTrue(in_array("тест1\\nтест2\n\\\nтест3", $context2));
}
public function testSave()
{
// initial data
$s = chr(4);
$messages = array(
'Hello!' => 'Привет!',
"context1{$s}Hello?" => 'Привет?',
'Hello!?' => '',
"context1{$s}Hello!?!" => '',
"context2{$s}\"Quotes\"" => '"Кавычки"',
"context2{$s}\nNew lines\n" => "\nПереносы строк\n",
"context2{$s}\tTabs\t" => "\tТабы\t",
"context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r",
);
// create temporary directory and dump messages
$poFileDirectory = __DIR__ . '/../../runtime/i18n';
if (!is_dir($poFileDirectory)) {
mkdir($poFileDirectory);
}
if (is_file($poFileDirectory . '/test.mo')) {
unlink($poFileDirectory . '/test.mo');
}
$moFile = new GettextMoFile();
$moFile->save($poFileDirectory . '/test.mo', $messages);
// load messages
$context1 = $moFile->load($poFileDirectory . '/test.mo', 'context1');
$context2 = $moFile->load($poFileDirectory . '/test.mo', 'context2');
// context1
$this->assertCount(2, $context1);
$this->assertArrayHasKey('Hello?', $context1);
$this->assertTrue(in_array('Привет?', $context1));
$this->assertArrayHasKey('Hello!?!', $context1);
$this->assertTrue(in_array('', $context1));
// context2
$this->assertCount(4, $context2);
$this->assertArrayHasKey("\"Quotes\"", $context2);
$this->assertTrue(in_array('"Кавычки"', $context2));
$this->assertArrayHasKey("\nNew lines\n", $context2);
$this->assertTrue(in_array("\nПереносы строк\n", $context2));
$this->assertArrayHasKey("\tTabs\t", $context2);
$this->assertTrue(in_array("\tТабы\t", $context2));
$this->assertArrayHasKey("\rCarriage returns\r", $context2);
$this->assertTrue(in_array("\rВозвраты кареток\r", $context2));
}
}

95
tests/unit/framework/i18n/GettextPoFileTest.php

@ -0,0 +1,95 @@
<?php
namespace yiiunit\framework\i18n;
use yii\i18n\GettextPoFile;
use yiiunit\TestCase;
class GettextPoFileTest extends TestCase
{
public function testLoad()
{
$poFile = new GettextPoFile();
$poFilePath = __DIR__ . '/../../data/i18n/test.po';
$context1 = $poFile->load($poFilePath, 'context1');
$context2 = $poFile->load($poFilePath, 'context2');
// item count
$this->assertCount(4, $context1);
$this->assertCount(2, $context2);
// original messages
$this->assertArrayHasKey("Missing\n\r\t\"translation.", $context1);
$this->assertArrayHasKey("Aliquam tempus elit vel purus molestie placerat. In sollicitudin tincidunt\naliquet. Integer tincidunt gravida tempor. In convallis blandit dui vel malesuada.\nNunc vel sapien nunc, a pretium nulla.", $context1);
$this->assertArrayHasKey("String number two.", $context1);
$this->assertArrayHasKey("Nunc vel sapien nunc, a pretium nulla.\nPellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.", $context1);
$this->assertArrayHasKey("The other\n\ncontext.\n", $context2);
$this->assertArrayHasKey("test1\\\ntest2\n\\\\\ntest3", $context2);
// translated messages
$this->assertTrue(in_array("", $context1));
$this->assertTrue(in_array("Олицетворение однократно. Представленный лексико-семантический анализ является\nпсихолингвистическим в своей основе, но механизм сочленений полидисперсен. Впечатление\nоднократно. Различное расположение выбирает сюжетный механизм сочленений.", $context1));
$this->assertTrue(in_array('Строка номер два.', $context1));
$this->assertTrue(in_array('Короткий перевод.', $context1));
$this->assertTrue(in_array("Другой\n\nконтекст.\n", $context2));
$this->assertTrue(in_array("тест1\\\nтест2\n\\\\\nтест3", $context2));
}
public function testSave()
{
// initial data
$s = chr(4);
$messages = array(
'Hello!' => 'Привет!',
"context1{$s}Hello?" => 'Привет?',
'Hello!?' => '',
"context1{$s}Hello!?!" => '',
"context2{$s}\"Quotes\"" => '"Кавычки"',
"context2{$s}\nNew lines\n" => "\nПереносы строк\n",
"context2{$s}\tTabs\t" => "\tТабы\t",
"context2{$s}\rCarriage returns\r" => "\rВозвраты кареток\r",
);
// create temporary directory and dump messages
$poFileDirectory = __DIR__ . '/../../runtime/i18n';
if (!is_dir($poFileDirectory)) {
mkdir($poFileDirectory);
}
if (is_file($poFileDirectory . '/test.po')) {
unlink($poFileDirectory . '/test.po');
}
$poFile = new GettextPoFile();
$poFile->save($poFileDirectory . '/test.po', $messages);
// load messages
$context1 = $poFile->load($poFileDirectory . '/test.po', 'context1');
$context2 = $poFile->load($poFileDirectory . '/test.po', 'context2');
// context1
$this->assertCount(2, $context1);
$this->assertArrayHasKey('Hello?', $context1);
$this->assertTrue(in_array('Привет?', $context1));
$this->assertArrayHasKey('Hello!?!', $context1);
$this->assertTrue(in_array('', $context1));
// context2
$this->assertCount(4, $context2);
$this->assertArrayHasKey("\"Quotes\"", $context2);
$this->assertTrue(in_array('"Кавычки"', $context2));
$this->assertArrayHasKey("\nNew lines\n", $context2);
$this->assertTrue(in_array("\nПереносы строк\n", $context2));
$this->assertArrayHasKey("\tTabs\t", $context2);
$this->assertTrue(in_array("\tТабы\t", $context2));
$this->assertArrayHasKey("\rCarriage returns\r", $context2);
$this->assertTrue(in_array("\rВозвраты кареток\r", $context2));
}
}

248
tests/unit/framework/rbac/ManagerTestBase.php

@ -0,0 +1,248 @@
<?php
namespace yiiunit\framework\rbac;
use yii\rbac\Assignment;
use yii\rbac\Item;
use yiiunit\TestCase;
abstract class ManagerTestBase extends TestCase
{
/** @var \yii\rbac\PhpManager|\yii\rbac\DbManager */
protected $auth;
public function testCreateItem()
{
$type = Item::TYPE_TASK;
$name = 'editUser';
$description = 'edit a user';
$bizRule = 'checkUserIdentity()';
$data = array(1, 2, 3);
$item = $this->auth->createItem($name, $type, $description, $bizRule, $data);
$this->assertTrue($item instanceof Item);
$this->assertEquals($item->type, $type);
$this->assertEquals($item->name, $name);
$this->assertEquals($item->description, $description);
$this->assertEquals($item->bizRule, $bizRule);
$this->assertEquals($item->data, $data);
// test shortcut
$name2 = 'createUser';
$item2 = $this->auth->createRole($name2, $description, $bizRule, $data);
$this->assertEquals($item2->type, Item::TYPE_ROLE);
// test adding an item with the same name
$this->setExpectedException('Exception');
$this->auth->createItem($name, $type, $description, $bizRule, $data);
}
public function testGetItem()
{
$this->assertTrue($this->auth->getItem('readPost') instanceof Item);
$this->assertTrue($this->auth->getItem('reader') instanceof Item);
$this->assertNull($this->auth->getItem('unknown'));
}
public function testRemoveAuthItem()
{
$this->assertTrue($this->auth->getItem('updatePost') instanceof Item);
$this->assertTrue($this->auth->removeItem('updatePost'));
$this->assertNull($this->auth->getItem('updatePost'));
$this->assertFalse($this->auth->removeItem('updatePost'));
}
public function testChangeItemName()
{
$item = $this->auth->getItem('readPost');
$this->assertTrue($item instanceof Item);
$this->assertTrue($this->auth->hasItemChild('reader', 'readPost'));
$item->name = 'readPost2';
$this->assertNull($this->auth->getItem('readPost'));
$this->assertEquals($this->auth->getItem('readPost2'), $item);
$this->assertFalse($this->auth->hasItemChild('reader', 'readPost'));
$this->assertTrue($this->auth->hasItemChild('reader', 'readPost2'));
}
public function testAddItemChild()
{
$this->auth->addItemChild('createPost', 'updatePost');
// test adding upper level item to lower one
$this->setExpectedException('Exception');
$this->auth->addItemChild('readPost', 'reader');
}
public function testAddItemChild2()
{
// test adding inexistent items
$this->setExpectedException('Exception');
$this->assertFalse($this->auth->addItemChild('createPost2', 'updatePost'));
}
public function testRemoveItemChild()
{
$this->assertTrue($this->auth->hasItemChild('reader', 'readPost'));
$this->assertTrue($this->auth->removeItemChild('reader', 'readPost'));
$this->assertFalse($this->auth->hasItemChild('reader', 'readPost'));
$this->assertFalse($this->auth->removeItemChild('reader', 'readPost'));
}
public function testGetItemChildren()
{
$this->assertEquals(array(), $this->auth->getItemChildren('readPost'));
$children = $this->auth->getItemChildren('author');
$this->assertEquals(3, count($children));
$this->assertTrue(reset($children) instanceof Item);
}
public function testAssign()
{
$auth = $this->auth->assign('new user', 'createPost', 'rule', 'data');
$this->assertTrue($auth instanceof Assignment);
$this->assertEquals($auth->userId, 'new user');
$this->assertEquals($auth->itemName, 'createPost');
$this->assertEquals($auth->bizRule, 'rule');
$this->assertEquals($auth->data, 'data');
$this->setExpectedException('Exception');
$this->auth->assign('new user', 'createPost2', 'rule', 'data');
}
public function testRevoke()
{
$this->assertTrue($this->auth->isAssigned('author B', 'author'));
$auth = $this->auth->getAssignment('author B', 'author');
$this->assertTrue($auth instanceof Assignment);
$this->assertTrue($this->auth->revoke('author B', 'author'));
$this->assertFalse($this->auth->isAssigned('author B', 'author'));
$this->assertFalse($this->auth->revoke('author B', 'author'));
}
public function testGetAssignments()
{
$this->auth->assign('author B', 'deletePost');
$auths = $this->auth->getAssignments('author B');
$this->assertEquals(2, count($auths));
$this->assertTrue(reset($auths) instanceof Assignment);
}
public function testGetItems()
{
$this->assertEquals(count($this->auth->getRoles()), 4);
$this->assertEquals(count($this->auth->getOperations()), 4);
$this->assertEquals(count($this->auth->getTasks()), 1);
$this->assertEquals(count($this->auth->getItems()), 9);
$this->assertEquals(count($this->auth->getItems('author B', null)), 1);
$this->assertEquals(count($this->auth->getItems('author C', null)), 0);
$this->assertEquals(count($this->auth->getItems('author B', Item::TYPE_ROLE)), 1);
$this->assertEquals(count($this->auth->getItems('author B', Item::TYPE_OPERATION)), 0);
}
public function testClearAll()
{
$this->auth->clearAll();
$this->assertEquals(count($this->auth->getRoles()), 0);
$this->assertEquals(count($this->auth->getOperations()), 0);
$this->assertEquals(count($this->auth->getTasks()), 0);
$this->assertEquals(count($this->auth->getItems()), 0);
$this->assertEquals(count($this->auth->getAssignments('author B')), 0);
}
public function testClearAssignments()
{
$this->auth->clearAssignments();
$this->assertEquals(count($this->auth->getAssignments('author B')), 0);
}
public function testDetectLoop()
{
$this->setExpectedException('Exception');
$this->auth->addItemChild('readPost', 'readPost');
}
public function testExecuteBizRule()
{
$this->assertTrue($this->auth->executeBizRule(null, array(), null));
$this->assertTrue($this->auth->executeBizRule('return 1==true;', array(), null));
$this->assertTrue($this->auth->executeBizRule('return $params[0]==$params[1];', array(1, '1'), null));
$this->assertFalse($this->auth->executeBizRule('invalid', array(), null));
}
public function testCheckAccess()
{
$results = array(
'reader A' => array(
'createPost' => false,
'readPost' => true,
'updatePost' => false,
'updateOwnPost' => false,
'deletePost' => false,
),
'author B' => array(
'createPost' => true,
'readPost' => true,
'updatePost' => true,
'updateOwnPost' => true,
'deletePost' => false,
),
'editor C' => array(
'createPost' => false,
'readPost' => true,
'updatePost' => true,
'updateOwnPost' => false,
'deletePost' => false,
),
'admin D' => array(
'createPost' => true,
'readPost' => true,
'updatePost' => true,
'updateOwnPost' => false,
'deletePost' => true,
),
);
$params = array('authorID' => 'author B');
foreach (array('reader A', 'author B', 'editor C', 'admin D') as $user) {
$params['userID'] = $user;
foreach (array('createPost', 'readPost', 'updatePost', 'updateOwnPost', 'deletePost') as $operation) {
$result = $this->auth->checkAccess($user, $operation, $params);
$this->assertEquals($results[$user][$operation], $result);
}
}
}
protected function prepareData()
{
$this->auth->createOperation('createPost', 'create a post');
$this->auth->createOperation('readPost', 'read a post');
$this->auth->createOperation('updatePost', 'update a post');
$this->auth->createOperation('deletePost', 'delete a post');
$task = $this->auth->createTask('updateOwnPost', 'update a post by author himself', 'return $params["authorID"]==$params["userID"];');
$task->addChild('updatePost');
$role = $this->auth->createRole('reader');
$role->addChild('readPost');
$role = $this->auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');
$role = $this->auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');
$role = $this->auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');
$this->auth->assign('reader A', 'reader');
$this->auth->assign('author B', 'author');
$this->auth->assign('editor C', 'editor');
$this->auth->assign('admin D', 'admin');
}
}

34
tests/unit/framework/rbac/PhpManagerTest.php

@ -0,0 +1,34 @@
<?php
namespace yiiunit\framework\rbac;
use Yii;
use yii\rbac\PhpManager;
require_once(__DIR__ . '/ManagerTestBase.php');
class PhpManagerTest extends ManagerTestBase
{
public function setUp()
{
$authFile = Yii::$app->getRuntimePath() . '/rbac.php';
@unlink($authFile);
$this->auth = new PhpManager;
$this->auth->authFile = $authFile;
$this->auth->init();
$this->prepareData();
}
public function tearDown()
{
@unlink($this->auth->authFile);
}
public function testSaveLoad()
{
$this->auth->save();
$this->auth->clearAll();
$this->auth->load();
$this->testCheckAccess();
}
}

7
tests/unit/phpunit.xml

@ -1,7 +0,0 @@
<phpunit bootstrap="bootstrap.php"
colors="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnFailure="false">
</phpunit>

2
tests/web/app/index.php

@ -1,6 +1,6 @@
<?php
require(__DIR__ . '/../../../framework/yii.php');
require(__DIR__ . '/../../../yii/Yii.php');
$application = new yii\web\Application('test', __DIR__ . '/protected');
$application->run();

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save