Browse Source

Merge branch 'master' of github.com:yiisoft/yii2 into mutex-10050

tags/2.0.0-beta
resurtm 12 years ago
parent
commit
c3bdb0fc5c
  1. 23
      .gitattributes
  2. 3
      apps/advanced/backend/config/main.php
  3. 6
      apps/advanced/backend/controllers/SiteController.php
  4. 6
      apps/advanced/common/models/User.php
  5. 4
      apps/advanced/console/config/main.php
  6. 2
      apps/advanced/environments/dev/backend/config/main-local.php
  7. 2
      apps/advanced/environments/dev/frontend/config/main-local.php
  8. 4
      apps/advanced/frontend/config/main.php
  9. 10
      apps/advanced/frontend/controllers/SiteController.php
  10. 2
      apps/advanced/frontend/views/layouts/main.php
  11. 3
      apps/basic/config/console.php
  12. 4
      apps/basic/config/web.php
  13. 10
      apps/basic/controllers/SiteController.php
  14. 85
      apps/basic/tests/acceptance/WebGuy.php
  15. 6
      apps/basic/tests/functional.suite.dist.yml
  16. 5
      apps/basic/tests/functional/AboutCept.php
  17. 36
      apps/basic/tests/functional/ContactCept.php
  18. 8
      apps/basic/tests/functional/HomeCept.php
  19. 23
      apps/basic/tests/functional/LoginCept.php
  20. 1048
      apps/basic/tests/functional/TestGuy.php
  21. 2
      apps/basic/views/layouts/main.php
  22. 4
      apps/basic/www/index-test.php
  23. 602
      docs/guide/active-record.md
  24. 185
      docs/guide/caching.md
  25. 187
      docs/guide/controller.md
  26. 0
      docs/guide/dao.md
  27. 237
      docs/guide/database-basics.md
  28. 94
      docs/guide/index.md
  29. 59
      docs/guide/migration.md
  30. 260
      docs/guide/model.md
  31. 87
      docs/guide/template.md
  32. 33
      docs/guide/upgrade-from-v1.md
  33. 0
      docs/internals/autoloader.md
  34. 214
      docs/model.md
  35. 85
      docs/view_renderers.md
  36. 38
      framework/yii/YiiBase.php
  37. 6
      framework/yii/assets/yii.activeForm.js
  38. 41
      framework/yii/base/Application.php
  39. 40
      framework/yii/base/Controller.php
  40. 2
      framework/yii/base/ErrorHandler.php
  41. 72
      framework/yii/base/Formatter.php
  42. 86
      framework/yii/base/Model.php
  43. 32
      framework/yii/base/Module.php
  44. 14
      framework/yii/base/View.php
  45. 13
      framework/yii/classes.php
  46. 11
      framework/yii/debug/LogTarget.php
  47. 35
      framework/yii/debug/Module.php
  48. 38
      framework/yii/debug/Toolbar.php
  49. 9
      framework/yii/debug/controllers/DefaultController.php
  50. 1
      framework/yii/debug/views/default/index.php
  51. 27
      framework/yii/debug/views/default/toolbar.php
  52. 21
      framework/yii/debug/views/layouts/main.php
  53. 47
      framework/yii/helpers/base/ArrayHelper.php
  54. 83
      framework/yii/helpers/base/Html.php
  55. 11
      framework/yii/helpers/base/Json.php
  56. 21
      framework/yii/i18n/Formatter.php
  57. 2
      framework/yii/i18n/I18N.php
  58. 2
      framework/yii/log/DbTarget.php
  59. 2
      framework/yii/log/EmailTarget.php
  60. 2
      framework/yii/log/FileTarget.php
  61. 73
      framework/yii/log/Logger.php
  62. 2
      framework/yii/log/Target.php
  63. 98
      framework/yii/logging/Router.php
  64. 6
      framework/yii/web/AccessControl.php
  65. 30
      framework/yii/web/Controller.php
  66. 26
      framework/yii/web/Response.php
  67. 2
      framework/yii/web/User.php
  68. 38
      framework/yii/widgets/ActiveForm.php
  69. 206
      framework/yii/widgets/DetailView.php
  70. 6
      tests/unit/framework/base/BehaviorTest.php
  71. 1
      tests/unit/framework/base/ComponentTest.php
  72. 13
      tests/unit/framework/base/FormatterTest.php
  73. 6
      tests/unit/framework/base/ModelTest.php
  74. 1
      tests/unit/framework/base/ObjectTest.php
  75. 1
      tests/unit/framework/console/controllers/AssetControllerTest.php
  76. 1
      tests/unit/framework/db/ActiveRecordTest.php
  77. 6
      tests/unit/framework/db/CommandTest.php
  78. 6
      tests/unit/framework/db/ConnectionTest.php
  79. 1
      tests/unit/framework/db/DatabaseTestCase.php
  80. 5
      tests/unit/framework/db/QueryBuilderTest.php
  81. 6
      tests/unit/framework/db/QueryTest.php
  82. 51
      tests/unit/framework/helpers/ArrayHelperTest.php
  83. 62
      tests/unit/framework/helpers/HtmlTest.php
  84. 5
      tests/unit/framework/i18n/FormatterTest.php

23
.gitattributes vendored

@ -0,0 +1,23 @@
# Autodetect text files
* text=auto
# ...Unless the name matches the following overriding patterns
# Definitively text files
*.php text
*.css text
*.js text
*.txt text
*.md text
*.xml text
*.json text
*.bat text
*.sql text
*.xml text
*.yml text
# Ensure those won't be messed up with
*.png binary
*.jpg binary
*.gif binary
*.ttf binary

3
apps/advanced/backend/config/main.php

@ -27,10 +27,9 @@ return array(
'bundles' => require(__DIR__ . '/assets.php'), 'bundles' => require(__DIR__ . '/assets.php'),
), ),
'log' => array( 'log' => array(
'class' => 'yii\logging\Router',
'targets' => array( 'targets' => array(
array( array(
'class' => 'yii\logging\FileTarget', 'class' => 'yii\log\FileTarget',
'levels' => array('error', 'warning'), 'levels' => array('error', 'warning'),
), ),
), ),

6
apps/advanced/backend/controllers/SiteController.php

@ -16,8 +16,8 @@ class SiteController extends Controller
public function actionLogin() public function actionLogin()
{ {
$model = new LoginForm(); $model = new LoginForm();
if ($this->populate($_POST, $model) && $model->login()) { if ($model->load($_POST) && $model->login()) {
return Yii::$app->response->redirect(array('site/index')); return $this->redirect(array('site/index'));
} else { } else {
return $this->render('login', array( return $this->render('login', array(
'model' => $model, 'model' => $model,
@ -28,6 +28,6 @@ class SiteController extends Controller
public function actionLogout() public function actionLogout()
{ {
Yii::$app->user->logout(); Yii::$app->user->logout();
return Yii::$app->response->redirect(array('site/index')); return $this->redirect(array('site/index'));
} }
} }

6
apps/advanced/common/models/User.php

@ -101,9 +101,9 @@ class User extends ActiveRecord implements Identity
public function beforeSave($insert) public function beforeSave($insert)
{ {
if(parent::beforeSave($insert)) { if (parent::beforeSave($insert)) {
if($this->isNewRecord) { if ($this->isNewRecord) {
if(!empty($this->password)) { if (!empty($this->password)) {
$this->password_hash = SecurityHelper::generatePasswordHash($this->password); $this->password_hash = SecurityHelper::generatePasswordHash($this->password);
} }
} }

4
apps/advanced/console/config/main.php

@ -12,7 +12,6 @@ return array(
'id' => 'app-console', 'id' => 'app-console',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'),
'controllerNamespace' => 'console\controllers', 'controllerNamespace' => 'console\controllers',
'modules' => array( 'modules' => array(
), ),
@ -20,10 +19,9 @@ return array(
'db' => $params['components.db'], 'db' => $params['components.db'],
'cache' => $params['components.cache'], 'cache' => $params['components.cache'],
'log' => array( 'log' => array(
'class' => 'yii\logging\Router',
'targets' => array( 'targets' => array(
array( array(
'class' => 'yii\logging\FileTarget', 'class' => 'yii\log\FileTarget',
'levels' => array('error', 'warning'), 'levels' => array('error', 'warning'),
), ),
), ),

2
apps/advanced/environments/dev/backend/config/main-local.php

@ -9,7 +9,7 @@ return array(
'log' => array( 'log' => array(
'targets' => array( 'targets' => array(
// array( // array(
// 'class' => 'yii\logging\DebugTarget', // 'class' => 'yii\log\DebugTarget',
// ) // )
), ),
), ),

2
apps/advanced/environments/dev/frontend/config/main-local.php

@ -9,7 +9,7 @@ return array(
'log' => array( 'log' => array(
'targets' => array( 'targets' => array(
// array( // array(
// 'class' => 'yii\logging\DebugTarget', // 'class' => 'yii\log\DebugTarget',
// ) // )
), ),
), ),

4
apps/advanced/frontend/config/main.php

@ -12,7 +12,6 @@ return array(
'id' => 'app-frontend', 'id' => 'app-frontend',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'preload' => array('log'),
'controllerNamespace' => 'frontend\controllers', 'controllerNamespace' => 'frontend\controllers',
'modules' => array( 'modules' => array(
), ),
@ -27,10 +26,9 @@ return array(
'bundles' => require(__DIR__ . '/assets.php'), 'bundles' => require(__DIR__ . '/assets.php'),
), ),
'log' => array( 'log' => array(
'class' => 'yii\logging\Router',
'targets' => array( 'targets' => array(
array( array(
'class' => 'yii\logging\FileTarget', 'class' => 'yii\log\FileTarget',
'levels' => array('error', 'warning'), 'levels' => array('error', 'warning'),
), ),
), ),

10
apps/advanced/frontend/controllers/SiteController.php

@ -26,8 +26,8 @@ class SiteController extends Controller
public function actionLogin() public function actionLogin()
{ {
$model = new LoginForm(); $model = new LoginForm();
if ($this->populate($_POST, $model) && $model->login()) { if ($model->load($_POST) && $model->login()) {
return Yii::$app->response->redirect(array('site/index')); return $this->redirect(array('site/index'));
} else { } else {
return $this->render('login', array( return $this->render('login', array(
'model' => $model, 'model' => $model,
@ -38,15 +38,15 @@ class SiteController extends Controller
public function actionLogout() public function actionLogout()
{ {
Yii::$app->user->logout(); Yii::$app->user->logout();
return Yii::$app->response->redirect(array('site/index')); return $this->redirect(array('site/index'));
} }
public function actionContact() public function actionContact()
{ {
$model = new ContactForm; $model = new ContactForm;
if ($this->populate($_POST, $model) && $model->contact(Yii::$app->params['adminEmail'])) { if ($model->load($_POST) && $model->contact(Yii::$app->params['adminEmail'])) {
Yii::$app->session->setFlash('contactFormSubmitted'); Yii::$app->session->setFlash('contactFormSubmitted');
return Yii::$app->response->refresh(); return $this->refresh();
} else { } else {
return $this->render('contact', array( return $this->render('contact', array(
'model' => $model, 'model' => $model,

2
apps/advanced/frontend/views/layouts/main.php

@ -2,7 +2,6 @@
use yii\helpers\Html; use yii\helpers\Html;
use yii\widgets\Menu; use yii\widgets\Menu;
use yii\widgets\Breadcrumbs; use yii\widgets\Breadcrumbs;
use yii\debug\Toolbar;
/** /**
* @var $this \yii\base\View * @var $this \yii\base\View
@ -60,7 +59,6 @@ $this->registerAssetBundle('app');
</div> </div>
<?php $this->endBody(); ?> <?php $this->endBody(); ?>
</div> </div>
<?php echo Toolbar::widget(); ?>
</body> </body>
</html> </html>
<?php $this->endPage(); ?> <?php $this->endPage(); ?>

3
apps/basic/config/console.php

@ -13,10 +13,9 @@ return array(
'class' => 'yii\caching\FileCache', 'class' => 'yii\caching\FileCache',
), ),
'log' => array( 'log' => array(
'class' => 'yii\logging\Router',
'targets' => array( 'targets' => array(
array( array(
'class' => 'yii\logging\FileTarget', 'class' => 'yii\log\FileTarget',
'levels' => array('error', 'warning'), 'levels' => array('error', 'warning'),
), ),
), ),

4
apps/basic/config/web.php

@ -3,7 +3,6 @@
return array( return array(
'id' => 'bootstrap', 'id' => 'bootstrap',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'preload' => array('log'),
'components' => array( 'components' => array(
'cache' => array( 'cache' => array(
'class' => 'yii\caching\FileCache', 'class' => 'yii\caching\FileCache',
@ -16,10 +15,9 @@ return array(
'bundles' => require(__DIR__ . '/assets.php'), 'bundles' => require(__DIR__ . '/assets.php'),
), ),
'log' => array( 'log' => array(
'class' => 'yii\logging\Router',
'targets' => array( 'targets' => array(
array( array(
'class' => 'yii\logging\FileTarget', 'class' => 'yii\log\FileTarget',
'levels' => array('error', 'warning'), 'levels' => array('error', 'warning'),
), ),
), ),

10
apps/basic/controllers/SiteController.php

@ -27,8 +27,8 @@ class SiteController extends Controller
public function actionLogin() public function actionLogin()
{ {
$model = new LoginForm(); $model = new LoginForm();
if ($this->populate($_POST, $model) && $model->login()) { if ($model->load($_POST) && $model->login()) {
return Yii::$app->response->redirect(array('site/index')); return $this->redirect(array('site/index'));
} else { } else {
return $this->render('login', array( return $this->render('login', array(
'model' => $model, 'model' => $model,
@ -39,15 +39,15 @@ class SiteController extends Controller
public function actionLogout() public function actionLogout()
{ {
Yii::$app->user->logout(); Yii::$app->user->logout();
return Yii::$app->response->redirect(array('site/index')); return $this->redirect(array('site/index'));
} }
public function actionContact() public function actionContact()
{ {
$model = new ContactForm; $model = new ContactForm;
if ($this->populate($_POST, $model) && $model->contact(Yii::$app->params['adminEmail'])) { if ($model->load($_POST) && $model->contact(Yii::$app->params['adminEmail'])) {
Yii::$app->session->setFlash('contactFormSubmitted'); Yii::$app->session->setFlash('contactFormSubmitted');
return Yii::$app->response->refresh(); return $this->refresh();
} else { } else {
return $this->render('contact', array( return $this->render('contact', array(
'model' => $model, 'model' => $model,

85
apps/basic/tests/acceptance/WebGuy.php

@ -1,19 +1,18 @@
<?php <?php
// This class was automatically generated by build task // This class was automatically generated by build task
// You can change it manually, but it will be overwritten on next build // You should not change it manually as it will be overwritten on next build
// @codingStandardsIgnoreFile // @codingStandardsIgnoreFile
use Codeception\Maybe;
use \Codeception\Maybe;
use Codeception\Module\PhpBrowser; use Codeception\Module\PhpBrowser;
use Codeception\Module\WebHelper; use Codeception\Module\WebHelper;
/** /**
* Inherited methods * Inherited methods
* @method void execute($callable)
* @method void wantToTest($text) * @method void wantToTest($text)
* @method void wantTo($text) * @method void wantTo($text)
* @method void amTesting($method)
* @method void amTestingMethod($method)
* @method void testMethod($signature)
* @method void expectTo($prediction) * @method void expectTo($prediction)
* @method void expect($prediction) * @method void expect($prediction)
* @method void amGoingTo($argumentation) * @method void amGoingTo($argumentation)
@ -236,7 +235,7 @@ class WebGuy extends \Codeception\AbstractGuy
* Opens the page. * Opens the page.
* *
* @param $page * @param $page
* @see PhpBrowser::amOnPage() * @see Mink::amOnPage()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -268,7 +267,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* @param $subdomain * @param $subdomain
* @return mixed * @return mixed
* @see PhpBrowser::amOnSubdomain() * @see Mink::amOnSubdomain()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -298,7 +297,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $text * @param $text
* @param null $selector * @param null $selector
* @see PhpBrowser::dontSee() * @see Mink::dontSee()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -329,7 +328,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $text * @param $text
* @param null $selector * @param null $selector
* @see PhpBrowser::see() * @see Mink::see()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -359,7 +358,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $text * @param $text
* @param null $url * @param null $url
* @see PhpBrowser::seeLink() * @see Mink::seeLink()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -388,7 +387,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $text * @param $text
* @param null $url * @param null $url
* @see PhpBrowser::dontSeeLink() * @see Mink::dontSeeLink()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -432,7 +431,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* @param $link * @param $link
* @param $context * @param $context
* @see PhpBrowser::click() * @see Mink::click()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -457,7 +456,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ?> * ?>
* ``` * ```
* @param $selector * @param $selector
* @see PhpBrowser::seeElement() * @see Mink::seeElement()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -482,7 +481,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ?> * ?>
* ``` * ```
* @param $selector * @param $selector
* @see PhpBrowser::dontSeeElement() * @see Mink::dontSeeElement()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -499,7 +498,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* Reloads current page * Reloads current page
* @see PhpBrowser::reloadPage() * @see Mink::reloadPage()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -516,7 +515,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* Moves back in history * Moves back in history
* @see PhpBrowser::moveBack() * @see Mink::moveBack()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -533,7 +532,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* Moves forward in history * Moves forward in history
* @see PhpBrowser::moveForward() * @see Mink::moveForward()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -553,7 +552,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $field * @param $field
* @param $value * @param $value
* @see PhpBrowser::fillField() * @see Mink::fillField()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -583,7 +582,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $select * @param $select
* @param $option * @param $option
* @see PhpBrowser::selectOption() * @see Mink::selectOption()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -611,7 +610,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* *
* @param $option * @param $option
* @see PhpBrowser::checkOption() * @see Mink::checkOption()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -638,7 +637,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* *
* @param $option * @param $option
* @see PhpBrowser::uncheckOption() * @see Mink::uncheckOption()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -666,7 +665,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* *
* @param $uri * @param $uri
* @see PhpBrowser::seeInCurrentUrl() * @see Mink::seeInCurrentUrl()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -691,7 +690,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* *
* @param $uri * @param $uri
* @see PhpBrowser::dontSeeInCurrentUrl() * @see Mink::dontSeeInCurrentUrl()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -716,7 +715,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ?> * ?>
* *
* @param $uri * @param $uri
* @see PhpBrowser::seeCurrentUrlEquals() * @see Mink::seeCurrentUrlEquals()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -741,7 +740,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ?> * ?>
* *
* @param $uri * @param $uri
* @see PhpBrowser::dontSeeCurrentUrlEquals() * @see Mink::dontSeeCurrentUrlEquals()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -765,7 +764,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ?> * ?>
* *
* @param $uri * @param $uri
* @see PhpBrowser::seeCurrentUrlMatches() * @see Mink::seeCurrentUrlMatches()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -789,7 +788,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ?> * ?>
* *
* @param $uri * @param $uri
* @see PhpBrowser::dontSeeCurrentUrlMatches() * @see Mink::dontSeeCurrentUrlMatches()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -806,7 +805,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* *
* @see PhpBrowser::seeCookie() * @see Mink::seeCookie()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -823,7 +822,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* *
* @see PhpBrowser::dontSeeCookie() * @see Mink::dontSeeCookie()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -840,7 +839,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* *
* @see PhpBrowser::setCookie() * @see Mink::setCookie()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -857,7 +856,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* *
* @see PhpBrowser::resetCookie() * @see Mink::resetCookie()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -874,7 +873,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* *
* @see PhpBrowser::grabCookie() * @see Mink::grabCookie()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -903,7 +902,7 @@ class WebGuy extends \Codeception\AbstractGuy
* @param null $uri * @param null $uri
* @internal param $url * @internal param $url
* @return mixed * @return mixed
* @see PhpBrowser::grabFromCurrentUrl() * @see Mink::grabFromCurrentUrl()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -932,7 +931,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $field * @param $field
* @param $filename * @param $filename
* @see PhpBrowser::attachFile() * @see Mink::attachFile()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -959,7 +958,7 @@ class WebGuy extends \Codeception\AbstractGuy
* @param $selector * @param $selector
* @param $optionText * @param $optionText
* @return mixed * @return mixed
* @see PhpBrowser::seeOptionIsSelected() * @see Mink::seeOptionIsSelected()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -986,7 +985,7 @@ class WebGuy extends \Codeception\AbstractGuy
* @param $selector * @param $selector
* @param $optionText * @param $optionText
* @return mixed * @return mixed
* @see PhpBrowser::dontSeeOptionIsSelected() * @see Mink::dontSeeOptionIsSelected()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -1016,7 +1015,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* *
* @param $checkbox * @param $checkbox
* @see PhpBrowser::seeCheckboxIsChecked() * @see Mink::seeCheckboxIsChecked()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -1045,7 +1044,7 @@ class WebGuy extends \Codeception\AbstractGuy
* ``` * ```
* *
* @param $checkbox * @param $checkbox
* @see PhpBrowser::dontSeeCheckboxIsChecked() * @see Mink::dontSeeCheckboxIsChecked()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -1078,7 +1077,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $field * @param $field
* @param $value * @param $value
* @see PhpBrowser::seeInField() * @see Mink::seeInField()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -1110,7 +1109,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $field * @param $field
* @param $value * @param $value
* @see PhpBrowser::dontSeeInField() * @see Mink::dontSeeInField()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -1141,7 +1140,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $cssOrXPathOrRegex * @param $cssOrXPathOrRegex
* @return mixed * @return mixed
* @see PhpBrowser::grabTextFrom() * @see Mink::grabTextFrom()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -1172,7 +1171,7 @@ class WebGuy extends \Codeception\AbstractGuy
* *
* @param $field * @param $field
* @return mixed * @return mixed
* @see PhpBrowser::grabValueFrom() * @see Mink::grabValueFrom()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !
@ -1189,7 +1188,7 @@ class WebGuy extends \Codeception\AbstractGuy
/** /**
* *
* @see PhpBrowser::grabAttribute() * @see Mink::grabAttribute()
* @return \Codeception\Maybe * @return \Codeception\Maybe
* ! This method is generated. DO NOT EDIT. ! * ! This method is generated. DO NOT EDIT. !
* ! Documentation taken from corresponding module ! * ! Documentation taken from corresponding module !

6
apps/basic/tests/functional.suite.dist.yml

@ -8,4 +8,8 @@
class_name: TestGuy class_name: TestGuy
modules: modules:
enabled: [Filesystem, TestHelper] enabled: [Filesystem, TestHelper, Yii2]
config:
Yii2:
entryScript: 'www/index-test.php'
url: 'http://localhost/'

5
apps/basic/tests/functional/AboutCept.php

@ -0,0 +1,5 @@
<?php
$I = new TestGuy($scenario);
$I->wantTo('ensure that about works');
$I->amOnPage('?r=site/about');
$I->see('About', 'h1');

36
apps/basic/tests/functional/ContactCept.php

@ -0,0 +1,36 @@
<?php
$I = new TestGuy($scenario);
$I->wantTo('ensure that contact works');
$I->amOnPage('?r=site/contact');
$I->see('Contact', 'h1');
$I->submitForm('#contact-form', array());
$I->see('Contact', 'h1');
$I->see('Name cannot be blank');
$I->see('Email cannot be blank');
$I->see('Subject cannot be blank');
$I->see('Body cannot be blank');
$I->see('The verification code is incorrect');
$I->submitForm('#contact-form', array(
'ContactForm[name]' => 'tester',
'ContactForm[email]' => 'tester.email',
'ContactForm[subject]' => 'test subject',
'ContactForm[body]' => 'test content',
'ContactForm[verifyCode]' => 'testme',
));
$I->dontSee('Name cannot be blank', '.help-inline');
$I->see('Email is not a valid email address.');
$I->dontSee('Subject cannot be blank', '.help-inline');
$I->dontSee('Body cannot be blank', '.help-inline');
$I->dontSee('The verification code is incorrect', '.help-inline');
$I->submitForm('#contact-form', array(
'ContactForm[name]' => 'tester',
'ContactForm[email]' => 'tester@example.com',
'ContactForm[subject]' => 'test subject',
'ContactForm[body]' => 'test content',
'ContactForm[verifyCode]' => 'testme',
));
$I->dontSeeElement('#contact-form');
$I->see('Thank you for contacting us. We will respond to you as soon as possible.');

8
apps/basic/tests/functional/HomeCept.php

@ -0,0 +1,8 @@
<?php
$I = new TestGuy($scenario);
$I->wantTo('ensure that home page works');
$I->amOnPage('');
$I->see('My Company');
$I->seeLink('About');
$I->click('About');
$I->see('This is the About page.');

23
apps/basic/tests/functional/LoginCept.php

@ -0,0 +1,23 @@
<?php
$I = new TestGuy($scenario);
$I->wantTo('ensure that login works');
$I->amOnPage('?r=site/login');
$I->see('Login', 'h1');
$I->submitForm('#login-form', array());
$I->dontSee('Logout (admin)');
$I->see('Username cannot be blank');
$I->see('Password cannot be blank');
$I->submitForm('#login-form', array(
'LoginForm[username]' => 'admin',
'LoginForm[password]' => 'wrong',
));
$I->dontSee('Logout (admin)');
$I->see('Incorrect username or password');
$I->submitForm('#login-form', array(
'LoginForm[username]' => 'admin',
'LoginForm[password]' => 'admin',
));
$I->see('Logout (admin)');

1048
apps/basic/tests/functional/TestGuy.php

File diff suppressed because it is too large Load Diff

2
apps/basic/views/layouts/main.php

@ -2,7 +2,6 @@
use yii\helpers\Html; use yii\helpers\Html;
use yii\widgets\Menu; use yii\widgets\Menu;
use yii\widgets\Breadcrumbs; use yii\widgets\Breadcrumbs;
use yii\debug\Toolbar;
/** /**
* @var $this \yii\base\View * @var $this \yii\base\View
@ -60,7 +59,6 @@ $this->registerAssetBundle('app');
</div> </div>
<?php $this->endBody(); ?> <?php $this->endBody(); ?>
</div> </div>
<?php echo Toolbar::widget(); ?>
</body> </body>
</html> </html>
<?php $this->endPage(); ?> <?php $this->endPage(); ?>

4
apps/basic/www/index-test.php

@ -8,8 +8,8 @@ defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test'); defined('YII_ENV') or define('YII_ENV', 'test');
require(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php'); require_once(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php');
require(__DIR__ . '/../vendor/autoload.php'); require_once(__DIR__ . '/../vendor/autoload.php');
$config = require(__DIR__ . '/../config/web-test.php'); $config = require(__DIR__ . '/../config/web-test.php');

602
docs/guide/active-record.md

@ -0,0 +1,602 @@
Active Record
=============
ActiveRecord implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record).
The idea is that an ActiveRecord object is associated with a row in a database table so object properties are mapped
to colums of the corresponding database row. For example, a `Customer` object is associated with a row in the
`tbl_customer` table.
Instead of writing raw SQL statements to access the data in the table, you can call intuitive methods available in the
corresponding ActiveRecord class to achieve the same goals. For example, calling [[save()]] would insert or update a row
in the underlying table:
```php
$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();
```
Declaring ActiveRecord Classes
------------------------------
To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and
implement `tableName` method like the following:
```php
class Customer extends \yii\db\ActiveRecord
{
/**
* @return string the name of the table associated with this ActiveRecord class.
*/
public static function tableName()
{
return 'tbl_customer';
}
}
```
Connecting to Database
----------------------
ActiveRecord relies on a [[Connection|DB connection]]. By default, it assumes that
there is an application component named `db` that gives the needed [[Connection]]
instance which serves as the DB connection. Usually this component is configured
via application configuration like the following:
```php
return array(
'components' => array(
'db' => array(
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=testdb',
'username' => 'demo',
'password' => 'demo',
// turn on schema caching to improve performance
// 'schemaCacheDuration' => 3600,
),
),
);
```
Check [Database basics](database-basics.md) section in order to learn more on how to configure and use database
connections.
Getting Data from Database
--------------------------
There are two ActiveRecord methods for getting data:
- [[find()]]
- [[findBySql()]]
They both return an [[ActiveQuery]] instance. Coupled with the various customization and query methods
provided by [[ActiveQuery]], ActiveRecord supports very flexible and powerful data retrieval approaches.
The followings are some examples,
```php
// to retrieve all *active* customers and order them by their ID:
$customers = Customer::find()
->where(array('status' => $active))
->orderBy('id')
->all();
// to return a single customer whose ID is 1:
$customer = Customer::find()
->where(array('id' => 1))
->one();
// or use the following shortcut approach:
$customer = Customer::find(1);
// to retrieve customers using a raw SQL statement:
$sql = 'SELECT * FROM tbl_customer';
$customers = Customer::findBySql($sql)->all();
// to return the number of *active* customers:
$count = Customer::find()
->where(array('status' => $active))
->count();
// to return customers in terms of arrays rather than `Customer` objects:
$customers = Customer::find()->asArray()->all();
// each $customers element is an array of name-value pairs
// to index the result by customer IDs:
$customers = Customer::find()->indexBy('id')->all();
// $customers array is indexed by customer IDs
```
Accessing Column Data
---------------------
ActiveRecord maps each column of the corresponding database table row to an *attribute* in the ActiveRecord
object. An attribute is like a regular object property whose name is the same as the corresponding column
name and is case sensitive.
To read the value of a column, we can use the following expression:
```php
// "id" is the name of a column in the table associated with $customer ActiveRecord object
$id = $customer->id;
// or alternatively,
$id = $customer->getAttribute('id');
```
We can get all column values through the [[attributes]] property:
```php
$values = $customer->attributes;
```
Persisting Data to Database
---------------------------
ActiveRecord provides the following methods to insert, update and delete data:
- [[save()]]
- [[insert()]]
- [[update()]]
- [[delete()]]
- [[updateCounters()]]
- [[updateAll()]]
- [[updateAllCounters()]]
- [[deleteAll()]]
Note that [[updateAll()]], [[updateAllCounters()]] and [[deleteAll()]] apply to the whole database
table, while the rest of the methods only apply to the row associated with the ActiveRecord object.
The followings are some examples:
```php
// to insert a new customer record
$customer = new Customer;
$customer->name = 'James';
$customer->email = 'james@example.com';
$customer->save(); // equivalent to $customer->insert();
// to update an existing customer record
$customer = Customer::find($id);
$customer->email = 'james@example.com';
$customer->save(); // equivalent to $customer->update();
// Note that model attributes will be validated first and
// model will not be saved unless it's valid.
// to delete an existing customer record
$customer = Customer::find($id);
$customer->delete();
// to increment the age of all customers by 1
Customer::updateAllCounters(array('age' => 1));
```
Getting Relational Data
-----------------------
Using ActiveRecord you can expose relationships as properties. For example, with an appropriate declaration,
`$customer->orders` can return an array of `Order` objects which represent the orders placed by the specified customer.
To declare a relationship, define a getter method which returns an [[ActiveRelation]] object. For example,
```php
class Customer extends \yii\db\ActiveRecord
{
public function getOrders()
{
return $this->hasMany('Order', array('customer_id' => 'id'));
}
}
class Order extends \yii\db\ActiveRecord
{
public function getCustomer()
{
return $this->hasOne('Customer', array('id' => 'customer_id'));
}
}
```
Within the getter methods above, we call [[hasMany()]] or [[hasOne()]] methods to
create a new [[ActiveRelation]] object. The [[hasMany()]] method declares
a one-many relationship. For example, a customer has many orders. And the [[hasOne()]]
method declares a many-one or one-one relationship. For example, an order has one customer.
Both methods take two parameters:
- `$class`: the name of the class related models should use. If specified without
a namespace, the namespace will be taken from the declaring class.
- `$link`: the association between columns from two tables. This should be given as an array.
The keys of the array are the names of the columns from the table associated with `$class`,
while the values of the array are the names of the columns from the declaring class.
It is a good practice to define relationships based on table foreign keys.
After declaring relationships getting relational data is as easy as accessing
a component property that is defined by the getter method:
```php
// the orders of a customer
$customer = Customer::find($id);
$orders = $customer->orders; // $orders is an array of Order objects
// the customer of the first order
$customer2 = $orders[0]->customer; // $customer == $customer2
```
Because [[ActiveRelation]] extends from [[ActiveQuery]], it has the same query building methods,
which allows us to customize the query for retrieving the related objects.
For example, we may declare a `bigOrders` relationship which returns orders whose
subtotal exceeds certain amount:
```php
class Customer extends \yii\db\ActiveRecord
{
public function getBigOrders($threshold = 100)
{
return $this->hasMany('Order', array('customer_id' => 'id'))
->where('subtotal > :threshold', array(':threshold' => $threshold))
->orderBy('id');
}
}
```
Sometimes, two tables are related together via an intermediary table called
[pivot table](http://en.wikipedia.org/wiki/Pivot_table). To declare such relationships, we can customize
the [[ActiveRelation]] object by calling its [[ActiveRelation::via()]] or [[ActiveRelation::viaTable()]]
method.
For example, if table `tbl_order` and table `tbl_item` are related via pivot table `tbl_order_item`,
we can declare the `items` relation in the `Order` class like the following:
```php
class Order extends \yii\db\ActiveRecord
{
public function getItems()
{
return $this->hasMany('Item', array('id' => 'item_id'))
->viaTable('tbl_order_item', array('order_id' => 'id'));
}
}
```
[[ActiveRelation::via()]] method is similar to [[ActiveRelation::viaTable()]] except that
the first parameter of [[ActiveRelation::via()]] takes a relation name declared in the ActiveRecord class.
For example, the above `items` relation can be equivalently declared as follows:
```php
class Order extends \yii\db\ActiveRecord
{
public function getOrderItems()
{
return $this->hasMany('OrderItem', array('order_id' => 'id'));
}
public function getItems()
{
return $this->hasMany('Item', array('id' => 'item_id'))
->via('orderItems');
}
}
```
When you access the related objects the first time, behind the scene ActiveRecord performs a DB query
to retrieve the corresponding data and populate it into the related objects. No query will be performed
if you access the same related objects again. We call this *lazy loading*. For example,
```php
// SQL executed: SELECT * FROM tbl_customer WHERE id=1
$customer = Customer::find(1);
// SQL executed: SELECT * FROM tbl_order WHERE customer_id=1
$orders = $customer->orders;
// no SQL executed
$orders2 = $customer->orders;
```
Lazy loading is very convenient to use. However, it may suffer from performance
issue in the following scenario:
```php
// SQL executed: SELECT * FROM tbl_customer LIMIT 100
$customers = Customer::find()->limit(100)->all();
foreach ($customers as $customer) {
// SQL executed: SELECT * FROM tbl_order WHERE customer_id=...
$orders = $customer->orders;
// ...handle $orders...
}
```
How many SQL queries will be performed in the above code, assuming there are more than 100 customers in
the database? 101! The first SQL query brings back 100 customers. Then for each customer, a SQL query
is performed to bring back the customer's orders.
To solve the above performance problem, you can use the so-called *eager loading* by calling [[ActiveQuery::with()]]:
```php
// SQL executed: SELECT * FROM tbl_customer LIMIT 100
// SELECT * FROM tbl_orders WHERE customer_id IN (1,2,...)
$customers = Customer::find()->limit(100)
->with('orders')->all();
foreach ($customers as $customer) {
// no SQL executed
$orders = $customer->orders;
// ...handle $orders...
}
```
As you can see, only two SQL queries are needed for the same task.
Sometimes, you may want to customize the relational queries on the fly. It can be
done for both lazy loading and eager loading. For example,
```php
$customer = Customer::find(1);
// lazy loading: SELECT * FROM tbl_order WHERE customer_id=1 AND subtotal>100
$orders = $customer->getOrders()->where('subtotal>100')->all();
// eager loading: SELECT * FROM tbl_customer LIMIT 10
SELECT * FROM tbl_order WHERE customer_id IN (1,2,...) AND subtotal>100
$customers = Customer::find()->limit(100)->with(array(
'orders' => function($query) {
$query->andWhere('subtotal>100');
},
))->all();
```
Working with Relationships
--------------------------
ActiveRecord provides the following two methods for establishing and breaking a
relationship between two ActiveRecord objects:
- [[link()]]
- [[unlink()]]
For example, given a customer and a new order, we can use the following code to make the
order owned by the customer:
```php
$customer = Customer::find(1);
$order = new Order;
$order->subtotal = 100;
$customer->link('orders', $order);
```
The [[link()]] call above will set the `customer_id` of the order to be the primary key
value of `$customer` and then call [[save()]] to save the order into database.
Data Input and Validation
-------------------------
ActiveRecord inherits data validation and data input features from [[\yii\base\Model]]. Data validation is called
automatically when `save()` is performed and is canceling saving in case attributes aren't valid.
For more details refer to [Model](model.md) section of the guide.
Life Cycles of an ActiveRecord Object
-------------------------------------
An ActiveRecord object undergoes different life cycles when it is used in different cases.
Subclasses or ActiveRecord behaviors may "inject" custom code in these life cycles through
method overriding and event handling mechanisms.
When instantiating a new ActiveRecord instance, we will have the following life cycles:
1. constructor
2. [[init()]]: will trigger an [[EVENT_INIT]] event
When getting an ActiveRecord instance through the [[find()]] method, we will have the following life cycles:
1. constructor
2. [[init()]]: will trigger an [[EVENT_INIT]] event
3. [[afterFind()]]: will trigger an [[EVENT_AFTER_FIND]] event
When calling [[save()]] to insert or update an ActiveRecord, we will have the following life cycles:
1. [[beforeValidate()]]: will trigger an [[EVENT_BEFORE_VALIDATE]] event
2. [[afterValidate()]]: will trigger an [[EVENT_AFTER_VALIDATE]] event
3. [[beforeSave()]]: will trigger an [[EVENT_BEFORE_INSERT]] or [[EVENT_BEFORE_UPDATE]] event
4. perform the actual data insertion or updating
5. [[afterSave()]]: will trigger an [[EVENT_AFTER_INSERT]] or [[EVENT_AFTER_UPDATE]] event
Finally when calling [[delete()]] to delete an ActiveRecord, we will have the following life cycles:
1. [[beforeDelete()]]: will trigger an [[EVENT_BEFORE_DELETE]] event
2. perform the actual data deletion
3. [[afterDelete()]]: will trigger an [[EVENT_AFTER_DELETE]] event
Scopes
------
A scope is a method that customizes a given [[ActiveQuery]] object. Scope methods are defined
in the ActiveRecord classes. They can be invoked through the [[ActiveQuery]] object that is created
via [[find()]] or [[findBySql()]]. The following is an example:
```php
class Customer extends \yii\db\ActiveRecord
{
// ...
/**
* @param ActiveQuery $query
*/
public static function active($query)
{
$query->andWhere('status = 1');
}
}
$customers = Customer::find()->active()->all();
```
In the above, the `active()` method is defined in `Customer` while we are calling it
through `ActiveQuery` returned by `Customer::find()`.
Scopes can be parameterized. For example, we can define and use the following `olderThan` scope:
```php
class Customer extends \yii\db\ActiveRecord
{
// ...
/**
* @param ActiveQuery $query
* @param integer $age
*/
public static function olderThan($query, $age = 30)
{
$query->andWhere('age > :age', array(':age' => $age));
}
}
$customers = Customer::find()->olderThan(50)->all();
```
The parameters should follow after the `$query` parameter when defining the scope method, and they
can take default values like shown above.
Atomic operations and scenarios
-------------------------------
TODO: FIXME: WIP, TBD, https://github.com/yiisoft/yii2/issues/226
Imagine situation where you have to save something related to the main model in [[beforeSave()]],
[[afterSave()]], [[beforeDelete()]] and/or [[afterDelete()]] life cycle methods. Developer may come
to solution of overriding ActiveRecord [[save()]] method with database transaction wrapping or
even using transaction in controller action, which is strictly speaking doesn't seems to be a good
practice (recall skinny-controller fat-model fundamental rule).
Here these ways are (**DO NOT** use them unless you're sure what are you actually doing). Models:
```php
class Feature extends \yii\db\ActiveRecord
{
// ...
public function getProduct()
{
return $this->hasOne('Product', array('product_id' => 'id'));
}
}
class Product extends \yii\db\ActiveRecord
{
// ...
public function getFeatures()
{
return $this->hasMany('Feature', array('id' => 'product_id'));
}
}
```
Overriding [[save()]] method:
```php
class ProductController extends \yii\web\Controller
{
public function actionCreate()
{
// FIXME: TODO: WIP, TBD
}
}
```
Using transactions within controller layer:
```php
class ProductController extends \yii\web\Controller
{
public function actionCreate()
{
// FIXME: TODO: WIP, TBD
}
}
```
Instead of using these fragile methods you should consider using atomic scenarios and operations feature.
```php
class Feature extends \yii\db\ActiveRecord
{
// ...
public function getProduct()
{
return $this->hasOne('Product', array('product_id' => 'id'));
}
public function scenarios()
{
return array(
'userCreates' => array(
'attributes' => array('name', 'value'),
'atomic' => array(self::OP_INSERT),
),
);
}
}
class Product extends \yii\db\ActiveRecord
{
// ...
public function getFeatures()
{
return $this->hasMany('Feature', array('id' => 'product_id'));
}
public function scenarios()
{
return array(
'userCreates' => array(
'attributes' => array('title', 'price'),
'atomic' => array(self::OP_INSERT),
),
);
}
public function afterValidate()
{
parent::afterValidate();
// FIXME: TODO: WIP, TBD
}
public function afterSave($insert)
{
parent::afterSave();
if ($this->getScenario() === 'userCreates') {
// FIXME: TODO: WIP, TBD
}
}
}
```
Controller is very thin and neat:
```php
class ProductController extends \yii\web\Controller
{
public function actionCreate()
{
// FIXME: TODO: WIP, TBD
}
}
```
See also
--------
- [Model](model.md)
- [[\yii\db\ActiveRecord]]

185
docs/guide/caching.md

@ -1,3 +1,188 @@
Caching Caching
======= =======
Overview and Base Concepts
--------------------------
Caching is a cheap and effective way to improve the performance of a web application. By storing relatively
static data in cache and serving it from cache when requested, we save the time needed to generate the data.
Using cache in Yii mainly involves configuring and accessing a cache application component. The following
application configuration specifies a cache component that uses [memcached](http://memcached.org/) with
two cache servers. Note, this configuration should be done in file located at `@app/config/web.php` alias
in case you're using basic sample application.
```php
'components' => array(
'cache' => array(
'class' => '\yii\caching\MemCache',
'servers' => array(
array(
'host' => 'server1',
'port' => 11211,
'weight' => 100,
),
array(
'host' => 'server2',
'port' => 11211,
'weight' => 50,
),
),
),
),
```
When the application is running, the cache component can be accessed through `Yii::$app->cache` call.
Yii provides various cache components that can store cached data in different media. The following
is a summary of the available cache components:
* [[\yii\caching\ApcCache]]: uses PHP [APC](http://php.net/manual/en/book.apc.php) extension. This option can be
considered as the fastest one when dealing with cache for a centralized thick application (e.g. one
server, no dedicated load balancers, etc.).
* [[\yii\caching\DbCache]]: uses a database table to store cached data. By default, it will create and use a
[SQLite3](http://sqlite.org/) database under the runtime directory. You can explicitly specify a database for
it to use by setting its `db` property.
* [[\yii\caching\DummyCache]]: presents dummy cache that does no caching at all. The purpose of this component
is to simplify the code that needs to check the availability of cache. For example, during development or if
the server doesn't have actual cache support, we can use this cache component. When an actual cache support
is enabled, we can switch to use the corresponding cache component. In both cases, we can use the same
code `Yii::$app->cache->get($key)` to attempt retrieving a piece of data without worrying that
`Yii::$all->cache` might be `null`.
* [[\yii\caching\FileCache]]: uses standard files to store cached data. This is particular suitable
to cache large chunk of data (such as pages).
* [[\yii\caching\MemCache]]: uses PHP [memcache](http://php.net/manual/en/book.memcache.php)
and [memcached](http://php.net/manual/en/book.memcached.php) extensions. This option can be considered as
the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load
balancers, etc.)
* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) NoSQL database.
* [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension)
([see also](http://php.net/manual/en/book.wincache.php)) extension.
* [[\yii\caching\XCache]]: uses PHP [XCache](http://xcache.lighttpd.net/) extension.
* [[\yii\caching\ZendDataCache]]: uses
[Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm)
as the underlying caching medium.
Tip: because all these cache components extend from the same base class [[Cache]], one can switch to use
a different type of cache without modifying the code that uses cache.
Caching can be used at different levels. At the lowest level, we use cache to store a single piece of data,
such as a variable, and we call this data caching. At the next level, we store in cache a page fragment which
is generated by a portion of a view script. And at the highest level, we store a whole page in cache and serve
it from cache as needed.
In the next few subsections, we elaborate how to use cache at these levels.
Note, by definition, cache is a volatile storage medium. It does not ensure the existence of the cached
data even if it does not expire. Therefore, do not use cache as a persistent storage (e.g. do not use cache
to store session data or other valuable information).
Data Caching
------------
Data caching is about storing some PHP variable in cache and retrieving it later from cache. For this purpose,
the cache component base class [[\yii\caching\Cache]] provides two methods that are used most of the time:
[[set()]] and [[get()]]. Note, only serializable variables and objects could be cached successfully.
To store a variable `$value` in cache, we choose a unique `$key` and call [[set()]] to store it:
```php
Yii::$app->cache->set($key, $value);
```
The cached data will remain in the cache forever unless it is removed because of some caching policy
(e.g. caching space is full and the oldest data are removed). To change this behavior, we can also supply
an expiration parameter when calling [[set()]] so that the data will be removed from the cache after
a certain period of time:
```php
// keep the value in cache for at most 45 seconds
Yii::$app->cache->set($key, $value, 45);
```
Later when we need to access this variable (in either the same or a different web request), we call [[get()]]
with the key to retrieve it from cache. If the value returned is `false`, it means the value is not available
in cache and we should regenerate it:
```php
public function getCachedData()
{
$key = /* generate unique key here */;
$value = Yii::$app->getCache()->get($key);
if ($value === false) {
$value = /* regenerate value because it is not found in cache and then save it in cache for later use */;
Yii::$app->cache->set($id, $value);
}
return $value;
}
```
This is the common pattern of arbitrary data caching for general use.
When choosing the key for a variable to be cached, make sure the key is unique among all other variables that
may be cached in the application. It is **NOT** required that the key is unique across applications because
the cache component is intelligent enough to differentiate keys for different applications.
Some cache storages, such as MemCache, APC, support retrieving multiple cached values in a batch mode,
which may reduce the overhead involved in retrieving cached data. A method named [[mget()]] is provided
to exploit this feature. In case the underlying cache storage does not support this feature,
[[mget()]] will still simulate it.
To remove a cached value from cache, call [[delete()]]; and to remove everything from cache, call [[flush()]].
Be very careful when calling [[flush()]] because it also removes cached data that are from other applications.
Note, because [[Cache]] implements `ArrayAccess`, a cache component can be used liked an array. The followings
are some examples:
```php
$cache = Yii::$app->getComponent('cache');
$cache['var1'] = $value1; // equivalent to: $cache->set('var1', $value1);
$value2 = $cache['var2']; // equivalent to: $value2 = $cache->get('var2');
```
### Cache Dependency
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.data#cache-dependency
### Query Caching
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.data#query-caching
Fragment Caching
----------------
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment
### Caching Options
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment#caching-options
### Nested Caching
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment#nested-caching
Dynamic Content
---------------
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.dynamic
Page Caching
------------
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page
### Output Caching
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#output-caching
### HTTP Caching
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#http-caching

187
docs/guide/controller.md

@ -0,0 +1,187 @@
Controller
==========
Controller is one of the key parts of the application. It determines how to handle incoming request and creates a response.
Most often a controller takes HTTP request data and returns HTML, JSON or XML as a response.
Basics
------
Controller resides in application's `controllers` directory is is named like `SiteController.php` where `Site`
part could be anything describing a set of actions it contains.
The basic web controller is a class that extends [[\yii\web\Controller]] and could be very simple:
```php
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public function actionIndex()
{
// will render view from "views/site/index.php"
return $this->render('index');
}
public function actionTest()
{
// will just print "test" to the browser
return 'test';
}
}
```
As you can see, typical controller contains actions that are public class methods named as `actionSomething`.
Routes
------
Each controller action has a corresponding internal route. In our example above `actionIndex` has `site/index` route
and `actionTest` has `site/test` route. In this route `site` is referred to as controller ID while `test` is referred to
as action ID.
By default you can access specific controller and action using the `http://example.com/?r=controller/action` URL. This
behavior is fully customizable. For details refer to [URL Management](url.md).
If controller is located inside a module its action internal route will be `module/controller/action`.
In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404.
### Defaults
If user isn't specifying any route i.e. using URL like `http://example.com/`, Yii assumes that default route should be
used. It is determined by [[\yii\web\Application::defaultRoute]] method and is `site` by default meaning that `SiteController`
will be loaded.
A controller has a default action. When the user request does not specify which action to execute by usign an URL such as
`http://example.com/?r=site`, the default action will be executed. By default, the default action is named as `index`.
It can be changed by setting the [[\yii\base\Controller::defaultAction]] property.
Action parameters
-----------------
It was already mentioned that a simple action is just a public method named as `actionSomething`. Now we'll review
ways that an action can get parameters from HTTP.
### Action parameters
You can define named arguments for an action and these will be automatically populated from corresponding values from
`$_GET`. This is very convenient both because of the short syntax and an ability to specify defaults:
```php
namespace app\controllers;
use yii\web\Controller;
class BlogController extends Controller
{
public function actionView($id, $version = null)
{
$post = Post::find($id);
$text = $post->text;
if($version) {
$text = $post->getHistory($version);
}
return $this->render('view', array(
'post' => $post,
'text' => $text,
));
}
}
```
The action above can be accessed using either `http://example.com/?r=blog/view&id=42` or
`http://example.com/?r=blog/view&id=42&version=3`. In the first case `version` isn't specified and default parameter
value is used instead.
### Getting data from request
If your action is working with data from HTTP POST or has too many GET parameters you can rely on request object that
is accessible via `\Yii::$app->request`:
```php
namespace app\controllers;
use yii\web\Controller;
use yii\web\HttpException;
class BlogController extends Controller
{
public function actionUpdate($id)
{
$post = Post::find($id);
if(!$post) {
throw new HttpException(404);
}
if(\Yii::$app->request->isPost)) {
$post->load($_POST);
if($post->save()) {
$this->redirect(array('view', 'id' => $post->id));
}
}
return $this->render('update', array(
'post' => $post,
));
}
}
```
Standalone actions
------------------
If action is generic enough it makes sense to implement it in a separate class to be able to reuse it.
Create `actions/Page.php`
```php
namespace \app\actions;
class Page extends \yii\base\Action
{
public $view = 'index';
public function run()
{
$this->controller->render($view);
}
}
```
The following code is too simple to implement as a separate action but gives an idea of how it works. Action implemented
can be used in your controller as following:
```php
public SiteController extends \yii\web\Controller
{
public function actions()
{
return array(
'about' => array(
'class' => '@app/actions/Page',
'view' => 'about',
),
),
);
}
}
```
After doing so you can access your action as `http://example.com/?r=site/about`.
Filters
-------
Catching all incoming requests
------------------------------
See also
--------
- [Console](console.md)

0
docs/guide/dao.md

237
docs/guide/database-basics.md

@ -0,0 +1,237 @@
Database basics
===============
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides
uniform API and solves some inconsistencies between different DBMS. By default Yii supports MySQL, SQLite, PostgreSQL,
Oracle and MSSQL.
Configuration
-------------
In order to start using database you need to configure database connection component first by adding `db` component
to application configuration (for "basic" web application it's `config/web.php`) like the following:
```php
return array(
// ...
'components' => array(
// ...
'db' => array(
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=mydatabase', // MySQL, MariaDB
//'dsn' => 'sqlite:/path/to/database/file', // SQLite
//'dsn' => 'pgsql:host=localhost;port=5432;dbname=mydatabase', // PostgreSQL
//'dsn' => 'sqlsrv:Server=localhost;Database=mydatabase', // MS SQL Server, sqlsrv driver
//'dsn' => 'dblib:host=localhost;dbname=mydatabase', // MS SQL Server, dblib driver
//'dsn' => 'mssql:host=localhost;dbname=mydatabase', // MS SQL Server, mssql driver
//'dsn' => 'oci:dbname=//localhost:1521/testdb', // Oracle
'username' => 'root',
'password' => '',
'charset' => 'utf8',
),
),
// ...
);
```
After the component is configured you can access it using the following syntax:
```php
$connection = \Yii::$app->db;
```
You can refer to [[\yii\db\Connection]] for a list of properties you can configure. Also note that you can define more
than one connection component and use both at the same time if needed:
```php
$primaryConnection = \Yii::$app->db;
$secondaryConnection = \Yii::$app->secondDb;
```
If you don't want to define the connection as an application component you can instantiate it directly:
```php
$connection = new \yii\db\Connection(array(
'dsn' => $dsn,
'username' => $username,
'password' => $password,
));
$connection->open();
```
Basic SQL queries
-----------------
Once you have a connection instance you can execute SQL queries using [[\yii\db\Command]].
### SELECT
When query returns a set of rows:
```php
$command = $connection->createCommand('SELECT * FROM tbl_post');
$posts = $command->queryAll();
```
When only a single row is returned:
```php
$command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=1');
$post = $command->query();
```
When there are multiple values from the same column:
```php
$command = $connection->createCommand('SELECT title FROM tbl_post');
$titles = $command->queryColumn();
```
When there's a scalar value:
```php
$command = $connection->createCommand('SELECT COUNT(*) FROM tbl_post');
$postCount = $command->queryScalar();
```
### UPDATE, INSERT, DELETE etc.
If SQL executed doesn't return any data you can use command's `execute` method:
```php
$command = $connection->createCommand('UPDATE tbl_post SET status=1 WHERE id=1');
$command->execute();
```
Alternatively the following syntax that takes care of proper table and column names quoting is possible:
```php
// INSERT
$connection->createCommand()->insert('tbl_user', array(
'name' => 'Sam',
'age' => 30,
))->execute();
// INSERT multiple rows at once
$connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
array('Tom', 30),
array('Jane', 20),
array('Linda', 25),
))->execute();
// UPDATE
$connection->createCommand()->update('tbl_user', array(
'status' => 1,
), 'age > 30')->execute();
// DELETE
$connection->createCommand()->delete('tbl_user', 'status = 0')->execute();
```
Quoting table and column names
------------------------------
Most of the time you would use the following syntax for quoting table and column names:
```php
$sql = "SELECT COUNT([[$column]]) FROM {{$table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();
```
In the code above `[[X]]` will be converted to properly quoted column name while `{{Y}}` will be converted to properly
quoted table name.
The alternative is to quote table and column names manually using [[\yii\db\Connection::quoteTableName()]] and
[[\yii\db\Connection::quoteColumnName()]]:
```php
$column = $connection->quoteColumnName($column);
$table = $connection->quoteTableName($table);
$sql = "SELECT COUNT($column) FROM $table";
$rowCount = $connection->createCommand($sql)->queryScalar();
```
Prepared statements
-------------------
In order to securely pass query parameters you can use prepared statements:
```php
$command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=:id');
$command->bindValue(':id', $_GET['id']);
$post = $command->query();
```
Another usage is performing a query multiple times while preparing it only once:
```php
$command = $connection->createCommand('DELETE FROM tbl_post WHERE id=:id');
$command->bindParam(':id', $id);
$id = 1;
$command->execute();
$id = 2;
$command->execute();
```
Transactions
------------
If the underlying DBMS supports transactions, you can perform transactional SQL queries like the following:
```php
$transaction = $connection->beginTransaction();
try {
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
// ... executing other SQL statements ...
$transaction->commit();
} catch(Exception $e) {
$transaction->rollback();
}
```
Working with database schema
----------------------------
### Getting schema information
You can get a [[\yii\db\Schema]] instance like the following:
```php
$schema = $connection->getSchema();
```
It contains a set of methods allowing you to retrieve various information about the database:
```php
$tables = $schema->getTableNames();
```
For the full reference check [[\yii\db\Schema]].
### Modifying schema
Aside from basic SQL queries [[\yii\db\Command]] contains a set of methods allowing to modify database schema:
- createTable, renameTable, dropTable, truncateTable
- addColumn, renameColumn, dropColumn, alterColumn
- addPrimaryKey, dropPrimaryKey
- addForeignKey, dropForeignKey
- createIndex, dropIndex
These can be used as follows:
```php
// CREATE TABLE
$connection->createCommand()->createTable('tbl_post', array(
'id' => 'pk',
'title' => 'string',
'text' => 'text',
);
```
For the full reference check [[\yii\db\Command]].

94
docs/guide/index.md

@ -1,30 +1,64 @@
* [Overview](overview.md) Introduction
* [Installation](installation.md) ============
* [Bootstrap with Yii](bootstrap.md)
* [MVC Overview](mvc.md) - [Overview](overview.md)
* [Controller](controller.md)
* [Model](model.md) Getting started
* [View](view.md) ===============
* [Application](application.md)
* [Form](form.md) - [Installation](installation.md)
* [Data Validation](validation.md) - [Bootstrap with Yii](bootstrap.md)
* [Database Access Objects](dao.md) - [Configuration](configuration.md)
* [Query Builder](query-builder.md)
* [ActiveRecord](active-record.md) Base concepts
* [Database Migration](migration.md) =============
* [Caching](caching.md)
* [Internationalization](i18n.md) - [MVC Overview](mvc.md)
* [Extending Yii](extension.md) - [Controller](controller.md)
* [Authentication](authentication.md) - [Model](model.md)
* [Authorization](authorization.md) - [View](view.md)
* [Logging](logging.md) - [Application](application.md)
* [URL Management](url.md)
* [Theming](theming.md) Database
* [Error Handling](error.md) ========
* [Template](template.md)
* [Console Application](console.md) - [Basics](database-basics.md)
* [Security](security.md) - [Query Builder](query-builder.md)
* [Performance Tuning](performance.md) - [ActiveRecord](active-record.md)
* [Testing](testing.md) - [Database Migration](migration.md)
* [Automatic Code Generation](gii.md)
* [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) Extensions
==========
- [Extending Yii](extension.md)
- [Using template engines](template.md)
Security and access control
===========================
- [Authentication](authentication.md)
- [Authorization](authorization.md)
- [Security](security.md)
- Role based access control
Toolbox
=======
- [Automatic Code Generation](gii.md)
- Debug toolbar
- [Error Handling](error.md)
- [Logging](logging.md)
More
====
- [Form](form.md)
- [Model validation reference](validation.md)
- [Caching](caching.md)
- [Internationalization](i18n.md)
- [URL Management](url.md)
- [Theming](theming.md)
- [Console Application](console.md)
- [Performance Tuning](performance.md)
- [Testing](testing.md)
- [Upgrading from 1.1 to 2.0](upgrade-from-v1.md)

59
docs/guide/migration.md

@ -28,25 +28,24 @@ Creating Migrations
To create a new migration (e.g. create a news table), we run the following command: To create a new migration (e.g. create a news table), we run the following command:
~~~ ```
yii migrate/create <name> yii migrate/create <name>
~~~ ```
The required `name` parameter specifies a very brief description of the migration 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 (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, is used as part of a PHP class name. Therefore, it should only contain letters,
digits and/or underscore characters. digits and/or underscore characters.
~~~ ```
yii migrate/create create_news_table yii migrate/create create_news_table
~~~ ```
The above command will create under the `protected/migrations` directory a new The above command will create under the `protected/migrations` directory a new
file named `m101129_185401_create_news_table.php` which contains the following file named `m101129_185401_create_news_table.php` which contains the following
initial code: initial code:
~~~ ```php
[php]
class m101129_185401_create_news_table extends \yii\db\Migration class m101129_185401_create_news_table extends \yii\db\Migration
{ {
public function up() public function up()
@ -59,7 +58,7 @@ class m101129_185401_create_news_table extends \yii\db\Migration
return false; return false;
} }
} }
~~~ ```
Notice that the class name is the same as the file name which is of the pattern 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 `m<timestamp>_<name>`, where `<timestamp>` refers to the UTC timestamp (in the
@ -78,8 +77,7 @@ method returns `false` to indicate that the migration cannot be reverted.
As an example, let's show the migration about creating a news table. As an example, let's show the migration about creating a news table.
~~~ ```php
[php]
class m101129_185401_create_news_table extends \yii\db\Migration class m101129_185401_create_news_table extends \yii\db\Migration
{ {
public function up() public function up()
@ -96,7 +94,7 @@ class m101129_185401_create_news_table extends \yii\db\Migration
$this->db->createCommand()->dropTable('tbl_news')->execute(); $this->db->createCommand()->dropTable('tbl_news')->execute();
} }
} }
~~~ ```
The base class [\yii\db\Migration] exposes a database connection via `db` 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. property. You can use it for manipulating data and schema of a database.
@ -112,8 +110,7 @@ DB transactions.
We could explicitly start a DB transaction and enclose the rest of the DB-related We could explicitly start a DB transaction and enclose the rest of the DB-related
code within the transaction, like the following: code within the transaction, like the following:
~~~ ```php
[php]
class m101129_185401_create_news_table extends \yii\db\Migration class m101129_185401_create_news_table extends \yii\db\Migration
{ {
public function up() public function up()
@ -138,7 +135,7 @@ class m101129_185401_create_news_table extends \yii\db\Migration
// ...similar code for down() // ...similar code for down()
} }
~~~ ```
> Note: Not all DBMS support transactions. And some DB queries cannot be put > 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 > into a transaction. In this case, you will have to implement `up()` and
@ -152,9 +149,9 @@ Applying Migrations
To apply all available new migrations (i.e., make the local database up-to-date), To apply all available new migrations (i.e., make the local database up-to-date),
run the following command: run the following command:
~~~ ```
yii migrate yii migrate
~~~ ```
The command will show the list of all new migrations. If you confirm to apply 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 the migrations, it will run the `up()` method in every new migration class, one
@ -169,18 +166,18 @@ application component.
Sometimes, we may only want to apply one or a few new migrations. We can use the Sometimes, we may only want to apply one or a few new migrations. We can use the
following command: following command:
~~~ ```
yii migrate/up 3 yii migrate/up 3
~~~ ```
This command will apply the 3 new migrations. Changing the value 3 will allow This command will apply the 3 new migrations. Changing the value 3 will allow
us to change the number of migrations to be applied. us to change the number of migrations to be applied.
We can also migrate the database to a specific version with the following command: We can also migrate the database to a specific version with the following command:
~~~ ```
yii migrate/to 101129_185401 yii migrate/to 101129_185401
~~~ ```
That is, we use the timestamp part of a migration name to specify the version 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 that we want to migrate the database to. If there are multiple migrations between
@ -195,9 +192,9 @@ Reverting Migrations
To revert the last one or several applied migrations, we can use the following To revert the last one or several applied migrations, we can use the following
command: command:
~~~ ```
yii migrate/down [step] yii migrate/down [step]
~~~ ```
where the optional `step` parameter specifies how many migrations to be reverted where the optional `step` parameter specifies how many migrations to be reverted
back. It defaults to 1, meaning reverting back the last applied migration. back. It defaults to 1, meaning reverting back the last applied migration.
@ -212,9 +209,9 @@ Redoing Migrations
Redoing migrations means first reverting and then applying the specified migrations. Redoing migrations means first reverting and then applying the specified migrations.
This can be done with the following command: This can be done with the following command:
~~~ ```
yii migrate/redo [step] yii migrate/redo [step]
~~~ ```
where the optional `step` parameter specifies how many migrations to be redone. where the optional `step` parameter specifies how many migrations to be redone.
It defaults to 1, meaning redoing the last migration. It defaults to 1, meaning redoing the last migration.
@ -226,10 +223,10 @@ Showing Migration Information
Besides applying and reverting migrations, the migration tool can also display Besides applying and reverting migrations, the migration tool can also display
the migration history and the new migrations to be applied. the migration history and the new migrations to be applied.
~~~ ```
yii migrate/history [limit] yii migrate/history [limit]
yii migrate/new [limit] yii migrate/new [limit]
~~~ ```
where the optional parameter `limit` specifies the number of migrations to be where the optional parameter `limit` specifies the number of migrations to be
displayed. If `limit` is not specified, all available migrations will be displayed. displayed. If `limit` is not specified, all available migrations will be displayed.
@ -246,9 +243,9 @@ version without actually applying or reverting the relevant migrations. This
often happens when developing a new migration. We can use the following command often happens when developing a new migration. We can use the following command
to achieve this goal. to achieve this goal.
~~~ ```
yii migrate/mark 101129_185401 yii migrate/mark 101129_185401
~~~ ```
This command is very similar to `yii migrate/to` command, except that it only This command is very similar to `yii migrate/to` command, except that it only
modifies the migration history table to the specified version without applying modifies the migration history table to the specified version without applying
@ -290,17 +287,17 @@ line:
To specify these options, execute the migrate command using the following format To specify these options, execute the migrate command using the following format
~~~ ```
yii migrate/up --option1=value1 --option2=value2 ... yii migrate/up --option1=value1 --option2=value2 ...
~~~ ```
For example, if we want to migrate for a `forum` module whose migration files 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 are located within the module's `migrations` directory, we can use the following
command: command:
~~~ ```
yii migrate/up --migrationPath=ext.forum.migrations yii migrate/up --migrationPath=ext.forum.migrations
~~~ ```
### Configure Command Globally ### Configure Command Globally

260
docs/guide/model.md

@ -0,0 +1,260 @@
Model
=====
A model in Yii is intended for application data storage and has the following basic features:
- attribute declaration: a model defines what is considered an attribute.
- attribute labels: each attribute may be associated with a label for display purpose.
- massive attribute assignment.
- scenario-based data validation.
Models extending from [[\yii\base\Model]] class are typically used to hold data and corresponding validation rules of complex web forms.
The class is also a base for more advanced models with additional functionality such as [Active Record](active-record.md).
Attributes
----------
Attributes store the actual data represented by a model and can
be accessed like object member variables. For example, a `Post` model
may contain a `title` attribute and a `content` attribute which may be
accessed as follows:
```php
$post = new Post;
$post->title = 'Hello, world';
$post->content = 'Something interesting is happening';
echo $post->title;
echo $post->content;
```
Since model implements [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) interface you can use it
as if it was an array:
```php
$post = new Post;
$post['title'] = 'Hello, world';
$post['content'] = 'Something interesting is happening';
echo $post['title'];
echo $post['content'];
```
Default model implementation has a strict rule that all its attributes should be explicitly declared as public and
non-static class properties such as the following:
```php
// LoginForm has two attributes: username and password
class LoginForm extends \yii\base\Model
{
public $username;
public $password;
}
```
In order to change this, you can override `attributes()` method that returns a list of model attribute names.
Attribute labels
----------------
Attribute labels are mainly used for display purpose. For example, given an attribute `firstName`, we can declare
a label `First Name` which is more user-friendly and can be displayed to end users for example as a form label.
By default an attribute label is generated using [[\yii\base\Model\generateAttributeLabel()]] but the better way is to
specify it explicitly like the following:
```php
// LoginForm has two attributes: username and password
class LoginForm extends \yii\base\Model
{
public $username;
public $password;
public function attributeLabels()
{
reuturn array(
'username' => 'Your name',
'password' => 'Your password',
);
}
}
```
Scenarios
---------
A model may be used in different scenarios. For example, a `User` model may be used to collect user login inputs,
and it may also be used for user registration purpose. For this reason, each model has a property named `scenario`
which stores the name of the scenario that the model is currently being used in. As we will explain in the next
few sections, the concept of scenario is mainly used for validation and massive attribute assignment.
Associated with each scenario is a list of attributes that are *active* in that particular scenario. For example,
in the `login` scenario, only the `username` and `password` attributes are active; while in the `register` scenario,
additional attributes such as `email` are *active*.
Possible scenarios should be listed in the `scenarios()` method which returns an array whose keys are the scenario
names and whose values are the corresponding active attribute lists. Below is an example:
```php
class User extends \yii\db\ActiveRecord
{
public function scenarios()
{
return array(
'login' => array('username', 'password'),
'register' => array('username', 'email', 'password'),
);
}
}
```
Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want it to be validated).
We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example,
```php
array('username', 'password', '!secret')
```
Active model scenario could be set using one of the following ways:
```php
class EmployeeController extends \yii\web\Controller
{
public function actionCreate($id = null)
{
// first way
$employee = new Employee(array('scenario' => 'managementPanel'));
// second way
$employee = new Employee;
$employee->scenario = 'managementPanel';
// third way
$employee = Employee::find()->where('id = :id', array(':id' => $id))->one();
if ($employee !== null) {
$employee->setScenario('managementPanel');
}
}
}
```
Validation
----------
When a model is used to collect user input data via its attributes, it usually needs to validate the affected attributes
to make sure they satisfy certain requirements, such as an attribute cannot be empty, an attribute must contain letters
only, etc. If errors are found in validation, they may be presented to the user to help him fix the errors.
The following example shows how the validation is performed:
```php
$model = new LoginForm;
$model->username = $_POST['username'];
$model->password = $_POST['password'];
if ($model->validate()) {
// ... login the user ...
} else {
$errors = $model->getErrors();
// ... display the errors to the end user ...
}
```
The possible validation rules for a model should be listed in its `rules()` method. Each validation rule applies to one
or several attributes and is effective in one or several scenarios. A rule can be specified using a validator object - an
instance of a [[\yii\validators\Validator]] child class, or an array with the following format:
```php
array(
'attribute1, attribute2, ...',
'validator class or alias',
// specifies in which scenario(s) this rule is active.
// if not given, it means it is active in all scenarios
'on' => 'scenario1, scenario2, ...',
// the following name-value pairs will be used
// to initialize the validator properties
'property1' => 'value1',
'property2' => 'value2',
// ...
)
```
When `validate()` is called, the actual validation rules executed are determined using both of the following criteria:
- the rule must be associated with at least one active attribute;
- the rule must be active for the current scenario.
### Active Attributes
An attribute is *active* if it is subject to some validations in the current scenario.
### Safe Attributes
An attribute is *safe* if it can be massively assigned in the current scenario.
Massive Attribute Retrieval and Assignment
------------------------------------------
Attributes can be massively retrieved via the `attributes` property.
The following code will return *all* attributes in the `$post` model
as an array of name-value pairs.
```php
$attributes = $post->attributes;
var_dump($attributes);
```
Using the same `attributes` property you can massively assign data from associative array to model attributes:
```php
$attributes = array(
'title' => 'Model attributes',
'create_time' => time(),
);
$post->attributes = $attributes;
```
In the code above we're assigning corresponding data to model attributes named as array keys. The key difference from mass
retrieval that always works for all attributes is that in order to be assigned an attribute should be **safe** else
it will be ignored.
Validation rules and mass assignment
------------------------------------
In Yii2 unlike Yii 1.x validation rules are separated from mass assignment. Validation
rules are described in `rules()` method of the model while what's safe for mass
assignment is described in `scenarios` method:
```php
function rules()
{
return array(
// rule applied when corresponding field is "safe"
array('username', 'length', 'min' => 2),
array('first_name', 'length', 'min' => 2),
array('password', 'required'),
// rule applied when scenario is "signup" no matter if field is "safe" or not
array('hashcode', 'check', 'on' => 'signup'),
);
}
function scenarios()
{
return array(
// on signup allow mass assignment of username
'signup' => array('username', 'password'),
'update' => array('username', 'first_name'),
);
}
```
Note that everything is unsafe by default and you can't make field "safe" without specifying scenario.
See also
--------
- [Model validation reference](validation.md)
- [[\yii\base\Model]]

87
docs/guide/template.md

@ -1,3 +1,86 @@
Template Using template engines
======== ======================
By default Yii uses PHP as template language but you can configure it to be able
to render templates with special engines such as Twig or Smarty.
The component responsible for rendering a view is called `view`. You can add
a custom template engines as follows:
```php
array(
'components' => array(
'view' => array(
'class' => 'yii\base\View',
'renderers' => array(
'tpl' => array(
'class' => 'yii\renderers\SmartyViewRenderer',
),
'twig' => array(
'class' => 'yii\renderers\TwigViewRenderer',
'twigPath' => '@app/vendors/Twig',
),
// ...
),
),
),
)
```
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
----
In order to use Twig you need to put you templates in files with extension `.twig`
(or another one if configured differently).
Also you need to specify this extension explicitly when calling `$this->render()`
or `$this->renderPartial()` from your controller:
```php
echo $this->render('renderer.twig', array('username' => 'Alex'));
```
### Additional functions
Additionally to regular Twig syntax the following is available in Yii:
```php
<a href="{{ path('blog/view', {'alias' : post.alias}) }}">{{ post.title }}</a>
```
path function calls `Html::url()` internally.
### Additional variables
- `app` = `\Yii::$app`
- `this` = current `View` object
Smarty
------
In order to use Smarty you need to put you templates in files with extension `.tpl`
(or another one if configured differently).
Also you need to specify this extension explicitly when calling `$this->render()`
or `$this->renderPartial()` from your controller:
```php
echo $this->render('renderer.tpl', array('username' => 'Alex'));
```
### Additional functions
Additionally to regular Smarty syntax the following is available in Yii:
```php
<a href="{path route='blog/view' alias=$post.alias}">{$post.title}</a>
```
path function calls `Html::url()` internally.
### Additional variables
- `$app` = `\Yii::$app`
- `$this` = current `View` object

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

@ -163,6 +163,26 @@ A model is now associated with a form name returned by its `formName()` method.
mainly used when using HTML forms to collect user inputs for a model. Previously in 1.1, 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. this is usually hardcoded as the class name of the model.
A new methods called `load()` and `Model::loadMultiple()` is introduced to simplify the data population from user inputs
to a model. For example,
```php
$model = new Post;
if ($model->load($_POST)) {...}
// which is equivalent to:
if (isset($_POST['Post'])) {
$model->attributes = $_POST['Post'];
}
$model->save();
$postTags = array();
$tagsCount = count($_POST['PostTag']);
while($tagsCount-- > 0){
$postTags[] = new PostTag(array('post_id' => $model->id));
}
Model::loadMultiple($postTags, $_POST);
```
Yii 2.0 introduces a new method called `scenarios()` to declare which attributes require 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 validation under which scenario. Child classes should overwrite `scenarios()` to return
@ -196,18 +216,6 @@ Controllers
The `render()` and `renderPartial()` methods now return the rendering results instead of directly 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(...);`. 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'];
}
```
Widgets Widgets
------- -------
@ -288,7 +296,6 @@ public function behaviors()
'class' => 'yii\web\AccessControl', 'class' => 'yii\web\AccessControl',
'rules' => array( 'rules' => array(
array('allow' => true, 'actions' => array('admin'), 'roles' => array('@')), array('allow' => true, 'actions' => array('admin'), 'roles' => array('@')),
array('allow' => false),
), ),
), ),
); );

0
docs/autoloader.md → docs/internals/autoloader.md

214
docs/model.md

@ -1,214 +0,0 @@
Model
=====
Attributes
----------
Attributes store the actual data represented by a model and can
be accessed like object member variables. For example, a `Post` model
may contain a `title` attribute and a `content` attribute which may be
accessed as follows:
~~~php
$post->title = 'Hello, world';
$post->content = 'Something interesting is happening';
echo $post->title;
echo $post->content;
~~~
A model should list all its available attributes in the `attributes()` method.
Attributes may be implemented in various ways. The [[\yii\base\Model]] class
implements attributes as public member variables of the class, while the
[[\yii\db\ActiveRecord]] class implements them as DB table columns. For example,
~~~php
// LoginForm has two attributes: username and password
class LoginForm extends \yii\base\Model
{
public $username;
public $password;
}
// Post is associated with the tbl_post DB table.
// Its attributes correspond to the columns in tbl_post
class Post extends \yii\db\ActiveRecord
{
public function table()
{
return 'tbl_post';
}
}
~~~
### Attribute Labels
Scenarios
---------
A model may be used in different scenarios. For example, a `User` model may be
used to collect user login inputs, and it may also be used for user registration
purpose. For this reason, each model has a property named `scenario` which stores
the name of the scenario that the model is currently being used in. As we will explain
in the next few sections, the concept of scenario is mainly used in validation and
massive attribute assignment.
Associated with each scenario is a list of attributes that are *active* in that
particular scenario. For example, in the `login` scenario, only the `username`
and `password` attributes are active; while in the `register` scenario,
additional attributes such as `email` are *active*.
Possible scenarios should be listed in the `scenarios()` method which returns an array
whose keys are the scenario names and whose values are the corresponding
active attribute lists. Below is an example:
~~~php
class User extends \yii\db\ActiveRecord
{
public function table()
{
return 'tbl_user';
}
public function scenarios()
{
return array(
'login' => array('username', 'password'),
'register' => array('username', 'email', 'password'),
);
}
}
~~~
Sometimes, we want to mark that an attribute is not safe for massive assignment
(but we still want it to be validated). We may do so by prefixing an exclamation
character to the attribute name when declaring it in `scenarios()`. For example,
~~~php
array('username', 'password', '!secret')
~~~
Validation
----------
When a model is used to collect user input data via its attributes,
it usually needs to validate the affected attributes to make sure they
satisfy certain requirements, such as an attribute cannot be empty,
an attribute must contain letters only, etc. If errors are found in
validation, they may be presented to the user to help him fix the errors.
The following example shows how the validation is performed:
~~~php
$model = new LoginForm;
$model->username = $_POST['username'];
$model->password = $_POST['password'];
if ($model->validate()) {
// ...login the user...
} else {
$errors = $model->getErrors();
// ...display the errors to the end user...
}
~~~
The possible validation rules for a model should be listed in its
`rules()` method. Each validation rule applies to one or several attributes
and is effective in one or several scenarios. A rule can be specified
using a validator object - an instance of a [[\yii\validators\Validator]]
child class, or an array with the following format:
~~~php
array(
'attribute1, attribute2, ...',
'validator class or alias',
// specifies in which scenario(s) this rule is active.
// if not given, it means it is active in all scenarios
'on' => 'scenario1, scenario2, ...',
// the following name-value pairs will be used
// to initialize the validator properties...
'name1' => 'value1',
'name2' => 'value2',
....
)
~~~
When `validate()` is called, the actual validation rules executed are
determined using both of the following criteria:
* the rules must be associated with at least one active attribute;
* the rules must be active for the current scenario.
### Active Attributes
An attribute is *active* if it is subject to some validations in the current scenario.
### Safe Attributes
An attribute is *safe* if it can be massively assigned in the current scenario.
Massive Access of Attributes
----------------------------
Massive Attribute Retrieval
---------------------------
Attributes can be massively retrieved via the `attributes` property.
The following code will return *all* attributes in the `$post` model
as an array of name-value pairs.
~~~php
$attributes = $post->attributes;
var_dump($attributes);
~~~
Massive Attribute Assignment
----------------------------
Safe Attributes
---------------
Safe attributes are those that can be massively assigned. For example,
Validation rules and mass assignment
------------------------------------
In Yii2 unlike Yii 1.x validation rules are separated from mass assignment. Validation
rules are described in `rules()` method of the model while what's safe for mass
assignment is described in `scenarios` method:
```php
function rules() {
return array(
// rule applied when corresponding field is "safe"
array('username', 'length', 'min' => 2),
array('first_name', 'length', 'min' => 2),
array('password', 'required'),
// rule applied when scenario is "signup" no matter if field is "safe" or not
array('hashcode', 'check', 'on' => 'signup'),
);
}
function scenarios() {
return array(
// on signup allow mass assignment of username
'signup' => array('username', 'password'),
'update' => array('username', 'first_name'),
);
}
```
Note that everything is unsafe by default and you can't make field "safe"
without specifying scenario.

85
docs/view_renderers.md

@ -1,85 +0,0 @@
Yii2 view renderers
===================
By default Yii uses PHP as template language but you can configure it to be able
to render templates with special engines such as Twig or Smarty.
The component responsible for rendering a view is called `view`. You can add
a custom template engines as follows:
```php
array(
'components' => array(
'view' => array(
'class' => 'yii\base\View',
'renderers' => array(
'tpl' => array(
'class' => 'yii\renderers\SmartyViewRenderer',
),
'twig' => array(
'class' => 'yii\renderers\TwigViewRenderer',
'twigPath' => '@app/vendors/Twig',
),
// ...
),
),
),
)
```
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
----
In order to use Twig you need to put you templates in files with extension `.twig`
(or another one if configured differently).
Also you need to specify this extension explicitly when calling `$this->render()`
or `$this->renderPartial()` from your controller:
```php
echo $this->render('renderer.twig', array('username' => 'Alex'));
```
### Additional functions
Additionally to regular Twig syntax the following is available in Yii:
```php
<a href="{{ path('blog/view', {'alias' : post.alias}) }}">{{ post.title }}</a>
```
path function calls `Html::url()` internally.
### Additional variables
- `app` = `\Yii::$app`
- `this` = current `View` object
Smarty
------
In order to use Smarty you need to put you templates in files with extension `.tpl`
(or another one if configured differently).
Also you need to specify this extension explicitly when calling `$this->render()`
or `$this->renderPartial()` from your controller:
```php
echo $this->render('renderer.tpl', array('username' => 'Alex'));
```
### Additional functions
Additionally to regular Smarty syntax the following is available in Yii:
```php
<a href="{path route='blog/view' alias=$post.alias}">{$post.title}</a>
```
path function calls `Html::url()` internally.
### Additional variables
- `$app` = `\Yii::$app`
- `$this` = current `View` object

38
framework/yii/YiiBase.php

@ -10,7 +10,7 @@ use yii\base\Exception;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\InvalidParamException; use yii\base\InvalidParamException;
use yii\base\UnknownClassException; use yii\base\UnknownClassException;
use yii\logging\Logger; use yii\log\Logger;
/** /**
* Gets the application start timestamp. * Gets the application start timestamp.
@ -65,7 +65,7 @@ class YiiBase
* @var boolean whether to search PHP include_path when autoloading unknown classes. * @var boolean whether to search PHP include_path when autoloading unknown classes.
* You may want to turn this off if you are also using autoloaders from other libraries. * You may want to turn this off if you are also using autoloaders from other libraries.
*/ */
public static $enableIncludePath = true; public static $enableIncludePath = false;
/** /**
* @var \yii\console\Application|\yii\web\Application the application instance * @var \yii\console\Application|\yii\web\Application the application instance
*/ */
@ -478,7 +478,7 @@ class YiiBase
public static function trace($message, $category = 'application') public static function trace($message, $category = 'application')
{ {
if (YII_DEBUG) { if (YII_DEBUG) {
self::getLogger()->log($message, Logger::LEVEL_TRACE, $category); self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category);
} }
} }
@ -491,7 +491,7 @@ class YiiBase
*/ */
public static function error($message, $category = 'application') public static function error($message, $category = 'application')
{ {
self::getLogger()->log($message, Logger::LEVEL_ERROR, $category); self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category);
} }
/** /**
@ -503,7 +503,7 @@ class YiiBase
*/ */
public static function warning($message, $category = 'application') public static function warning($message, $category = 'application')
{ {
self::getLogger()->log($message, Logger::LEVEL_WARNING, $category); self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category);
} }
/** /**
@ -515,7 +515,7 @@ class YiiBase
*/ */
public static function info($message, $category = 'application') public static function info($message, $category = 'application')
{ {
self::getLogger()->log($message, Logger::LEVEL_INFO, $category); self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category);
} }
/** /**
@ -537,7 +537,7 @@ class YiiBase
*/ */
public static function beginProfile($token, $category = 'application') public static function beginProfile($token, $category = 'application')
{ {
self::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
} }
/** /**
@ -549,29 +549,7 @@ class YiiBase
*/ */
public static function endProfile($token, $category = 'application') public static function endProfile($token, $category = 'application')
{ {
self::getLogger()->log($token, Logger::LEVEL_PROFILE_END, $category); self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category);
}
/**
* Returns the message logger object.
* @return \yii\logging\Logger message logger
*/
public static function getLogger()
{
if (self::$_logger !== null) {
return self::$_logger;
} else {
return self::$_logger = new Logger;
}
}
/**
* Sets the logger object.
* @param Logger $logger the logger object.
*/
public static function setLogger($logger)
{
self::$_logger = $logger;
} }
/** /**

6
framework/yii/assets/yii.activeForm.js

@ -41,6 +41,9 @@
// a callback that is called before validating each attribute. The signature of the callback should be: // a callback that is called before validating each attribute. The signature of the callback should be:
// function ($form, attribute, messages) { ...return false to cancel the validation...} // function ($form, attribute, messages) { ...return false to cancel the validation...}
beforeValidate: undefined, beforeValidate: undefined,
// a callback that is called after an attribute is validated. The signature of the callback should be:
// function ($form, attribute, messages)
afterValidate: undefined,
// the GET parameter name indicating an AJAX-based validation // the GET parameter name indicating an AJAX-based validation
ajaxVar: 'ajax' ajaxVar: 'ajax'
}; };
@ -333,6 +336,9 @@
$input = findInput($form, attribute), $input = findInput($form, attribute),
hasError = false; hasError = false;
if (data.settings.afterValidate) {
data.settings.afterValidate($form, attribute, messages);
}
attribute.status = 1; attribute.status = 1;
if ($input.length) { if ($input.length) {
hasError = messages && $.isArray(messages[attribute.name]) && messages[attribute.name].length; hasError = messages && $.isArray(messages[attribute.name]) && messages[attribute.name].length;

41
framework/yii/base/Application.php

@ -19,6 +19,23 @@ use yii\web\HttpException;
abstract class Application extends Module abstract class Application extends Module
{ {
/** /**
* @event Event an event raised before the application starts to handle a request.
*/
const EVENT_BEFORE_REQUEST = 'beforeRequest';
/**
* @event Event an event raised after the application successfully handles a request (before the response is sent out).
*/
const EVENT_AFTER_REQUEST = 'afterRequest';
/**
* @event ActionEvent an event raised before executing a controller action.
* You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
*/
const EVENT_BEFORE_ACTION = 'beforeAction';
/**
* @event ActionEvent an event raised after executing a controller action.
*/
const EVENT_AFTER_ACTION = 'afterAction';
/**
* @var string the application name. * @var string the application name.
*/ */
public $name = 'My Application'; public $name = 'My Application';
@ -124,6 +141,16 @@ abstract class Application extends Module
} }
/** /**
* Loads components that are declared in [[preload]].
* @throws InvalidConfigException if a component or module to be preloaded is unknown
*/
public function preloadComponents()
{
$this->getComponent('log');
parent::preloadComponents();
}
/**
* Registers error handlers. * Registers error handlers.
*/ */
public function registerErrorHandlers() public function registerErrorHandlers()
@ -146,7 +173,9 @@ abstract class Application extends Module
*/ */
public function run() public function run()
{ {
$this->trigger(self::EVENT_BEFORE_REQUEST);
$response = $this->handleRequest($this->getRequest()); $response = $this->handleRequest($this->getRequest());
$this->trigger(self::EVENT_AFTER_REQUEST);
$response->send(); $response->send();
return $response->exitStatus; return $response->exitStatus;
} }
@ -247,6 +276,15 @@ abstract class Application extends Module
} }
/** /**
* Returns the log component.
* @return \yii\log\Logger the log component
*/
public function getLog()
{
return $this->getComponent('log');
}
/**
* Returns the error handler component. * Returns the error handler component.
* @return ErrorHandler the error handler application component. * @return ErrorHandler the error handler application component.
*/ */
@ -325,6 +363,9 @@ abstract class Application extends Module
public function registerCoreComponents() public function registerCoreComponents()
{ {
$this->setComponents(array( $this->setComponents(array(
'log' => array(
'class' => 'yii\log\Logger',
),
'errorHandler' => array( 'errorHandler' => array(
'class' => 'yii\base\ErrorHandler', 'class' => 'yii\base\ErrorHandler',
), ),

40
framework/yii/base/Controller.php

@ -26,7 +26,6 @@ class Controller extends Component
* @event ActionEvent an event raised right after executing a controller action. * @event ActionEvent an event raised right after executing a controller action.
*/ */
const EVENT_AFTER_ACTION = 'afterAction'; const EVENT_AFTER_ACTION = 'afterAction';
/** /**
* @var string the ID of this controller * @var string the ID of this controller
*/ */
@ -111,12 +110,15 @@ class Controller extends Component
$oldAction = $this->action; $oldAction = $this->action;
$this->action = $action; $this->action = $action;
$result = null; $result = null;
if ($this->module->beforeAction($action)) { $event = new ActionEvent($action);
if ($this->beforeAction($action)) { $this->trigger(Application::EVENT_BEFORE_ACTION, $event);
if ($event->isValid && $this->module->beforeAction($action) && $this->beforeAction($action)) {
$result = $action->runWithParams($params); $result = $action->runWithParams($params);
$this->afterAction($action, $result); $this->afterAction($action, $result);
}
$this->module->afterAction($action, $result); $this->module->afterAction($action, $result);
$event = new ActionEvent($action);
$event->result = &$result;
Yii::$app->trigger(Application::EVENT_AFTER_ACTION, $event);
} }
$this->action = $oldAction; $this->action = $oldAction;
return $result; return $result;
@ -213,7 +215,7 @@ class Controller extends Component
public function afterAction($action, &$result) public function afterAction($action, &$result)
{ {
$event = new ActionEvent($action); $event = new ActionEvent($action);
$event->result = &$result; $event->result = & $result;
$this->trigger(self::EVENT_AFTER_ACTION, $event); $this->trigger(self::EVENT_AFTER_ACTION, $event);
} }
@ -247,34 +249,6 @@ class Controller extends Component
} }
/** /**
* Populates one or multiple models from the given data array.
* @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array.
* @param Model $model the model to be populated. If there are more than one model to be populated,
* you may supply them as additional parameters.
* @return boolean whether at least one model is successfully populated with the data.
*/
public function populate($data, $model)
{
$success = false;
if (!empty($data) && is_array($data)) {
$models = func_get_args();
array_shift($models);
foreach ($models as $model) {
/** @var Model $model */
$scope = $model->formName();
if ($scope == '') {
$model->setAttributes($data);
$success = true;
} elseif (isset($data[$scope])) {
$model->setAttributes($data[$scope]);
$success = true;
}
}
}
return $success;
}
/**
* Renders a view and applies layout if available. * Renders a view and applies layout if available.
* *
* The view to be rendered can be specified in one of the following formats: * The view to be rendered can be specified in one of the following formats:

2
framework/yii/base/ErrorHandler.php

@ -82,7 +82,7 @@ class ErrorHandler extends Component
*/ */
protected function renderException($exception) protected function renderException($exception)
{ {
if (Yii::$app instanceof \yii\console\Application) { if (Yii::$app instanceof \yii\console\Application || YII_ENV === 'test') {
echo Yii::$app->renderException($exception); echo Yii::$app->renderException($exception);
return; return;
} }

72
framework/yii/base/Formatter.php

@ -37,6 +37,10 @@ class Formatter extends Component
*/ */
public $datetimeFormat = 'Y/m/d h:i:s A'; public $datetimeFormat = 'Y/m/d h:i:s A';
/** /**
* @var string the text to be displayed when formatting a null. Defaults to '(not set)'.
*/
public $nullDisplay;
/**
* @var array the text to be displayed when formatting a boolean value. The first element corresponds * @var array the text to be displayed when formatting a boolean value. The first element corresponds
* to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`. * to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`.
*/ */
@ -61,6 +65,29 @@ class Formatter extends Component
if (empty($this->booleanFormat)) { if (empty($this->booleanFormat)) {
$this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes')); $this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes'));
} }
if ($this->nullDisplay === null) {
$this->nullDisplay = Yii::t('yii', '(not set)');
}
}
/**
* Formats the value based on the give type.
* This method will call one of the "as" methods available in this class to do the formatting.
* For type "xyz", the method "asXyz" will be used. For example, if the type is "html",
* then [[asHtml()]] will be used. Type names are case insensitive.
* @param mixed $value the value to be formatted
* @param string $type the type of the value, e.g., "html", "text".
* @return string the formatting result
* @throws InvalidParamException if the type is not supported by this class.
*/
public function format($value, $type)
{
$method = 'as' . $type;
if (method_exists($this, $method)) {
return $this->$method($value);
} else {
throw new InvalidParamException("Unknown type: $type");
}
} }
/** /**
@ -71,6 +98,9 @@ class Formatter extends Component
*/ */
public function asRaw($value) public function asRaw($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return $value; return $value;
} }
@ -81,6 +111,9 @@ class Formatter extends Component
*/ */
public function asText($value) public function asText($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return Html::encode($value); return Html::encode($value);
} }
@ -91,6 +124,9 @@ class Formatter extends Component
*/ */
public function asNtext($value) public function asNtext($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return nl2br(Html::encode($value)); return nl2br(Html::encode($value));
} }
@ -103,6 +139,9 @@ class Formatter extends Component
*/ */
public function asParagraphs($value) public function asParagraphs($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return str_replace('<p></p>', '', return str_replace('<p></p>', '',
'<p>' . preg_replace('/[\r\n]{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>' '<p>' . preg_replace('/[\r\n]{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>'
); );
@ -118,6 +157,9 @@ class Formatter extends Component
*/ */
public function asHtml($value, $config = null) public function asHtml($value, $config = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return HtmlPurifier::process($value, $config); return HtmlPurifier::process($value, $config);
} }
@ -128,6 +170,9 @@ class Formatter extends Component
*/ */
public function asEmail($value) public function asEmail($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return Html::mailto($value); return Html::mailto($value);
} }
@ -138,6 +183,9 @@ class Formatter extends Component
*/ */
public function asImage($value) public function asImage($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return Html::img($value); return Html::img($value);
} }
@ -148,6 +196,9 @@ class Formatter extends Component
*/ */
public function asUrl($value) public function asUrl($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$url = $value; $url = $value;
if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
$url = 'http://' . $url; $url = 'http://' . $url;
@ -163,6 +214,9 @@ class Formatter extends Component
*/ */
public function asBoolean($value) public function asBoolean($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return $value ? $this->booleanFormat[1] : $this->booleanFormat[0]; return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
} }
@ -183,6 +237,9 @@ class Formatter extends Component
*/ */
public function asDate($value, $format = null) public function asDate($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value); $value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->dateFormat : $format, $value); return date($format === null ? $this->dateFormat : $format, $value);
} }
@ -204,6 +261,9 @@ class Formatter extends Component
*/ */
public function asTime($value, $format = null) public function asTime($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value); $value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->timeFormat : $format, $value); return date($format === null ? $this->timeFormat : $format, $value);
} }
@ -225,6 +285,9 @@ class Formatter extends Component
*/ */
public function asDatetime($value, $format = null) public function asDatetime($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value); $value = $this->normalizeDatetimeValue($value);
return date($format === null ? $this->datetimeFormat : $format, $value); return date($format === null ? $this->datetimeFormat : $format, $value);
} }
@ -256,6 +319,9 @@ class Formatter extends Component
*/ */
public function asInteger($value) public function asInteger($value)
{ {
if ($value === null) {
return $this->nullDisplay;
}
if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) { if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
return $matches[1]; return $matches[1];
} else { } else {
@ -274,6 +340,9 @@ class Formatter extends Component
*/ */
public function asDouble($value, $decimals = 2) public function asDouble($value, $decimals = 2)
{ {
if ($value === null) {
return $this->nullDisplay;
}
if ($this->decimalSeparator === null) { if ($this->decimalSeparator === null) {
return sprintf("%.{$decimals}f", $value); return sprintf("%.{$decimals}f", $value);
} else { } else {
@ -292,6 +361,9 @@ class Formatter extends Component
*/ */
public function asNumber($value, $decimals = 0) public function asNumber($value, $decimals = 0)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.'; $ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.';
$ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ','; $ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ',';
return number_format($value, $decimals, $ds, $ts); return number_format($value, $decimals, $ds, $ts);

86
framework/yii/base/Model.php

@ -160,12 +160,10 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
{ {
$attributes = array(); $attributes = array();
foreach ($this->getActiveValidators() as $validator) { foreach ($this->getActiveValidators() as $validator) {
if ($validator->isActive('default')) {
foreach ($validator->attributes as $name) { foreach ($validator->attributes as $name) {
$attributes[$name] = true; $attributes[$name] = true;
} }
} }
}
return array( return array(
'default' => array_keys($attributes), 'default' => array_keys($attributes),
); );
@ -251,9 +249,16 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
* validation rules should be validated. * validation rules should be validated.
* @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
* @return boolean whether the validation is successful without any error. * @return boolean whether the validation is successful without any error.
* @throws InvalidParamException if the current scenario is unknown.
*/ */
public function validate($attributes = null, $clearErrors = true) public function validate($attributes = null, $clearErrors = true)
{ {
$scenarios = $this->scenarios();
$scenario = $this->getScenario();
if (!isset($scenarios[$scenario])) {
throw new InvalidParamException("Unknown scenario: $scenario");
}
if ($clearErrors) { if ($clearErrors) {
$this->clearErrors(); $this->clearErrors();
} }
@ -582,8 +587,9 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
/** /**
* Sets the scenario for the model. * Sets the scenario for the model.
* Note that this method does not check if the scenario exists or not.
* The method [[validate()]] will perform this check.
* @param string $value the scenario that this model is in. * @param string $value the scenario that this model is in.
* @see getScenario
*/ */
public function setScenario($value) public function setScenario($value)
{ {
@ -638,6 +644,80 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
} }
/** /**
* Populates the model with the data from end user.
* The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]].
* If [[formName()]] is empty, the whole `$data` array will be used to populate the model.
* The data being populated is subject to the safety check by [[setAttributes()]].
* @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
* supplied by end user.
* @return boolean whether the model is successfully populated with some data.
*/
public function load($data)
{
$scope = $this->formName();
if ($scope == '') {
$this->setAttributes($data);
return true;
} elseif (isset($data[$scope])) {
$this->setAttributes($data[$scope]);
return true;
} else {
return false;
}
}
/**
* Populates a set of models with the data from end user.
* This method is mainly used to collect tabular data input.
* The data to be loaded for each model is `$data[formName][index]`, where `formName`
* refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
* If [[formName()]] is empty, `$data[index]` will be used to populate each model.
* The data being populated to each model is subject to the safety check by [[setAttributes()]].
* @param array $models the models to be populated. Note that all models should have the same class.
* @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
* supplied by end user.
* @return boolean whether the model is successfully populated with some data.
*/
public static function loadMultiple($models, $data)
{
/** @var Model $model */
$model = reset($models);
if ($model === false) {
return false;
}
$success = false;
$scope = $model->formName();
foreach ($models as $i => $model) {
if ($scope == '') {
if (isset($data[$i])) {
$model->setAttributes($data[$i]);
$success = true;
}
} elseif (isset($data[$scope][$i])) {
$model->setAttributes($data[$scope][$i]);
$success = true;
}
}
return $success;
}
/**
* Validates multiple models.
* @param array $models the models to be validated
* @return boolean whether all models are valid. False will be returned if one
* or multiple models have validation error.
*/
public static function validateMultiple($models)
{
$valid = true;
/** @var Model $model */
foreach ($models as $model) {
$valid = $model->validate() && $valid;
}
return $valid;
}
/**
* Converts the object into an array. * Converts the object into an array.
* The default implementation will return [[attributes]]. * The default implementation will return [[attributes]].
* @return array the array representation of the object * @return array the array representation of the object

32
framework/yii/base/Module.php

@ -37,20 +37,11 @@ use Yii;
abstract class Module extends Component abstract class Module extends Component
{ {
/** /**
* @event ActionEvent an event raised before executing a controller action.
* You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
*/
const EVENT_BEFORE_ACTION = 'beforeAction';
/**
* @event ActionEvent an event raised after executing a controller action.
*/
const EVENT_AFTER_ACTION = 'afterAction';
/**
* @var array custom module parameters (name => value). * @var array custom module parameters (name => value).
*/ */
public $params = array(); public $params = array();
/** /**
* @var array the IDs of the components that should be preloaded when this module is created. * @var array the IDs of the components or modules that should be preloaded when this module is created.
*/ */
public $preload = array(); public $preload = array();
/** /**
@ -468,7 +459,6 @@ abstract class Module extends Component
if ($this->_components[$id] instanceof Object) { if ($this->_components[$id] instanceof Object) {
return $this->_components[$id]; return $this->_components[$id];
} elseif ($load) { } elseif ($load) {
Yii::trace("Loading component: $id", __METHOD__);
return $this->_components[$id] = Yii::createObject($this->_components[$id]); return $this->_components[$id] = Yii::createObject($this->_components[$id]);
} }
} }
@ -556,11 +546,18 @@ abstract class Module extends Component
/** /**
* Loads components that are declared in [[preload]]. * Loads components that are declared in [[preload]].
* @throws InvalidConfigException if a component or module to be preloaded is unknown
*/ */
public function preloadComponents() public function preloadComponents()
{ {
foreach ($this->preload as $id) { foreach ($this->preload as $id) {
if ($this->hasComponent($id)) {
$this->getComponent($id); $this->getComponent($id);
} elseif ($this->hasModule($id)) {
$this->getModule($id);
} else {
throw new InvalidConfigException("Unknown component or module: $id");
}
} }
} }
@ -643,28 +640,25 @@ abstract class Module extends Component
} }
/** /**
* This method is invoked right before an action is to be executed (after all possible filters.) * This method is invoked right before an action of this module is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action. * You may override this method to do last-minute preparation for the action.
* Make sure you call the parent implementation so that the relevant event is triggered.
* @param Action $action the action to be executed. * @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed. * @return boolean whether the action should continue to be executed.
*/ */
public function beforeAction($action) public function beforeAction($action)
{ {
$event = new ActionEvent($action); return true;
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
return $event->isValid;
} }
/** /**
* This method is invoked right after an action is executed. * This method is invoked right after an action of this module has been executed.
* You may override this method to do some postprocessing for the action. * You may override this method to do some postprocessing for the action.
* Make sure you call the parent implementation so that the relevant event is triggered.
* @param Action $action the action just executed. * @param Action $action the action just executed.
* @param mixed $result the action return result. * @param mixed $result the action return result.
*/ */
public function afterAction($action, &$result) public function afterAction($action, &$result)
{ {
$event = new ActionEvent($action);
$event->result = &$result;
$this->trigger(self::EVENT_AFTER_ACTION, $event);
} }
} }

14
framework/yii/base/View.php

@ -25,14 +25,22 @@ use yii\widgets\FragmentCache;
class View extends Component class View extends Component
{ {
/** /**
* @event ViewEvent an event that is triggered by [[beginPage()]]. * @event Event an event that is triggered by [[beginPage()]].
*/ */
const EVENT_BEGIN_PAGE = 'beginPage'; const EVENT_BEGIN_PAGE = 'beginPage';
/** /**
* @event ViewEvent an event that is triggered by [[endPage()]]. * @event Event an event that is triggered by [[endPage()]].
*/ */
const EVENT_END_PAGE = 'endPage'; const EVENT_END_PAGE = 'endPage';
/** /**
* @event Event an event that is triggered by [[beginBody()]].
*/
const EVENT_BEGIN_BODY = 'beginBody';
/**
* @event Event an event that is triggered by [[endBody()]].
*/
const EVENT_END_BODY = 'endBody';
/**
* @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file. * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file.
*/ */
const EVENT_BEFORE_RENDER = 'beforeRender'; const EVENT_BEFORE_RENDER = 'beforeRender';
@ -532,6 +540,7 @@ class View extends Component
public function beginBody() public function beginBody()
{ {
echo self::PL_BODY_BEGIN; echo self::PL_BODY_BEGIN;
$this->trigger(self::EVENT_BEGIN_BODY);
} }
/** /**
@ -539,6 +548,7 @@ class View extends Component
*/ */
public function endBody() public function endBody()
{ {
$this->trigger(self::EVENT_END_BODY);
echo self::PL_BODY_END; echo self::PL_BODY_END;
} }

13
framework/yii/classes.php

@ -41,13 +41,12 @@ return array(
'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php', 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php',
'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php', 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php',
'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php',
'yii\logging\Target' => YII_PATH . '/logging/Target.php', 'yii\log\Target' => YII_PATH . '/log/Target.php',
'yii\logging\DebugTarget' => YII_PATH . '/logging/DebugTarget.php', 'yii\log\DebugTarget' => YII_PATH . '/log/DebugTarget.php',
'yii\logging\Router' => YII_PATH . '/logging/Router.php', 'yii\log\Logger' => YII_PATH . '/log/Logger.php',
'yii\logging\Logger' => YII_PATH . '/logging/Logger.php', 'yii\log\EmailTarget' => YII_PATH . '/log/EmailTarget.php',
'yii\logging\EmailTarget' => YII_PATH . '/logging/EmailTarget.php', 'yii\log\DbTarget' => YII_PATH . '/log/DbTarget.php',
'yii\logging\DbTarget' => YII_PATH . '/logging/DbTarget.php', 'yii\log\FileTarget' => YII_PATH . '/log/FileTarget.php',
'yii\logging\FileTarget' => YII_PATH . '/logging/FileTarget.php',
'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php', 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php',
'yii\widgets\Captcha' => YII_PATH . '/widgets/Captcha.php', 'yii\widgets\Captcha' => YII_PATH . '/widgets/Captcha.php',
'yii\widgets\ListPager' => YII_PATH . '/widgets/ListPager.php', 'yii\widgets\ListPager' => YII_PATH . '/widgets/ListPager.php',

11
framework/yii/logging/DebugTarget.php → framework/yii/debug/LogTarget.php

@ -5,15 +5,16 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\logging; namespace yii\debug;
use Yii; use Yii;
use yii\log\Target;
/** /**
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class DebugTarget extends Target class LogTarget extends Target
{ {
public $maxLogFiles = 20; public $maxLogFiles = 20;
@ -29,7 +30,7 @@ class DebugTarget extends Target
if (!is_dir($path)) { if (!is_dir($path)) {
mkdir($path); mkdir($path);
} }
$file = $path . '/' . Yii::getLogger()->getTag() . '.log'; $file = $path . '/' . Yii::$app->getLog()->getTag() . '.log';
$data = array( $data = array(
'messages' => $messages, 'messages' => $messages,
'_SERVER' => $_SERVER, '_SERVER' => $_SERVER,
@ -54,9 +55,6 @@ class DebugTarget extends Target
*/ */
public function collect($messages, $final) public function collect($messages, $final)
{ {
if (Yii::$app->getModule('debug', false) !== null) {
return;
}
$this->messages = array_merge($this->messages, $this->filterMessages($messages)); $this->messages = array_merge($this->messages, $this->filterMessages($messages));
if ($final) { if ($final) {
$this->export($this->messages); $this->export($this->messages);
@ -72,6 +70,7 @@ class DebugTarget extends Target
$iterator = new \DirectoryIterator(Yii::$app->getRuntimePath() . '/debug'); $iterator = new \DirectoryIterator(Yii::$app->getRuntimePath() . '/debug');
$files = array(); $files = array();
foreach ($iterator as $file) { foreach ($iterator as $file) {
/** @var \DirectoryIterator $file */
if (preg_match('/^[\d\-]+\.log$/', $file->getFileName()) && $file->isFile()) { if (preg_match('/^[\d\-]+\.log$/', $file->getFileName()) && $file->isFile()) {
$files[] = $file->getPathname(); $files[] = $file->getPathname();
} }

35
framework/yii/debug/Module.php

@ -7,6 +7,10 @@
namespace yii\debug; namespace yii\debug;
use Yii;
use yii\base\View;
use yii\helpers\Html;
/** /**
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
@ -14,4 +18,35 @@ namespace yii\debug;
class Module extends \yii\base\Module class Module extends \yii\base\Module
{ {
public $controllerNamespace = 'yii\debug\controllers'; public $controllerNamespace = 'yii\debug\controllers';
public $panels;
public function init()
{
parent::init();
Yii::$app->log->targets['debug'] = new LogTarget;
Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar'));
}
public function beforeAction($action)
{
Yii::$app->getView()->off(View::EVENT_END_BODY, array($this, 'renderToolbar'));
unset(Yii::$app->log->targets['debug']);
return parent::beforeAction($action);
}
public function renderToolbar($event)
{
/** @var View $view */
$id = 'yii-debug-toolbar';
$url = Yii::$app->getUrlManager()->createUrl('debug/default/toolbar', array(
'tag' => Yii::$app->getLog()->getTag(),
));
$view = $event->sender;
$view->registerJs("yii.debug.load('$id', '$url');");
$view->registerAssetBundle('yii/debug');
echo Html::tag('div', '', array(
'id' => $id,
'style' => 'display: none',
));
}
} }

38
framework/yii/debug/Toolbar.php

@ -1,38 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use Yii;
use yii\base\Widget;
use yii\helpers\Html;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Toolbar extends Widget
{
public $debugAction = 'debug/default/toolbar';
public function run()
{
if (Yii::$app->hasModule('debug')) {
$id = 'yii-debug-toolbar';
$url = Yii::$app->getUrlManager()->createUrl($this->debugAction, array(
'tag' => Yii::getLogger()->tag,
));
$view = $this->getView();
$view->registerJs("yii.debug.load('$id', '$url');");
$view->registerAssetBundle('yii/debug');
echo Html::tag('div', '', array(
'id' => $id,
'style' => 'display: none',
));
}
}
}

9
framework/yii/debug/controllers/DefaultController.php

@ -16,9 +16,11 @@ use yii\web\Controller;
*/ */
class DefaultController extends Controller class DefaultController extends Controller
{ {
public $layout = 'main';
public function actionIndex($tag) public function actionIndex($tag)
{ {
echo $tag; return $this->render('index');
} }
public function actionToolbar($tag) public function actionToolbar($tag)
@ -26,9 +28,10 @@ class DefaultController extends Controller
$file = Yii::$app->getRuntimePath() . "/debug/$tag.log"; $file = Yii::$app->getRuntimePath() . "/debug/$tag.log";
if (preg_match('/^[\w\-]+$/', $tag) && is_file($file)) { if (preg_match('/^[\w\-]+$/', $tag) && is_file($file)) {
$data = json_decode(file_get_contents($file), true); $data = json_decode(file_get_contents($file), true);
echo $this->renderPartial('toolbar', $data); $data['tag'] = $tag;
return $this->renderPartial('toolbar', $data);
} else { } else {
echo "Unable to find debug data tagged with '$tag'."; return "Unable to find debug data tagged with '$tag'.";
} }
} }
} }

1
framework/yii/debug/views/default/index.php

@ -0,0 +1 @@
here we are

27
framework/yii/debug/views/default/toolbar.php

@ -19,21 +19,22 @@ echo Html::style("
margin: 0 10px; margin: 0 10px;
"); ");
?> ?>
<div id="yii-debug-toolbar">
<div class="yii-debug-toolbar-block">
<?php echo Html::a('more details', array('index', 'tag' => $tag)); ?>
</div>
<div class="yii-debug-toolbar-block"> <div class="yii-debug-toolbar-block">
</div> Peak memory: <?php echo sprintf('%.2fMB', $memory / 1048576); ?>
</div>
<div class="yii-debug-toolbar-block">
Peak memory: <?php echo sprintf('%.2fMB', $memory / 1048576); ?>
</div>
<div class="yii-debug-toolbar-block"> <div class="yii-debug-toolbar-block">
Time spent: <?php echo sprintf('%.3fs', $time); ?> Time spent: <?php echo sprintf('%.3fs', $time); ?>
</div> </div>
<div class="yii-debug-toolbar-block"> <div class="yii-debug-toolbar-block">
</div> </div>
<div class="yii-debug-toolbar-block"> <div class="yii-debug-toolbar-block">
</div>
</div> </div>

21
framework/yii/debug/views/layouts/main.php

@ -0,0 +1,21 @@
<?php
/**
* @var \yii\base\View $this
* @var string $content
*/
use yii\helpers\Html;
?>
<!DOCTYPE html>
<html>
<?php $this->beginPage(); ?>
<head>
<title><?php echo Html::encode($this->title); ?></title>
<?php $this->head(); ?>
</head>
<body>
<?php $this->beginBody(); ?>
<?php echo $content; ?>
<?php $this->endBody(); ?>
</body>
<?php $this->endPage(); ?>
</html>

47
framework/yii/helpers/base/ArrayHelper.php

@ -21,13 +21,56 @@ use yii\base\InvalidParamException;
class ArrayHelper class ArrayHelper
{ {
/** /**
* Converts the object into an array. * Converts an object or an array of objects into an array.
* @param object|array $object the object to be converted into an array * @param object|array $object the object to be converted into an array
* @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays.
* The properties specified for each class is an array of the following format:
*
* ~~~
* array(
* 'app\models\Post' => array(
* 'id',
* 'title',
* // the key name in array result => property name
* 'createTime' => 'create_time',
* // the key name in array result => anonymous function
* 'length' => function ($post) {
* return strlen($post->content);
* },
* ),
* )
* ~~~
*
* The result of `ArrayHelper::toArray($post, $properties)` could be like the following:
*
* ~~~
* array(
* 'id' => 123,
* 'title' => 'test',
* 'createTime' => '2013-01-01 12:00AM',
* 'length' => 301,
* )
* ~~~
*
* @param boolean $recursive whether to recursively converts properties which are objects into arrays. * @param boolean $recursive whether to recursively converts properties which are objects into arrays.
* @return array the array representation of the object * @return array the array representation of the object
*/ */
public static function toArray($object, $recursive = true) public static function toArray($object, $properties = array(), $recursive = true)
{ {
if (!empty($properties) && is_object($object)) {
$className = get_class($object);
if (!empty($properties[$className])) {
$result = array();
foreach ($properties[$className] as $key => $name) {
if (is_int($key)) {
$result[$name] = $object->$name;
} else {
$result[$key] = static::getValue($object, $name);
}
}
return $result;
}
}
if ($object instanceof Arrayable) { if ($object instanceof Arrayable) {
$object = $object->toArray(); $object = $object->toArray();
if (!$recursive) { if (!$recursive) {

83
framework/yii/helpers/base/Html.php

@ -732,11 +732,12 @@ class Html
* @param string|array $selection the selected value(s). * @param string|array $selection the selected value(s).
* @param array $items the data item used to generate the checkboxes. * @param array $items the data item used to generate the checkboxes.
* The array keys are the labels, while the array values are the corresponding checkbox values. * The array keys are the labels, while the array values are the corresponding checkbox values.
* Note that the labels will NOT be HTML-encoded, while the values will.
* @param array $options options (name => config) for the checkbox list. The following options are supported: * @param array $options options (name => config) for the checkbox list. The following options are supported:
* *
* - unselect: string, the value that should be submitted when none of the checkboxes is selected. * - unselect: string, the value that should be submitted when none of the checkboxes is selected.
* By setting this option, a hidden input will be generated. * By setting this option, a hidden input will be generated.
* - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
* This option is ignored if `item` option is set.
* - separator: string, the HTML code that separates items. * - separator: string, the HTML code that separates items.
* - item: callable, a callback that can be used to customize the generation of the HTML code * - item: callable, a callback that can be used to customize the generation of the HTML code
* corresponding to a single item in $items. The signature of this callback must be: * corresponding to a single item in $items. The signature of this callback must be:
@ -757,6 +758,7 @@ class Html
} }
$formatter = isset($options['item']) ? $options['item'] : null; $formatter = isset($options['item']) ? $options['item'] : null;
$encode = !isset($options['encode']) || $options['encode'];
$lines = array(); $lines = array();
$index = 0; $index = 0;
foreach ($items as $value => $label) { foreach ($items as $value => $label) {
@ -766,7 +768,8 @@ class Html
if ($formatter !== null) { if ($formatter !== null) {
$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else { } else {
$lines[] = static::label(static::checkbox($name, $checked, array('value' => $value)) . ' ' . $label); $checkbox = static::checkbox($name, $checked, array('value' => $value));
$lines[] = static::label($checkbox . ' ' . ($encode ? static::encode($label) : $label));
} }
$index++; $index++;
} }
@ -790,11 +793,12 @@ class Html
* @param string|array $selection the selected value(s). * @param string|array $selection the selected value(s).
* @param array $items the data item used to generate the radio buttons. * @param array $items the data item used to generate the radio buttons.
* The array keys are the labels, while the array values are the corresponding radio button values. * The array keys are the labels, while the array values are the corresponding radio button values.
* Note that the labels will NOT be HTML-encoded, while the values will.
* @param array $options options (name => config) for the radio button list. The following options are supported: * @param array $options options (name => config) for the radio button list. The following options are supported:
* *
* - unselect: string, the value that should be submitted when none of the radio buttons is selected. * - unselect: string, the value that should be submitted when none of the radio buttons is selected.
* By setting this option, a hidden input will be generated. * By setting this option, a hidden input will be generated.
* - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
* This option is ignored if `item` option is set.
* - separator: string, the HTML code that separates items. * - separator: string, the HTML code that separates items.
* - item: callable, a callback that can be used to customize the generation of the HTML code * - item: callable, a callback that can be used to customize the generation of the HTML code
* corresponding to a single item in $items. The signature of this callback must be: * corresponding to a single item in $items. The signature of this callback must be:
@ -810,6 +814,7 @@ class Html
*/ */
public static function radioList($name, $selection = null, $items = array(), $options = array()) public static function radioList($name, $selection = null, $items = array(), $options = array())
{ {
$encode = !isset($options['encode']) || $options['encode'];
$formatter = isset($options['item']) ? $options['item'] : null; $formatter = isset($options['item']) ? $options['item'] : null;
$lines = array(); $lines = array();
$index = 0; $index = 0;
@ -820,7 +825,8 @@ class Html
if ($formatter !== null) { if ($formatter !== null) {
$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else { } else {
$lines[] = static::label(static::radio($name, $checked, array('value' => $value)) . ' ' . $label); $radio = static::radio($name, $checked, array('value' => $value));
$lines[] = static::label($radio . ' ' . ($encode ? static::encode($label) : $label));
} }
$index++; $index++;
} }
@ -837,6 +843,75 @@ class Html
} }
/** /**
* Generates an unordered list.
* @param array|\Traversable $items the items for generating the list. Each item generates a single list item.
* Note that items will be automatically HTML encoded if `$options['encode']` is not set or true.
* @param array $options options (name => config) for the radio button list. The following options are supported:
*
* - encode: boolean, whether to HTML-encode the items. Defaults to true.
* This option is ignored if the `item` option is specified.
* - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified.
* - item: callable, a callback that is used to generate each individual list item.
* The signature of this callback must be:
*
* ~~~
* function ($index, $item)
* ~~~
*
* where $index is the array key corresponding to `$item` in `$items`. The callback should return
* the whole list item tag.
*
* @return string the generated unordered list. An empty string is returned if `$items` is empty.
*/
public static function ul($items, $options = array())
{
if (empty($items)) {
return '';
}
$tag = isset($options['tag']) ? $options['tag'] : 'ul';
$encode = !isset($options['encode']) || $options['encode'];
$formatter = isset($options['item']) ? $options['item'] : null;
$itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array();
unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']);
$results = array();
foreach ($items as $index => $item) {
if ($formatter !== null) {
$results[] = call_user_func($formatter, $index, $item);
} else {
$results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions);
}
}
return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options);
}
/**
* Generates an ordered list.
* @param array|\Traversable $items the items for generating the list. Each item generates a single list item.
* Note that items will be automatically HTML encoded if `$options['encode']` is not set or true.
* @param array $options options (name => config) for the radio button list. The following options are supported:
*
* - encode: boolean, whether to HTML-encode the items. Defaults to true.
* This option is ignored if the `item` option is specified.
* - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified.
* - item: callable, a callback that is used to generate each individual list item.
* The signature of this callback must be:
*
* ~~~
* function ($index, $item)
* ~~~
*
* where $index is the array key corresponding to `$item` in `$items`. The callback should return
* the whole list item tag.
*
* @return string the generated ordered list. An empty string is returned if `$items` is empty.
*/
public static function ol($items, $options = array())
{
$options['tag'] = 'ol';
return static::ul($items, $options);
}
/**
* Generates a label tag for the given model attribute. * Generates a label tag for the given model attribute.
* The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]].
* @param Model $model the model object * @param Model $model the model object

11
framework/yii/helpers/base/Json.php

@ -33,7 +33,7 @@ class Json
public static function encode($value, $options = 0) public static function encode($value, $options = 0)
{ {
$expressions = array(); $expressions = array();
$value = static::processData($value, $expressions); $value = static::processData($value, $expressions, uniqid());
$json = json_encode($value, $options); $json = json_encode($value, $options);
return empty($expressions) ? $json : strtr($json, $expressions); return empty($expressions) ? $json : strtr($json, $expressions);
} }
@ -75,20 +75,21 @@ class Json
* Pre-processes the data before sending it to `json_encode()`. * Pre-processes the data before sending it to `json_encode()`.
* @param mixed $data the data to be processed * @param mixed $data the data to be processed
* @param array $expressions collection of JavaScript expressions * @param array $expressions collection of JavaScript expressions
* @param string $expPrefix a prefix internally used to handle JS expressions
* @return mixed the processed data * @return mixed the processed data
*/ */
protected static function processData($data, &$expressions) protected static function processData($data, &$expressions, $expPrefix)
{ {
if (is_array($data)) { if (is_array($data)) {
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
if (is_array($value) || is_object($value)) { if (is_array($value) || is_object($value)) {
$data[$key] = static::processData($value, $expressions); $data[$key] = static::processData($value, $expressions, $expPrefix);
} }
} }
return $data; return $data;
} elseif (is_object($data)) { } elseif (is_object($data)) {
if ($data instanceof JsExpression) { if ($data instanceof JsExpression) {
$token = '!{[' . count($expressions) . ']}!'; $token = "!{[$expPrefix=" . count($expressions) . ']}!';
$expressions['"' . $token . '"'] = $data->expression; $expressions['"' . $token . '"'] = $data->expression;
return $token; return $token;
} else { } else {
@ -96,7 +97,7 @@ class Json
$result = array(); $result = array();
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
if (is_array($value) || is_object($value)) { if (is_array($value) || is_object($value)) {
$result[$key] = static::processData($value, $expressions); $result[$key] = static::processData($value, $expressions, $expPrefix);
} else { } else {
$result[$key] = $value; $result[$key] = $value;
} }

21
framework/yii/i18n/Formatter.php

@ -120,6 +120,9 @@ class Formatter extends \yii\base\Formatter
*/ */
public function asDate($value, $format = null) public function asDate($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value); $value = $this->normalizeDatetimeValue($value);
if ($format === null) { if ($format === null) {
$format = $this->dateFormat; $format = $this->dateFormat;
@ -153,6 +156,9 @@ class Formatter extends \yii\base\Formatter
*/ */
public function asTime($value, $format = null) public function asTime($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value); $value = $this->normalizeDatetimeValue($value);
if ($format === null) { if ($format === null) {
$format = $this->timeFormat; $format = $this->timeFormat;
@ -186,6 +192,9 @@ class Formatter extends \yii\base\Formatter
*/ */
public function asDatetime($value, $format = null) public function asDatetime($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value); $value = $this->normalizeDatetimeValue($value);
if ($format === null) { if ($format === null) {
$format = $this->datetimeFormat; $format = $this->datetimeFormat;
@ -208,6 +217,9 @@ class Formatter extends \yii\base\Formatter
*/ */
public function asDecimal($value, $format = null) public function asDecimal($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value);
} }
@ -221,6 +233,9 @@ class Formatter extends \yii\base\Formatter
*/ */
public function asCurrency($value, $currency = 'USD', $format = null) public function asCurrency($value, $currency = 'USD', $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency);
} }
@ -233,6 +248,9 @@ class Formatter extends \yii\base\Formatter
*/ */
public function asPercent($value, $format = null) public function asPercent($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value);
} }
@ -245,6 +263,9 @@ class Formatter extends \yii\base\Formatter
*/ */
public function asScientific($value, $format = null) public function asScientific($value, $format = null)
{ {
if ($value === null) {
return $this->nullDisplay;
}
return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value);
} }

2
framework/yii/i18n/I18N.php

@ -161,7 +161,7 @@ class I18N extends Component
protected function getPluralRules($language) protected function getPluralRules($language)
{ {
if (isset($this->_pluralRules[$language])) { if (isset($this->_pluralRules[$language])) {
return $this->_pluralRules; return $this->_pluralRules[$language];
} }
$allRules = require(Yii::getAlias($this->pluralRuleFile)); $allRules = require(Yii::getAlias($this->pluralRuleFile));
if (isset($allRules[$language])) { if (isset($allRules[$language])) {

2
framework/yii/logging/DbTarget.php → framework/yii/log/DbTarget.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\logging; namespace yii\log;
use Yii; use Yii;
use yii\db\Connection; use yii\db\Connection;

2
framework/yii/logging/EmailTarget.php → framework/yii/log/EmailTarget.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\logging; namespace yii\log;
/** /**
* EmailTarget sends selected log messages to the specified email addresses. * EmailTarget sends selected log messages to the specified email addresses.

2
framework/yii/logging/FileTarget.php → framework/yii/log/FileTarget.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\logging; namespace yii\log;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;

73
framework/yii/logging/Logger.php → framework/yii/log/Logger.php

@ -5,13 +5,58 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\logging; namespace yii\log;
use \yii\base\Component; use Yii;
use \yii\base\InvalidConfigException; use yii\base\Component;
use yii\base\InvalidConfigException;
/** /**
* Logger records logged messages in memory. * Logger records logged messages in memory and sends them to different targets as needed.
*
* Logger is registered as a core application component and can be accessed using `Yii::$app->log`.
* You can call the method [[log()]] to record a single log message. For convenience, a set of shortcut
* methods are provided for logging messages of various severity levels via the [[Yii]] class:
*
* - [[Yii::trace()]]
* - [[Yii::error()]]
* - [[Yii::warning()]]
* - [[Yii::info()]]
* - [[Yii::beginProfile()]]
* - [[Yii::endProfile()]]
*
* When enough messages are accumulated in the logger, or when the current request finishes,
* the logged messages will be sent to different [[targets]], such as log files, emails.
*
* You may configure the targets in application configuration, like the following:
*
* ~~~
* array(
* 'components' => array(
* 'log' => array(
* 'targets' => array(
* 'file' => array(
* 'class' => 'yii\log\FileTarget',
* 'levels' => array('trace', 'info'),
* 'categories' => array('yii\*'),
* ),
* 'email' => array(
* 'class' => 'yii\log\EmailTarget',
* 'levels' => array('error', 'warning'),
* 'emails' => array('admin@example.com'),
* ),
* ),
* ),
* ),
* )
* ~~~
*
* Each log target can have a name and can be referenced via the [[targets]] property
* as follows:
*
* ~~~
* Yii::$app->log->targets['file']->enabled = false;
* ~~~
* *
* When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]] * When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]]
* to send logged messages to different log targets, such as file, email, Web. * to send logged messages to different log targets, such as file, email, Web.
@ -65,7 +110,7 @@ class Logger extends Component
*/ */
public $flushInterval = 1000; public $flushInterval = 1000;
/** /**
* @var array logged messages. This property is mainly managed by [[log()]] and [[flush()]]. * @var array logged messages. This property is managed by [[log()]] and [[flush()]].
* Each log message is of the following structure: * Each log message is of the following structure:
* *
* ~~~ * ~~~
@ -79,9 +124,10 @@ class Logger extends Component
*/ */
public $messages = array(); public $messages = array();
/** /**
* @var Router the log target router registered with this logger. * @var array the log targets. Each array element represents a single [[Target|log target]] instance
* or the configuration for creating the log target instance.
*/ */
public $router; public $targets = array();
/** /**
@ -96,6 +142,11 @@ class Logger extends Component
public function init() public function init()
{ {
parent::init(); parent::init();
foreach ($this->targets as $name => $target) {
if (!$target instanceof Target) {
$this->targets[$name] = Yii::createObject($target);
}
}
register_shutdown_function(array($this, 'flush'), true); register_shutdown_function(array($this, 'flush'), true);
} }
@ -132,13 +183,15 @@ class Logger extends Component
/** /**
* Flushes log messages from memory to targets. * Flushes log messages from memory to targets.
* This method will trigger an [[EVENT_FLUSH]] or [[EVENT_FINAL_FLUSH]] event depending on the $final value.
* @param boolean $final whether this is a final call during a request. * @param boolean $final whether this is a final call during a request.
*/ */
public function flush($final = false) public function flush($final = false)
{ {
if ($this->router) { /** @var Target $target */
$this->router->dispatch($this->messages, $final); foreach ($this->targets as $target) {
if ($target->enabled) {
$target->collect($this->messages, $final);
}
} }
$this->messages = array(); $this->messages = array();
} }

2
framework/yii/logging/Target.php → framework/yii/log/Target.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\logging; namespace yii\log;
use Yii; use Yii;
use yii\base\Component; use yii\base\Component;

98
framework/yii/logging/Router.php

@ -1,98 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\logging;
use Yii;
use yii\base\Component;
/**
* Router manages [[Target|log targets]] that record log messages in different media.
*
* For example, a [[FileTarget|file log target]] records log messages
* in files; an [[EmailTarget|email log target]] sends log messages
* to specific email addresses. Each log target may specify filters on
* message levels and categories to record specific messages only.
*
* Router and the targets it manages may be configured in application configuration,
* like the following:
*
* ~~~
* array(
* // preload log component when application starts
* 'preload' => array('log'),
* 'components' => array(
* 'log' => array(
* 'class' => 'yii\logging\Router',
* 'targets' => array(
* 'file' => array(
* 'class' => 'yii\logging\FileTarget',
* 'levels' => array('trace', 'info'),
* 'categories' => array('yii\*'),
* ),
* 'email' => array(
* 'class' => 'yii\logging\EmailTarget',
* 'levels' => array('error', 'warning'),
* 'emails' => array('admin@example.com'),
* ),
* ),
* ),
* ),
* )
* ~~~
*
* Each log target can have a name and can be referenced via the [[targets]] property
* as follows:
*
* ~~~
* Yii::$app->log->targets['file']->enabled = false;
* ~~~
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Router extends Component
{
/**
* @var Target[] list of log target objects or configurations. If the latter, target objects will
* be created in [[init()]] by calling [[Yii::createObject()]] with the corresponding object configuration.
*/
public $targets = array();
/**
* Initializes this application component.
* This method is invoked when the Router component is created by the application.
* The method attaches the [[processLogs]] method to both the [[Logger::EVENT_FLUSH]] event
* and the [[Logger::EVENT_FINAL_FLUSH]] event.
*/
public function init()
{
parent::init();
foreach ($this->targets as $name => $target) {
if (!$target instanceof Target) {
$this->targets[$name] = Yii::createObject($target);
}
}
Yii::getLogger()->router = $this;
}
/**
* Dispatches log messages to [[targets]].
* This method is called by [[Logger]] when its [[Logger::flush()]] method is called.
* It will forward the messages to each log target registered in [[targets]].
* @param array $messages the messages to be processed
* @param boolean $final whether this is the final call during a request cycle
*/
public function dispatch($messages, $final = false)
{
foreach ($this->targets as $target) {
if ($target->enabled) {
$target->collect($messages, $final);
}
}
}
}

6
framework/yii/web/AccessControl.php

@ -17,7 +17,7 @@ use yii\base\ActionFilter;
* AccessControl is an action filter. It will check its [[rules]] to find * AccessControl is an action filter. It will check its [[rules]] to find
* the first rule that matches the current context variables (such as user IP address, user role). * the first rule that matches the current context variables (such as user IP address, user role).
* The matching rule will dictate whether to allow or deny the access to the requested controller * The matching rule will dictate whether to allow or deny the access to the requested controller
* action. * action. If no rule matches, the access will be denied.
* *
* To use AccessControl, declare it in the `behaviors()` method of your controller class. * To use AccessControl, declare it in the `behaviors()` method of your controller class.
* For example, the following declarations will allow authenticated users to access the "create" * For example, the following declarations will allow authenticated users to access the "create"
@ -105,7 +105,7 @@ class AccessControl extends ActionFilter
/** @var $rule AccessRule */ /** @var $rule AccessRule */
foreach ($this->rules as $rule) { foreach ($this->rules as $rule) {
if ($allow = $rule->allows($action, $user, $request)) { if ($allow = $rule->allows($action, $user, $request)) {
break; return true;
} elseif ($allow === false) { } elseif ($allow === false) {
if (isset($rule->denyCallback)) { if (isset($rule->denyCallback)) {
call_user_func($rule->denyCallback, $rule); call_user_func($rule->denyCallback, $rule);
@ -117,7 +117,7 @@ class AccessControl extends ActionFilter
return false; return false;
} }
} }
return true; return false;
} }
/** /**

30
framework/yii/web/Controller.php

@ -83,4 +83,34 @@ class Controller extends \yii\base\Controller
} }
return Yii::$app->getUrlManager()->createUrl($route, $params); return Yii::$app->getUrlManager()->createUrl($route, $params);
} }
/**
* Redirects the browser to the specified URL.
* This method is a shortcut to [[Response::redirect()]].
*
* @param array|string $url the URL to be redirected to. [[\yii\helpers\Html::url()]]
* will be used to normalize the URL. If the resulting URL is still a relative URL
* (one without host info), the current request host info will be used.
* @param integer $statusCode the HTTP status code. If null, it will use 302
* for normal requests, and [[ajaxRedirectCode]] for AJAX requests.
* See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]]
* for details about HTTP status code
* @return Response the response object itself
*/
public function redirect($url, $statusCode = null)
{
return Yii::$app->getResponse()->redirect($url, $statusCode);
}
/**
* Refreshes the current page.
* This method is a shortcut to [[Response::refresh()]].
* @param string $anchor the anchor that should be appended to the redirection URL.
* Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
* @return Response the response object itself
*/
public function refresh($anchor = '')
{
return Yii::$app->getResponse()->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
}
} }

26
framework/yii/web/Response.php

@ -35,7 +35,7 @@ class Response extends \yii\base\Response
* @event ResponseEvent an event that is triggered right after [[prepare()]] is called in [[send()]]. * @event ResponseEvent an event that is triggered right after [[prepare()]] is called in [[send()]].
* You may respond to this event to filter the response content before it is sent to the client. * You may respond to this event to filter the response content before it is sent to the client.
*/ */
const EVENT_PREPARE = 'prepare'; const EVENT_AFTER_PREPARE = 'afterPrepare';
const FORMAT_RAW = 'raw'; const FORMAT_RAW = 'raw';
const FORMAT_HTML = 'html'; const FORMAT_HTML = 'html';
@ -218,6 +218,11 @@ class Response extends \yii\base\Response
*/ */
public function setStatusCode($value, $text = null) public function setStatusCode($value, $text = null)
{ {
if ($value === null) {
$this->_statusCode = null;
$this->statusText = null;
return;
}
$this->_statusCode = (int)$value; $this->_statusCode = (int)$value;
if ($this->getIsInvalid()) { if ($this->getIsInvalid()) {
throw new InvalidParamException("The HTTP status code is invalid: $value"); throw new InvalidParamException("The HTTP status code is invalid: $value");
@ -249,7 +254,7 @@ class Response extends \yii\base\Response
{ {
$this->trigger(self::EVENT_BEFORE_SEND, new ResponseEvent($this)); $this->trigger(self::EVENT_BEFORE_SEND, new ResponseEvent($this));
$this->prepare(); $this->prepare();
$this->trigger(self::EVENT_PREPARE, new ResponseEvent($this)); $this->trigger(self::EVENT_AFTER_PREPARE, new ResponseEvent($this));
$this->sendHeaders(); $this->sendHeaders();
$this->sendContent(); $this->sendContent();
$this->trigger(self::EVENT_AFTER_SEND, new ResponseEvent($this)); $this->trigger(self::EVENT_AFTER_SEND, new ResponseEvent($this));
@ -319,14 +324,8 @@ class Response extends \yii\base\Response
*/ */
protected function sendContent() protected function sendContent()
{ {
if (is_array($this->content)) {
echo 'array()';
} elseif (is_object($this->content)) {
echo method_exists($this->content, '__toString') ? (string)$this->content : get_class($this->content);
} else {
echo $this->content; echo $this->content;
} }
}
/** /**
* Sends a file to the browser. * Sends a file to the browser.
@ -721,12 +720,10 @@ class Response extends \yii\base\Response
} }
if ($formatter instanceof ResponseFormatter) { if ($formatter instanceof ResponseFormatter) {
$formatter->format($this); $formatter->format($this);
return;
} else { } else {
throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatter interface."); throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatter interface.");
} }
} } else {
switch ($this->format) { switch ($this->format) {
case self::FORMAT_HTML: case self::FORMAT_HTML:
$this->getHeaders()->setDefault('Content-Type', 'text/html; charset=' . $this->charset); $this->getHeaders()->setDefault('Content-Type', 'text/html; charset=' . $this->charset);
@ -755,4 +752,11 @@ class Response extends \yii\base\Response
throw new InvalidConfigException("Unsupported response format: {$this->format}"); throw new InvalidConfigException("Unsupported response format: {$this->format}");
} }
} }
if (is_array($this->content)) {
$this->content = 'array()';
} elseif (is_object($this->content)) {
$this->content = method_exists($this->content, '__toString') ? (string)$this->content : get_class($this->content);
}
}
} }

2
framework/yii/web/User.php

@ -416,7 +416,9 @@ class User extends Component
public function switchIdentity($identity, $duration = 0) public function switchIdentity($identity, $duration = 0)
{ {
$session = Yii::$app->getSession(); $session = Yii::$app->getSession();
if (YII_ENV !== 'test') {
$session->regenerateID(true); $session->regenerateID(true);
}
$this->setIdentity($identity); $this->setIdentity($identity);
$session->remove($this->idVar); $session->remove($this->idVar);
$session->remove($this->authTimeoutVar); $session->remove($this->authTimeoutVar);

38
framework/yii/widgets/ActiveForm.php

@ -12,6 +12,7 @@ use yii\base\Widget;
use yii\base\Model; use yii\base\Model;
use yii\helpers\Html; use yii\helpers\Html;
use yii\helpers\Json; use yii\helpers\Json;
use yii\web\JsExpression;
/** /**
* ActiveForm ... * ActiveForm ...
@ -103,6 +104,38 @@ class ActiveForm extends Widget
*/ */
public $ajaxVar = 'ajax'; public $ajaxVar = 'ajax';
/** /**
* @var string|JsExpression a JS callback that will be called when the form is being submitted.
* The signature of the callback should be:
*
* ~~~
* function ($form) {
* ...return false to cancel submission...
* }
* ~~~
*/
public $beforeSubmit;
/**
* @var string|JsExpression a JS callback that is called before validating an attribute.
* The signature of the callback should be:
*
* ~~~
* function ($form, attribute, messages) {
* ...return false to cancel the validation...
* }
* ~~~
*/
public $beforeValidate;
/**
* @var string|JsExpression a JS callback that is called after validating an attribute.
* The signature of the callback should be:
*
* ~~~
* function ($form, attribute, messages) {
* }
* ~~~
*/
public $afterValidate;
/**
* @var array the client validation options for individual attributes. Each element of the array * @var array the client validation options for individual attributes. Each element of the array
* represents the validation options for a particular attribute. * represents the validation options for a particular attribute.
* @internal * @internal
@ -157,6 +190,11 @@ class ActiveForm extends Widget
if ($this->validationUrl !== null) { if ($this->validationUrl !== null) {
$options['validationUrl'] = Html::url($this->validationUrl); $options['validationUrl'] = Html::url($this->validationUrl);
} }
foreach (array('beforeSubmit', 'beforeValidate', 'afterValidate') as $name) {
if (($value = $this->$name) !== null) {
$options[$name] = $value instanceof JsExpression ? $value : new JsExpression($value);
}
}
return $options; return $options;
} }

206
framework/yii/widgets/DetailView.php

@ -0,0 +1,206 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\Arrayable;
use yii\base\Formatter;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Inflector;
/**
* DetailView displays the detail of a single data [[model]].
*
* DetailView is best used for displaying a model in a regular format (e.g. each model attribute
* is displayed as a row in a table.) The model can be either an instance of [[Model]] or
* or an associative array.
*
* DetailView uses the [[attributes]] property to determines which model attributes
* should be displayed and how they should be formatted.
*
* A typical usage of DetailView is as follows:
*
* ~~~
* \yii\widgets\DetailView::widget(array(
* 'data' => $model,
* 'attributes' => array(
* 'title', // title attribute (in plain text)
* 'description:html', // description attribute in HTML
* array( // the owner name of the model
* 'label' => 'Owner',
* 'value' => $model->owner->name,
* ),
* ),
* ));
* ~~~
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DetailView extends Widget
{
/**
* @var array|object the data model whose details are to be displayed. This can be either a [[Model]] instance
* or an associative array.
*/
public $model;
/**
* @var array a list of attributes to be displayed in the detail view. Each array element
* represents the specification for displaying one particular attribute.
*
* An attribute can be specified as a string in the format of "Name" or "Name:Type", where "Name" refers to
* the attribute name, and "Type" represents the type of the attribute. The "Type" is passed to the [[Formatter::format()]]
* method to format an attribute value into a displayable text. Please refer to [[Formatter]] for the supported types.
*
* An attribute can also be specified in terms of an array with the following elements:
*
* - name: the attribute name. This is required if either "label" or "value" is not specified.
* - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
* - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
* by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
* according to the "type" option.
* - type: the type of the value that determines how the value would be formatted into a displayable text.
* Please refer to [[Formatter]] for supported types.
* - visible: whether the attribute is visible. If set to `false`, the attribute will be displayed.
*/
public $attributes;
/**
* @var string|\Closure the template used to render a single attribute. If a string, the token `{label}`
* and `{value}` will be replaced with the label and the value of the corresponding attribute.
* If an anonymous function, the signature must be as follows:
*
* ~~~
* function ($attribute, $index, $widget)
* ~~~
*
* where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based
* index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance.
*/
public $template = "<tr><th>{label}</th><td>{value}</td></tr>";
/**
* @var array the HTML attributes for the container tag of this widget. The "tag" option specifies
* what container tag should be used. It defaults to "table" if not set.
*/
public $options = array('class' => 'table table-striped');
/**
* @var array|Formatter the formatter used to format model attribute values into displayable texts.
* This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
* instance. If this property is not set, the "formatter" application component will be used.
*/
public $formatter;
/**
* Initializes the detail view.
* This method will initialize required property values.
*/
public function init()
{
if ($this->model === null) {
throw new InvalidConfigException('Please specify the "data" property.');
}
if ($this->formatter == null) {
$this->formatter = Yii::$app->getFormatter();
} elseif (is_array($this->formatter)) {
$this->formatter = Yii::createObject($this->formatter);
} elseif (!$this->formatter instanceof Formatter) {
throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
}
$this->normalizeAttributes();
}
/**
* Renders the detail view.
* This is the main entry of the whole detail view rendering.
*/
public function run()
{
$rows = array();
$i = 0;
foreach ($this->attributes as $attribute) {
$rows[] = $this->renderAttribute($attribute, $i++);
}
$tag = ArrayHelper::remove($this->options, 'tag', 'table');
echo Html::tag($tag, implode("\n", $rows), $this->options);
}
/**
* Renders a single attribute.
* @param array $attribute the specification of the attribute to be rendered.
* @param integer $index the zero-based index of the attribute in the [[attributes]] array
* @return string the rendering result
*/
protected function renderAttribute($attribute, $index)
{
if (is_string($this->template)) {
return strtr($this->template, array(
'{label}' => $attribute['label'],
'{value}' => $this->formatter->format($attribute['value'], $attribute['type']),
));
} else {
return call_user_func($this->template, $attribute, $index, $this);
}
}
/**
* Normalizes the attribute specifications.
* @throws InvalidConfigException
*/
protected function normalizeAttributes()
{
if ($this->attributes === null) {
if ($this->model instanceof Model) {
$this->attributes = $this->model->attributes();
} elseif (is_object($this->model)) {
$this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model));
} elseif (is_array($this->model)) {
$this->attributes = array_keys($this->model);
} else {
throw new InvalidConfigException('The "data" property must be either an array or an object.');
}
sort($this->attributes);
}
foreach ($this->attributes as $i => $attribute) {
if (is_string($attribute)) {
if (!preg_match('/^(\w+)(\s*:\s*(\w+))?$/', $attribute, $matches)) {
throw new InvalidConfigException('The attribute must be in the format of "Name" or "Name:Type"');
}
$attribute = array(
'name' => $matches[1],
'type' => isset($matches[3]) ? $matches[3] : 'text',
);
}
if (!is_array($attribute)) {
throw new InvalidConfigException('The attribute configuration must be an array.');
}
if (!isset($attribute['type'])) {
$attribute['type'] = 'text';
}
if (isset($attribute['name'])) {
$name = $attribute['name'];
if (!isset($attribute['label'])) {
$attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($name) : Inflector::camel2words($name, true);
}
if (!array_key_exists('value', $attribute)) {
$attribute['value'] = ArrayHelper::getValue($this->model, $name);
}
} elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) {
throw new InvalidConfigException('The attribute configuration requires the "name" element to determine the value and display label.');
}
$this->attributes[$i] = $attribute;
}
}
}

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

@ -33,6 +33,12 @@ class BarBehavior extends Behavior
class BehaviorTest extends TestCase class BehaviorTest extends TestCase
{ {
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
public function testAttachAndAccessing() public function testAttachAndAccessing()
{ {
$bar = new BarClass(); $bar = new BarClass();

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

@ -27,6 +27,7 @@ class ComponentTest extends TestCase
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication();
$this->component = new NewComponent(); $this->component = new NewComponent();
} }

13
tests/unit/framework/base/FormatterTest.php

@ -42,6 +42,7 @@ class FormatterTest extends TestCase
$this->assertSame($value, $this->formatter->asRaw($value)); $this->assertSame($value, $this->formatter->asRaw($value));
$value = '<>'; $value = '<>';
$this->assertSame($value, $this->formatter->asRaw($value)); $this->assertSame($value, $this->formatter->asRaw($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRaw(null));
} }
public function testAsText() public function testAsText()
@ -52,6 +53,7 @@ class FormatterTest extends TestCase
$this->assertSame("$value", $this->formatter->asText($value)); $this->assertSame("$value", $this->formatter->asText($value));
$value = '<>'; $value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asText($value)); $this->assertSame('&lt;&gt;', $this->formatter->asText($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asText(null));
} }
public function testAsNtext() public function testAsNtext()
@ -64,6 +66,7 @@ class FormatterTest extends TestCase
$this->assertSame('&lt;&gt;', $this->formatter->asNtext($value)); $this->assertSame('&lt;&gt;', $this->formatter->asNtext($value));
$value = "123\n456"; $value = "123\n456";
$this->assertSame("123<br />\n456", $this->formatter->asNtext($value)); $this->assertSame("123<br />\n456", $this->formatter->asNtext($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asNtext(null));
} }
public function testAsParagraphs() public function testAsParagraphs()
@ -80,6 +83,7 @@ class FormatterTest extends TestCase
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value)); $this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n\n456"; $value = "123\n\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value)); $this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asParagraphs(null));
} }
public function testAsHtml() public function testAsHtml()
@ -91,12 +95,14 @@ class FormatterTest extends TestCase
{ {
$value = 'test@sample.com'; $value = 'test@sample.com';
$this->assertSame("<a href=\"mailto:$value\">$value</a>", $this->formatter->asEmail($value)); $this->assertSame("<a href=\"mailto:$value\">$value</a>", $this->formatter->asEmail($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asEmail(null));
} }
public function testAsImage() public function testAsImage()
{ {
$value = 'http://sample.com/img.jpg'; $value = 'http://sample.com/img.jpg';
$this->assertSame("<img src=\"$value\" alt=\"\">", $this->formatter->asImage($value)); $this->assertSame("<img src=\"$value\" alt=\"\">", $this->formatter->asImage($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asImage(null));
} }
public function testAsBoolean() public function testAsBoolean()
@ -109,6 +115,7 @@ class FormatterTest extends TestCase
$this->assertSame('Yes', $this->formatter->asBoolean($value)); $this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = ""; $value = "";
$this->assertSame('No', $this->formatter->asBoolean($value)); $this->assertSame('No', $this->formatter->asBoolean($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asBoolean(null));
} }
public function testAsDate() public function testAsDate()
@ -116,6 +123,7 @@ class FormatterTest extends TestCase
$value = time(); $value = time();
$this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value)); $this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value));
$this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value, 'Y-m-d')); $this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value, 'Y-m-d'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
} }
public function testAsTime() public function testAsTime()
@ -123,6 +131,7 @@ class FormatterTest extends TestCase
$value = time(); $value = time();
$this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value)); $this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value));
$this->assertSame(date('h:i:s', $value), $this->formatter->asTime($value, 'h:i:s')); $this->assertSame(date('h:i:s', $value), $this->formatter->asTime($value, 'h:i:s'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTime(null));
} }
public function testAsDatetime() public function testAsDatetime()
@ -130,6 +139,7 @@ class FormatterTest extends TestCase
$value = time(); $value = time();
$this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value)); $this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value));
$this->assertSame(date('Y-m-d h:i:s', $value), $this->formatter->asDatetime($value, 'Y-m-d h:i:s')); $this->assertSame(date('Y-m-d h:i:s', $value), $this->formatter->asDatetime($value, 'Y-m-d h:i:s'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null));
} }
public function testAsInteger() public function testAsInteger()
@ -144,6 +154,7 @@ class FormatterTest extends TestCase
$this->assertSame("-123", $this->formatter->asInteger($value)); $this->assertSame("-123", $this->formatter->asInteger($value));
$value = "-123abc"; $value = "-123abc";
$this->assertSame("-123", $this->formatter->asInteger($value)); $this->assertSame("-123", $this->formatter->asInteger($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asInteger(null));
} }
public function testAsDouble() public function testAsDouble()
@ -161,6 +172,7 @@ class FormatterTest extends TestCase
$this->assertSame("123", $this->formatter->asDouble($value, 0)); $this->assertSame("123", $this->formatter->asDouble($value, 0));
$value = 123123.123; $value = 123123.123;
$this->assertSame("123123,12", $this->formatter->asDouble($value)); $this->assertSame("123123,12", $this->formatter->asDouble($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDouble(null));
} }
public function testAsNumber() public function testAsNumber()
@ -175,5 +187,6 @@ class FormatterTest extends TestCase
$this->formatter->thousandSeparator = ''; $this->formatter->thousandSeparator = '';
$this->assertSame("123123", $this->formatter->asNumber($value)); $this->assertSame("123123", $this->formatter->asNumber($value));
$this->assertSame("123123,12", $this->formatter->asNumber($value, 2)); $this->assertSame("123123,12", $this->formatter->asNumber($value, 2));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asNumber(null));
} }
} }

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

@ -12,6 +12,12 @@ use yiiunit\data\base\InvalidRulesModel;
*/ */
class ModelTest extends TestCase class ModelTest extends TestCase
{ {
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
public function testGetAttributeLabel() public function testGetAttributeLabel()
{ {
$speaker = new Speaker(); $speaker = new Speaker();

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

@ -17,6 +17,7 @@ class ObjectTest extends TestCase
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication();
$this->object = new NewObject; $this->object = new NewObject;
} }

1
tests/unit/framework/console/controllers/AssetControllerTest.php

@ -20,6 +20,7 @@ class AssetControllerTest extends TestCase
public function setUp() public function setUp()
{ {
$this->mockApplication();
$this->testFilePath = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . get_class($this); $this->testFilePath = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . get_class($this);
$this->createDir($this->testFilePath); $this->createDir($this->testFilePath);
$this->testAssetsBasePath = $this->testFilePath . DIRECTORY_SEPARATOR . 'assets'; $this->testAssetsBasePath = $this->testFilePath . DIRECTORY_SEPARATOR . 'assets';

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

@ -14,6 +14,7 @@ class ActiveRecordTest extends DatabaseTestCase
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication();
ActiveRecord::$db = $this->getConnection(); ActiveRecord::$db = $this->getConnection();
} }

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

@ -9,6 +9,12 @@ use yii\db\DataReader;
class CommandTest extends DatabaseTestCase class CommandTest extends DatabaseTestCase
{ {
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
function testConstruct() function testConstruct()
{ {
$db = $this->getConnection(false); $db = $this->getConnection(false);

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

@ -6,6 +6,12 @@ use yii\db\Connection;
class ConnectionTest extends DatabaseTestCase class ConnectionTest extends DatabaseTestCase
{ {
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
function testConstruct() function testConstruct()
{ {
$connection = $this->getConnection(false); $connection = $this->getConnection(false);

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

@ -12,6 +12,7 @@ abstract class DatabaseTestCase extends TestCase
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication();
$databases = $this->getParam('databases'); $databases = $this->getParam('databases');
$this->database = $databases[$this->driverName]; $this->database = $databases[$this->driverName];
$pdo_database = 'pdo_'.$this->driverName; $pdo_database = 'pdo_'.$this->driverName;

5
tests/unit/framework/db/QueryBuilderTest.php

@ -11,6 +11,11 @@ use yii\db\pgsql\QueryBuilder as PgsqlQueryBuilder;
class QueryBuilderTest extends DatabaseTestCase class QueryBuilderTest extends DatabaseTestCase
{ {
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
/** /**
* @throws \Exception * @throws \Exception

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

@ -9,6 +9,12 @@ use yii\db\DataReader;
class QueryTest extends DatabaseTestCase class QueryTest extends DatabaseTestCase
{ {
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
function testSelect() function testSelect()
{ {
// default // default

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

@ -2,16 +2,61 @@
namespace yiiunit\framework\helpers; namespace yiiunit\framework\helpers;
use yii\base\Object;
use yii\helpers\ArrayHelper; use yii\helpers\ArrayHelper;
use yii\test\TestCase; use yii\test\TestCase;
use yii\data\Sort; use yii\data\Sort;
class ArrayHelperTest extends TestCase class Post1
{ {
public function testMerge() public $id = 23;
{ public $title = 'tt';
}
class Post2 extends Object
{
public $id = 123;
public $content = 'test';
private $secret = 's';
public function getSecret()
{
return $this->secret;
}
}
class ArrayHelperTest extends TestCase
{
public function testToArray()
{
$object = new Post1;
$this->assertEquals(get_object_vars($object), ArrayHelper::toArray($object));
$object = new Post2;
$this->assertEquals(get_object_vars($object), ArrayHelper::toArray($object));
$object1 = new Post1;
$object2 = new Post2;
$this->assertEquals(array(
get_object_vars($object1),
get_object_vars($object2),
), ArrayHelper::toArray(array(
$object1,
$object2,
)));
$object = new Post2;
$this->assertEquals(array(
'id' => 123,
'secret' => 's',
'_content' => 'test',
'length' => 4,
), ArrayHelper::toArray($object, array(
$object->className() => array(
'id', 'secret',
'_content' => 'content',
'length' => function ($post) {
return strlen($post->content);
}
))));
} }
public function testRemove() public function testRemove()

62
tests/unit/framework/helpers/HtmlTest.php

@ -305,7 +305,7 @@ EOD;
$this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems())); $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems()));
$expected = <<<EOD $expected = <<<EOD
<label><input type="checkbox" name="test[]" value="value1&lt;&gt;"> text1<></label> <label><input type="checkbox" name="test[]" value="value1&lt;&gt;"> text1&lt;&gt;</label>
<label><input type="checkbox" name="test[]" value="value 2"> text 2</label> <label><input type="checkbox" name="test[]" value="value 2"> text 2</label>
EOD; EOD;
$this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems2())); $this->assertEqualsWithoutLE($expected, Html::checkboxList('test', array('value2'), $this->getDataItems2()));
@ -341,7 +341,7 @@ EOD;
$this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems())); $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems()));
$expected = <<<EOD $expected = <<<EOD
<label><input type="radio" name="test" value="value1&lt;&gt;"> text1<></label> <label><input type="radio" name="test" value="value1&lt;&gt;"> text1&lt;&gt;</label>
<label><input type="radio" name="test" value="value 2"> text 2</label> <label><input type="radio" name="test" value="value 2"> text 2</label>
EOD; EOD;
$this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems2())); $this->assertEqualsWithoutLE($expected, Html::radioList('test', array('value2'), $this->getDataItems2()));
@ -366,6 +366,64 @@ EOD;
))); )));
} }
public function testUl()
{
$data = array(
1, 'abc', '<>',
);
$expected = <<<EOD
<ul>
<li>1</li>
<li>abc</li>
<li>&lt;&gt;</li>
</ul>
EOD;
$this->assertEqualsWithoutLE($expected, Html::ul($data));
$expected = <<<EOD
<ul class="test">
<li class="item-0">1</li>
<li class="item-1">abc</li>
<li class="item-2"><></li>
</ul>
EOD;
$this->assertEqualsWithoutLE($expected, Html::ul($data, array(
'class' => 'test',
'item' => function($index, $item) {
return "<li class=\"item-$index\">$item</li>";
}
)));
}
public function testOl()
{
$data = array(
1, 'abc', '<>',
);
$expected = <<<EOD
<ol>
<li class="ti">1</li>
<li class="ti">abc</li>
<li class="ti">&lt;&gt;</li>
</ol>
EOD;
$this->assertEqualsWithoutLE($expected, Html::ol($data, array(
'itemOptions' => array('class' => 'ti'),
)));
$expected = <<<EOD
<ol class="test">
<li class="item-0">1</li>
<li class="item-1">abc</li>
<li class="item-2"><></li>
</ol>
EOD;
$this->assertEqualsWithoutLE($expected, Html::ol($data, array(
'class' => 'test',
'item' => function($index, $item) {
return "<li class=\"item-$index\">$item</li>";
}
)));
}
public function testRenderOptions() public function testRenderOptions()
{ {
$data = array( $data = array(

5
tests/unit/framework/i18n/FormatterTest.php

@ -47,6 +47,7 @@ class FormatterTest extends TestCase
$this->assertSame("123,456", $this->formatter->asDecimal($value)); $this->assertSame("123,456", $this->formatter->asDecimal($value));
$value = '-123456.123'; $value = '-123456.123';
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value)); $this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null));
} }
public function testAsPercent() public function testAsPercent()
@ -57,6 +58,7 @@ class FormatterTest extends TestCase
$this->assertSame("12%", $this->formatter->asPercent($value)); $this->assertSame("12%", $this->formatter->asPercent($value));
$value = '-0.009343'; $value = '-0.009343';
$this->assertSame("-1%", $this->formatter->asPercent($value)); $this->assertSame("-1%", $this->formatter->asPercent($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asPercent(null));
} }
public function testAsScientific() public function testAsScientific()
@ -67,6 +69,7 @@ class FormatterTest extends TestCase
$this->assertSame("1.23456E5", $this->formatter->asScientific($value)); $this->assertSame("1.23456E5", $this->formatter->asScientific($value));
$value = '-123456.123'; $value = '-123456.123';
$this->assertSame("-1.23456123E5", $this->formatter->asScientific($value)); $this->assertSame("-1.23456123E5", $this->formatter->asScientific($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null));
} }
public function testAsCurrency() public function testAsCurrency()
@ -77,6 +80,7 @@ class FormatterTest extends TestCase
$this->assertSame("$123.46", $this->formatter->asCurrency($value)); $this->assertSame("$123.46", $this->formatter->asCurrency($value));
$value = '-123456.123'; $value = '-123456.123';
$this->assertSame("($123,456.12)", $this->formatter->asCurrency($value)); $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
} }
public function testDate() public function testDate()
@ -84,5 +88,6 @@ class FormatterTest extends TestCase
$time = time(); $time = time();
$this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time));
$this->assertSame(date('F j, Y', $time), $this->formatter->asDate($time, 'long')); $this->assertSame(date('F j, Y', $time), $this->formatter->asDate($time, 'long'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
} }
} }

Loading…
Cancel
Save