Browse Source

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

tags/2.0.0-beta
Paul Klimov 11 years ago
parent
commit
93ca701a10
  1. 7
      apps/advanced/README.md
  2. 1
      apps/advanced/backend/assets/.gitkeep
  3. 2
      apps/advanced/backend/assets/AppAsset.php
  4. 6
      apps/advanced/backend/config/main.php
  5. 2
      apps/advanced/backend/controllers/SiteController.php
  6. 12
      apps/advanced/backend/views/layouts/main.php
  7. 2
      apps/advanced/common/models/LoginForm.php
  8. 3
      apps/advanced/composer.json
  9. 2
      apps/advanced/frontend/assets/.gitkeep
  10. 2
      apps/advanced/frontend/assets/AppAsset.php
  11. 2
      apps/advanced/frontend/controllers/SiteController.php
  12. 2
      apps/advanced/frontend/models/ContactForm.php
  13. 4
      apps/advanced/frontend/views/emails/passwordResetToken.php
  14. 14
      apps/advanced/frontend/views/layouts/main.php
  15. 2
      apps/advanced/frontend/views/site/login.php
  16. 2
      apps/basic/assets/AppAsset.php
  17. 1
      apps/basic/codeception.yml
  18. 2
      apps/basic/config/console.php
  19. 2
      apps/basic/config/web.php
  20. 2
      apps/basic/models/ContactForm.php
  21. 2
      apps/basic/models/LoginForm.php
  22. 20
      apps/basic/tests/README.md
  23. 1367
      apps/basic/tests/acceptance/WebGuy.php
  24. 1367
      apps/basic/tests/functional/TestGuy.php
  25. 15
      apps/basic/tests/unit/CodeGuy.php
  26. 15
      apps/basic/views/layouts/main.php
  27. 11
      apps/basic/web/index-test.php
  28. 24
      build/controllers/PhpDocController.php
  29. 4
      docs/guide/query-builder.md
  30. 4
      docs/guide/upgrade-from-v1.md
  31. 2
      docs/guide/view.md
  32. 7
      extensions/bootstrap/composer.json
  33. 1
      extensions/composer/composer.json
  34. 7
      extensions/debug/composer.json
  35. 14
      extensions/debug/panels/ConfigPanel.php
  36. 10
      extensions/gii/Generator.php
  37. 7
      extensions/gii/composer.json
  38. 12
      extensions/gii/generators/controller/Generator.php
  39. 36
      extensions/gii/generators/crud/Generator.php
  40. 1
      extensions/gii/generators/crud/templates/controller.php
  41. 16
      extensions/gii/generators/form/Generator.php
  42. 28
      extensions/gii/generators/model/Generator.php
  43. 12
      extensions/gii/generators/module/Generator.php
  44. 8
      extensions/gii/views/default/index.php
  45. 4
      extensions/gii/views/default/view/files.php
  46. 8
      extensions/gii/views/layouts/generator.php
  47. 6
      extensions/gii/views/layouts/main.php
  48. 7
      extensions/jui/composer.json
  49. 3
      extensions/smarty/composer.json
  50. 8
      extensions/swiftmailer/Mailer.php
  51. 16
      extensions/swiftmailer/Message.php
  52. 3
      extensions/swiftmailer/composer.json
  53. 3
      extensions/twig/composer.json
  54. 12
      framework/composer.json
  55. 19
      framework/yii/BaseYii.php
  56. 5
      framework/yii/base/ActionFilter.php
  57. 8
      framework/yii/base/Application.php
  58. 18
      framework/yii/base/Component.php
  59. 5
      framework/yii/base/Controller.php
  60. 3
      framework/yii/base/ErrorHandler.php
  61. 3
      framework/yii/base/Formatter.php
  62. 38
      framework/yii/base/Model.php
  63. 2
      framework/yii/base/Module.php
  64. 15
      framework/yii/base/Object.php
  65. 1
      framework/yii/base/Request.php
  66. 2
      framework/yii/base/Response.php
  67. 4
      framework/yii/base/View.php
  68. 2
      framework/yii/base/ViewEvent.php
  69. 14
      framework/yii/caching/ChainedDependency.php
  70. 18
      framework/yii/caching/DbDependency.php
  71. 15
      framework/yii/caching/ExpressionDependency.php
  72. 16
      framework/yii/caching/FileDependency.php
  73. 22
      framework/yii/caching/GroupDependency.php
  74. 2
      framework/yii/caching/MemCache.php
  75. 2
      framework/yii/captcha/CaptchaAsset.php
  76. 4
      framework/yii/console/Request.php
  77. 2
      framework/yii/console/Response.php
  78. 2
      framework/yii/console/controllers/CacheController.php
  79. 20
      framework/yii/data/ActiveDataProvider.php
  80. 205
      framework/yii/db/ActiveQuery.php
  81. 77
      framework/yii/db/ActiveQueryInterface.php
  82. 199
      framework/yii/db/ActiveQueryTrait.php
  83. 47
      framework/yii/db/ActiveRecord.php
  84. 236
      framework/yii/db/ActiveRelation.php
  85. 29
      framework/yii/db/ActiveRelationInterface.php
  86. 246
      framework/yii/db/ActiveRelationTrait.php
  87. 6
      framework/yii/db/Command.php
  88. 142
      framework/yii/db/Query.php
  89. 2
      framework/yii/db/QueryBuilder.php
  90. 206
      framework/yii/db/QueryInterface.php
  91. 208
      framework/yii/db/QueryTrait.php
  92. 12
      framework/yii/db/Schema.php
  93. 2
      framework/yii/grid/ActionColumn.php
  94. 4
      framework/yii/grid/DataColumn.php
  95. 15
      framework/yii/grid/GridView.php
  96. 1
      framework/yii/grid/GridViewAsset.php
  97. 26
      framework/yii/helpers/BaseHtml.php
  98. 2
      framework/yii/helpers/BaseInflector.php
  99. 8
      framework/yii/helpers/BaseSecurity.php
  100. 9
      framework/yii/i18n/Formatter.php
  101. Some files were not shown because too many files have changed in this diff Show More

7
apps/advanced/README.md

@ -76,6 +76,13 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-a
Note that in order to install some dependencies you must have `php_openssl` extension enabled. Note that in order to install some dependencies you must have `php_openssl` extension enabled.
After the application is installed, switch to the project folder and run the following command
to initialize the application:
~~~
./init (init on Windows)
~~~
### Install from an Archive File ### Install from an Archive File

1
apps/advanced/backend/assets/.gitkeep

@ -1 +0,0 @@
*

2
apps/advanced/backend/config/AppAsset.php → apps/advanced/backend/assets/AppAsset.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace backend\config; namespace backend\assets;
use yii\web\AssetBundle; use yii\web\AssetBundle;

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

@ -1,5 +1,5 @@
<?php <?php
$rootDir = __DIR__ . '/../..'; $rootDir = dirname(dirname(__DIR__));
$params = array_merge( $params = array_merge(
require($rootDir . '/common/config/params.php'), require($rootDir . '/common/config/params.php'),
@ -11,11 +11,11 @@ $params = array_merge(
return [ return [
'id' => 'app-backend', 'id' => 'app-backend',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'vendorPath' => $rootDir . '/vendor',
'preload' => ['log'], 'preload' => ['log'],
'controllerNamespace' => 'backend\controllers', 'controllerNamespace' => 'backend\controllers',
'modules' => [], 'modules' => [],
'extensions' => require(__DIR__ . '/../../vendor/yiisoft/extensions.php'), 'extensions' => require($rootDir . '/vendor/yiisoft/extensions.php'),
'components' => [ 'components' => [
'request' => [ 'request' => [
'enableCsrfValidation' => true, 'enableCsrfValidation' => true,

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

@ -50,7 +50,7 @@ class SiteController extends Controller
$model = new LoginForm(); $model = new LoginForm();
if ($model->load($_POST) && $model->login()) { if ($model->load($_POST) && $model->login()) {
return $this->goHome(); return $this->goBack();
} else { } else {
return $this->render('login', [ return $this->render('login', [
'model' => $model, 'model' => $model,

12
apps/advanced/backend/views/layouts/main.php

@ -1,13 +1,13 @@
<?php <?php
use backend\config\AppAsset; use backend\assets\AppAsset;
use yii\helpers\Html; use yii\helpers\Html;
use yii\bootstrap\Nav; use yii\bootstrap\Nav;
use yii\bootstrap\NavBar; use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs; use yii\widgets\Breadcrumbs;
/** /**
* @var $this \yii\web\View * @var \yii\web\View $this
* @var $content string * @var string $content
*/ */
AppAsset::register($this); AppAsset::register($this);
?> ?>
@ -38,16 +38,16 @@ AppAsset::register($this);
$menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']]; $menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']];
} }
echo Nav::widget([ echo Nav::widget([
'options' => ['class' => 'navbar-nav pull-right'], 'options' => ['class' => 'navbar-nav navbar-right'],
'items' => $menuItems, 'items' => $menuItems,
]); ]);
NavBar::end(); NavBar::end();
?> ?>
<div class="container"> <div class="container">
<?=Breadcrumbs::widget([ <?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]); ?> ]) ?>
<?= $content ?> <?= $content ?>
</div> </div>

2
apps/advanced/common/models/LoginForm.php

@ -21,7 +21,7 @@ class LoginForm extends Model
{ {
return [ return [
// username and password are both required // username and password are both required
['username, password', 'required'], [['username', 'password'], 'required'],
// password is validated by validatePassword() // password is validated by validatePassword()
['password', 'validatePassword'], ['password', 'validatePassword'],
// rememberMe must be a boolean value // rememberMe must be a boolean value

3
apps/advanced/composer.json

@ -23,8 +23,7 @@
}, },
"scripts": { "scripts": {
"post-create-project-cmd": [ "post-create-project-cmd": [
"yii\\composer\\Installer::setPermission", "yii\\composer\\Installer::setPermission"
"./init"
] ]
}, },
"extra": { "extra": {

2
apps/advanced/frontend/assets/.gitkeep

@ -1,2 +0,0 @@
*
!.gitignore

2
apps/advanced/frontend/config/AppAsset.php → apps/advanced/frontend/assets/AppAsset.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace frontend\config; namespace frontend\assets;
use yii\web\AssetBundle; use yii\web\AssetBundle;

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

@ -60,7 +60,7 @@ class SiteController extends Controller
$model = new LoginForm(); $model = new LoginForm();
if ($model->load($_POST) && $model->login()) { if ($model->load($_POST) && $model->login()) {
return $this->goHome(); return $this->goBack();
} else { } else {
return $this->render('login', [ return $this->render('login', [
'model' => $model, 'model' => $model,

2
apps/advanced/frontend/models/ContactForm.php

@ -23,7 +23,7 @@ class ContactForm extends Model
{ {
return [ return [
// name, email, subject and body are required // name, email, subject and body are required
['name, email, subject, body', 'required'], [['name', 'email', 'subject', 'body'], 'required'],
// email has to be a valid email address // email has to be a valid email address
['email', 'email'], ['email', 'email'],
// verifyCode needs to be entered correctly // verifyCode needs to be entered correctly

4
apps/advanced/frontend/views/emails/passwordResetToken.php

@ -9,8 +9,8 @@ use yii\helpers\Html;
$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', ['token' => $user->password_reset_token]); $resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', ['token' => $user->password_reset_token]);
?> ?>
Hello <?=Html::encode($user->username)?>, Hello <?= Html::encode($user->username) ?>,
Follow the link below to reset your password: Follow the link below to reset your password:
<?=Html::a(Html::encode($resetLink), $resetLink)?> <?= Html::a(Html::encode($resetLink), $resetLink) ?>

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

@ -1,14 +1,14 @@
<?php <?php
use frontend\config\AppAsset;
use yii\helpers\Html; use yii\helpers\Html;
use yii\bootstrap\Nav; use yii\bootstrap\Nav;
use yii\bootstrap\NavBar; use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs; use yii\widgets\Breadcrumbs;
use frontend\assets\AppAsset;
use frontend\widgets\Alert; use frontend\widgets\Alert;
/** /**
* @var $this \yii\web\View * @var \yii\web\View $this
* @var $content string * @var string $content
*/ */
AppAsset::register($this); AppAsset::register($this);
?> ?>
@ -42,17 +42,17 @@ AppAsset::register($this);
$menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']]; $menuItems[] = ['label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => ['/site/logout']];
} }
echo Nav::widget([ echo Nav::widget([
'options' => ['class' => 'navbar-nav pull-right'], 'options' => ['class' => 'navbar-nav navbar-right'],
'items' => $menuItems, 'items' => $menuItems,
]); ]);
NavBar::end(); NavBar::end();
?> ?>
<div class="container"> <div class="container">
<?=Breadcrumbs::widget([ <?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]); ?> ]) ?>
<?=Alert::widget()?> <?= Alert::widget() ?>
<?= $content ?> <?= $content ?>
</div> </div>

2
apps/advanced/frontend/views/site/login.php

@ -22,7 +22,7 @@ $this->params['breadcrumbs'][] = $this->title;
<?= $form->field($model, 'password')->passwordInput() ?> <?= $form->field($model, 'password')->passwordInput() ?>
<?= $form->field($model, 'rememberMe')->checkbox() ?> <?= $form->field($model, 'rememberMe')->checkbox() ?>
<div style="color:#999;margin:1em 0"> <div style="color:#999;margin:1em 0">
If you forgot your password you can <?=Html::a('reset it', ['site/request-password-reset'])?>. If you forgot your password you can <?= Html::a('reset it', ['site/request-password-reset']) ?>.
</div> </div>
<div class="form-group"> <div class="form-group">
<?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?> <?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?>

2
apps/basic/config/AppAsset.php → apps/basic/assets/AppAsset.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace app\config; namespace app\assets;
use yii\web\AssetBundle; use yii\web\AssetBundle;

1
apps/basic/codeception.yml

@ -6,7 +6,6 @@ paths:
settings: settings:
bootstrap: _bootstrap.php bootstrap: _bootstrap.php
suite_class: \PHPUnit_Framework_TestSuite suite_class: \PHPUnit_Framework_TestSuite
colors: true
memory_limit: 1024M memory_limit: 1024M
log: true log: true
modules: modules:

2
apps/basic/config/console.php

@ -1,7 +1,7 @@
<?php <?php
$params = require(__DIR__ . '/params.php'); $params = require(__DIR__ . '/params.php');
return [ return [
'id' => 'bootstrap-console', 'id' => 'basic-console',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'preload' => ['log'], 'preload' => ['log'],
'controllerPath' => dirname(__DIR__) . '/commands', 'controllerPath' => dirname(__DIR__) . '/commands',

2
apps/basic/config/web.php

@ -1,7 +1,7 @@
<?php <?php
$params = require(__DIR__ . '/params.php'); $params = require(__DIR__ . '/params.php');
$config = [ $config = [
'id' => 'bootstrap', 'id' => 'basic',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => [ 'components' => [

2
apps/basic/models/ContactForm.php

@ -23,7 +23,7 @@ class ContactForm extends Model
{ {
return [ return [
// name, email, subject and body are required // name, email, subject and body are required
['name, email, subject, body', 'required'], [['name', 'email', 'subject', 'body'], 'required'],
// email has to be a valid email address // email has to be a valid email address
['email', 'email'], ['email', 'email'],
// verifyCode needs to be entered correctly // verifyCode needs to be entered correctly

2
apps/basic/models/LoginForm.php

@ -21,7 +21,7 @@ class LoginForm extends Model
{ {
return [ return [
// username and password are both required // username and password are both required
['username, password', 'required'], [['username', 'password'], 'required'],
// password is validated by validatePassword() // password is validated by validatePassword()
['password', 'validatePassword'], ['password', 'validatePassword'],
// rememberMe must be a boolean value // rememberMe must be a boolean value

20
apps/basic/tests/README.md

@ -0,0 +1,20 @@
This folder contains various tests for the basic application.
These tests are developed with [Codeception PHP Testing Framework](http://codeception.com/).
To run the tests, follow these steps:
1. [Install Codeception](http://codeception.com/quickstart) if you do not have it yet.
2. Create test configuration files based on your environment:
- Copy `acceptance.suite.dist.yml` to `acceptance.suite.yml` and customize it;
- Copy `functional.suite.dist.yml` to `functional.suite.yml` and customize it;
- Copy `unit.suite.dist.yml` to `unit.suite.yml` and customize it.
3. Switch to the parent folder and run tests:
```
cd ..
php codecept.phar build // rebuild test scripts, only need to be run once
php codecept.phar run // run all available tests
```
Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for
more details about writing acceptance, functional and unit tests.

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

15
apps/basic/tests/unit/CodeGuy.php

@ -1,23 +1,26 @@
<?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\CodeHelper; use Codeception\Module\CodeHelper;
/** /**
* 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)
* @method void am($role) * @method void am($role)
* @method void lookForwardTo($role) * @method void lookForwardTo($achieveValue)
* @method void offsetGet($offset)
* @method void offsetSet($offset, $value)
* @method void offsetExists($offset)
* @method void offsetUnset($offset)
*/ */
class CodeGuy extends \Codeception\AbstractGuy class CodeGuy extends \Codeception\AbstractGuy

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

@ -3,12 +3,13 @@ use yii\helpers\Html;
use yii\bootstrap\Nav; use yii\bootstrap\Nav;
use yii\bootstrap\NavBar; use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs; use yii\widgets\Breadcrumbs;
use app\assets\AppAsset;
/** /**
* @var $this \yii\web\View * @var \yii\web\View $this
* @var $content string * @var string $content
*/ */
app\config\AppAsset::register($this); AppAsset::register($this);
?> ?>
<?php $this->beginPage(); ?> <?php $this->beginPage(); ?>
<!DOCTYPE html> <!DOCTYPE html>
@ -29,14 +30,14 @@ app\config\AppAsset::register($this);
], ],
]); ]);
echo Nav::widget([ echo Nav::widget([
'options' => ['class' => 'navbar-nav pull-right'], 'options' => ['class' => 'navbar-nav navbar-right'],
'items' => [ 'items' => [
['label' => 'Home', 'url' => ['/site/index']], ['label' => 'Home', 'url' => ['/site/index']],
['label' => 'About', 'url' => ['/site/about']], ['label' => 'About', 'url' => ['/site/about']],
['label' => 'Contact', 'url' => ['/site/contact']], ['label' => 'Contact', 'url' => ['/site/contact']],
Yii::$app->user->isGuest ? Yii::$app->user->isGuest ?
['label' => 'Login', 'url' => ['/site/login']] : ['label' => 'Login', 'url' => ['/site/login']] :
['label' => 'Logout (' . Yii::$app->user->identity->username .')' , ['label' => 'Logout (' . Yii::$app->user->identity->username . ')' ,
'url' => ['/site/logout'], 'url' => ['/site/logout'],
'linkOptions' => ['data-method' => 'post']], 'linkOptions' => ['data-method' => 'post']],
], ],
@ -45,9 +46,9 @@ app\config\AppAsset::register($this);
?> ?>
<div class="container"> <div class="container">
<?=Breadcrumbs::widget([ <?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]); ?> ]) ?>
<?= $content ?> <?= $content ?>
</div> </div>

11
apps/basic/web/index-test.php

@ -13,5 +13,12 @@ require_once(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php');
$config = require(__DIR__ . '/../config/web-test.php'); $config = require(__DIR__ . '/../config/web-test.php');
$application = new yii\web\Application($config); if (isset($this)) {
$application->run(); // run in functional tests
$config['class'] = 'yii\web\Application';
return $config;
} else {
// run in acceptance tests
$application = new yii\web\Application($config);
$application->run();
}

24
build/controllers/PhpDocController.php

@ -278,11 +278,35 @@ class PhpDocController extends Controller
. ' See [[get'.ucfirst($propName).'()]] and [[set'.ucfirst($propName).'()]] for details.'; . ' See [[get'.ucfirst($propName).'()]] and [[set'.ucfirst($propName).'()]] for details.';
} }
} elseif (isset($prop['get'])) { } elseif (isset($prop['get'])) {
// check if parent class has setter defined
$c = $className;
$parentSetter = false;
while($parent = get_parent_class($c)) {
if (method_exists($parent, 'set' . ucfirst($propName))) {
$parentSetter = true;
break;
}
$c = $parent;
}
if (!$parentSetter) {
$note = ' This property is read-only.'; $note = ' This property is read-only.';
// $docline .= '-read'; // $docline .= '-read';
}
} elseif (isset($prop['set'])) { } elseif (isset($prop['set'])) {
// check if parent class has getter defined
$c = $className;
$parentGetter = false;
while($parent = get_parent_class($c)) {
if (method_exists($parent, 'set' . ucfirst($propName))) {
$parentGetter = true;
break;
}
$c = $parent;
}
if (!$parentGetter) {
$note = ' This property is write-only.'; $note = ' This property is write-only.';
// $docline .= '-write'; // $docline .= '-write';
}
} else { } else {
continue; continue;
} }

4
docs/guide/query-builder.md

@ -167,8 +167,8 @@ For ordering results `orderBy` and `addOrderBy` could be used:
```php ```php
$query->orderBy([ $query->orderBy([
'id' => Query::SORT_ASC, 'id' => SORT_ASC,
'name' => Query::SORT_DESC, 'name' => SORT_DESC,
]); ]);
``` ```

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

@ -38,7 +38,7 @@ of `Object` should declare its constructor (if needed) in the following way so t
it can be properly configured: it can be properly configured:
```php ```php
class MyClass extends \yii\Object class MyClass extends \yii\base\Object
{ {
public function __construct($param1, $param2, $config = []) public function __construct($param1, $param2, $config = [])
{ {
@ -109,7 +109,7 @@ Yii::$app->trigger($eventName);
If you need to handle all instances of a class instead of the object you can attach a handler like the following: If you need to handle all instances of a class instead of the object you can attach a handler like the following:
```php ```php
Event::on([ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT], function ($event) { Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::trace(get_class($event->sender) . ' is inserted.'); Yii::trace(get_class($event->sender) . ' is inserted.');
}); });
``` ```

2
docs/guide/view.md

@ -240,7 +240,7 @@ details on how to define asset bundles in [asset manager](assets.md) section of
asset bundle, it's very straightforward: asset bundle, it's very straightforward:
```php ```php
frontend\config\AppAsset::register($this); frontend\assets\AppAsset::register($this);
``` ```
### Layout ### Layout

7
extensions/bootstrap/composer.json

@ -11,7 +11,12 @@
"irc": "irc://irc.freenode.net/yii", "irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2" "source": "https://github.com/yiisoft/yii2"
}, },
"minimum-stability": "dev", "authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"twbs/bootstrap": "3.0.*" "twbs/bootstrap": "3.0.*"

1
extensions/composer/composer.json

@ -17,7 +17,6 @@
"email": "qiang.xue@gmail.com" "email": "qiang.xue@gmail.com"
} }
], ],
"minimum-stability": "dev",
"autoload": { "autoload": {
"psr-0": { "yii\\composer\\": "" } "psr-0": { "yii\\composer\\": "" }
}, },

7
extensions/debug/composer.json

@ -11,7 +11,12 @@
"irc": "irc://irc.freenode.net/yii", "irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2" "source": "https://github.com/yiisoft/yii2"
}, },
"minimum-stability": "dev", "authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*" "yiisoft/yii2-bootstrap": "*"

14
extensions/debug/panels/ConfigPanel.php

@ -63,6 +63,7 @@ EOD;
]; ];
return "<h1>Configuration</h1>\n" return "<h1>Configuration</h1>\n"
. $this->renderData('Application Configuration', $app) . "\n" . $this->renderData('Application Configuration', $app) . "\n"
. $this->renderExtensions()
. $this->renderData('PHP Configuration', $php) . "\n" . $this->renderData('PHP Configuration', $php) . "\n"
. $this->getPhpInfo(); . $this->getPhpInfo();
} }
@ -93,6 +94,18 @@ $rows
EOD; EOD;
} }
protected function renderExtensions()
{
if (empty($this->data['extensions'])) {
return '';
}
$data = [];
foreach ($this->data['extensions'] as $extension) {
$data[$extension['name']] = $extension['version'];
}
return $this->renderData('Installed Extensions', $data) . "\n";
}
public function save() public function save()
{ {
return [ return [
@ -110,6 +123,7 @@ EOD;
'apc' => extension_loaded('apc'), 'apc' => extension_loaded('apc'),
'memcache' => extension_loaded('memcache'), 'memcache' => extension_loaded('memcache'),
], ],
'extensions' => Yii::$app->extensions,
]; ];
} }
} }

10
extensions/gii/Generator.php

@ -178,8 +178,8 @@ abstract class Generator extends Model
public function rules() public function rules()
{ {
return [ return [
['template', 'required', 'message' => 'A code template must be selected.'], [['template'], 'required', 'message' => 'A code template must be selected.'],
['template', 'validateTemplate'], [['template'], 'validateTemplate'],
]; ];
} }
@ -193,7 +193,7 @@ abstract class Generator extends Model
$attributes[] = 'template'; $attributes[] = 'template';
$path = $this->getStickyDataFile(); $path = $this->getStickyDataFile();
if (is_file($path)) { if (is_file($path)) {
$result = @include($path); $result = json_decode(file_get_contents($path), true);
if (is_array($result)) { if (is_array($result)) {
foreach ($stickyAttributes as $name) { foreach ($stickyAttributes as $name) {
if (isset($result[$name])) { if (isset($result[$name])) {
@ -218,7 +218,7 @@ abstract class Generator extends Model
} }
$path = $this->getStickyDataFile(); $path = $this->getStickyDataFile();
@mkdir(dirname($path), 0755, true); @mkdir(dirname($path), 0755, true);
file_put_contents($path, "<?php\nreturn " . var_export($values, true) . ";\n"); file_put_contents($path, json_encode($values));
} }
/** /**
@ -227,7 +227,7 @@ abstract class Generator extends Model
*/ */
public function getStickyDataFile() public function getStickyDataFile()
{ {
return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.php'; return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.json';
} }
/** /**

7
extensions/gii/composer.json

@ -11,7 +11,12 @@
"irc": "irc://irc.freenode.net/yii", "irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2" "source": "https://github.com/yiisoft/yii2"
}, },
"minimum-stability": "dev", "authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*" "yiisoft/yii2-bootstrap": "*"

12
extensions/gii/generators/controller/Generator.php

@ -69,12 +69,12 @@ class Generator extends \yii\gii\Generator
public function rules() public function rules()
{ {
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
['controller, actions, baseClass, ns', 'filter', 'filter' => 'trim'], [['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'],
['controller, baseClass', 'required'], [['controller', 'baseClass'], 'required'],
['controller', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'], [['controller'], 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'],
['actions', 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'], [['actions'], 'match', 'pattern' => '/^[a-z\\-,\\s]*$/', 'message' => 'Only a-z, dashes (-), spaces and commas are allowed.'],
['baseClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['baseClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
['ns', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
]); ]);
} }

36
extensions/gii/generators/crud/Generator.php

@ -42,17 +42,17 @@ class Generator extends \yii\gii\Generator
public function rules() public function rules()
{ {
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
['moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'], [['moduleID', 'controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'],
['modelClass, searchModelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'], [['modelClass', 'searchModelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'],
['searchModelClass', 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'],
['modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
['modelClass', 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], [['modelClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]],
['baseControllerClass', 'validateClass', 'params' => ['extends' => Controller::className()]], [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]],
['controllerClass', 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'],
['controllerClass, searchModelClass', 'validateNewClass'], [['controllerClass', 'searchModelClass'], 'validateNewClass'],
['indexWidgetType', 'in', 'range' => ['grid', 'list']], [['indexWidgetType'], 'in', 'range' => ['grid', 'list']],
['modelClass', 'validateModelClass'], [['modelClass'], 'validateModelClass'],
['moduleID', 'validateModuleID'], [['moduleID'], 'validateModuleID'],
]); ]);
} }
@ -278,7 +278,7 @@ class Generator extends \yii\gii\Generator
$rules = []; $rules = [];
foreach ($types as $type => $columns) { foreach ($types as $type => $columns) {
$rules[] = "['" . implode(', ', $columns) . "', '$type']"; $rules[] = "[['" . implode("', '", $columns) . "'], '$type']";
} }
return $rules; return $rules;
@ -341,7 +341,9 @@ class Generator extends \yii\gii\Generator
public function generateUrlParams() public function generateUrlParams()
{ {
$pks = $this->getTableSchema()->primaryKey; /** @var ActiveRecord $class */
$class = $this->modelClass;
$pks = $class::primaryKey();
if (count($pks) === 1) { if (count($pks) === 1) {
return "'id' => \$model->{$pks[0]}"; return "'id' => \$model->{$pks[0]}";
} else { } else {
@ -355,7 +357,9 @@ class Generator extends \yii\gii\Generator
public function generateActionParams() public function generateActionParams()
{ {
$pks = $this->getTableSchema()->primaryKey; /** @var ActiveRecord $class */
$class = $this->modelClass;
$pks = $class::primaryKey();
if (count($pks) === 1) { if (count($pks) === 1) {
return '$id'; return '$id';
} else { } else {
@ -366,7 +370,9 @@ class Generator extends \yii\gii\Generator
public function generateActionParamComments() public function generateActionParamComments()
{ {
$table = $this->getTableSchema(); $table = $this->getTableSchema();
$pks = $table->primaryKey; /** @var ActiveRecord $class */
$class = $this->modelClass;
$pks = $class::primaryKey();
if (count($pks) === 1) { if (count($pks) === 1) {
return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id'];
} else { } else {

1
extensions/gii/generators/crud/templates/controller.php

@ -28,7 +28,6 @@ namespace <?= StringHelper::dirname(ltrim($generator->controllerClass, '\\')) ?>
use <?= ltrim($generator->modelClass, '\\') ?>; use <?= ltrim($generator->modelClass, '\\') ?>;
use <?= ltrim($generator->searchModelClass, '\\') ?><?php if (isset($searchModelAlias)):?> as <?= $searchModelAlias ?><?php endif ?>; use <?= ltrim($generator->searchModelClass, '\\') ?><?php if (isset($searchModelAlias)):?> as <?= $searchModelAlias ?><?php endif ?>;
use yii\data\ActiveDataProvider;
use <?= ltrim($generator->baseControllerClass, '\\') ?>; use <?= ltrim($generator->baseControllerClass, '\\') ?>;
use yii\web\HttpException; use yii\web\HttpException;
use yii\web\VerbFilter; use yii\web\VerbFilter;

16
extensions/gii/generators/form/Generator.php

@ -60,14 +60,14 @@ class Generator extends \yii\gii\Generator
public function rules() public function rules()
{ {
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
['modelClass, viewName, scenarioName, viewPath', 'filter', 'filter' => 'trim'], [['modelClass', 'viewName', 'scenarioName', 'viewPath'], 'filter', 'filter' => 'trim'],
['modelClass, viewName, viewPath', 'required'], [['modelClass', 'viewName', 'viewPath'], 'required'],
['modelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['modelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
['modelClass', 'validateClass', 'params' => ['extends' => Model::className()]], [['modelClass'], 'validateClass', 'params' => ['extends' => Model::className()]],
['viewName', 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'], [['viewName'], 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'],
['viewPath', 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'], [['viewPath'], 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'],
['viewPath', 'validateViewPath'], [['viewPath'], 'validateViewPath'],
['scenarioName', 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], [['scenarioName'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'],
]); ]);
} }

28
extensions/gii/generators/model/Generator.php

@ -53,17 +53,17 @@ class Generator extends \yii\gii\Generator
public function rules() public function rules()
{ {
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
['db, ns, tableName, modelClass, baseClass', 'filter', 'filter' => 'trim'], [['db', 'ns', 'tableName', 'modelClass', 'baseClass'], 'filter', 'filter' => 'trim'],
['db, ns, tableName, baseClass', 'required'], [['db', 'ns', 'tableName', 'baseClass'], 'required'],
['db, modelClass', 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'], [['db', 'modelClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'],
['ns, baseClass', 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], [['ns', 'baseClass'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'],
['tableName', 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], [['tableName'], 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'],
['db', 'validateDb'], [['db'], 'validateDb'],
['ns', 'validateNamespace'], [['ns'], 'validateNamespace'],
['tableName', 'validateTableName'], [['tableName'], 'validateTableName'],
['modelClass', 'validateModelClass', 'skipOnEmpty' => false], [['modelClass'], 'validateModelClass', 'skipOnEmpty' => false],
['baseClass', 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], [['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]],
['generateRelations, generateLabelsFromComments', 'boolean'], [['generateRelations', 'generateLabelsFromComments'], 'boolean'],
]); ]);
} }
@ -104,7 +104,7 @@ class Generator extends \yii\gii\Generator
'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', 'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.',
'generateRelations' => 'This indicates whether the generator should generate relations based on 'generateRelations' => 'This indicates whether the generator should generate relations based on
foreign key constraints it detects in the database. Note that if your database contains too many tables, foreign key constraints it detects in the database. Note that if your database contains too many tables,
you may want to uncheck this option to accelerate the code generation proc ess.', you may want to uncheck this option to accelerate the code generation process.',
'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels 'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels
by using the comments of the corresponding DB columns.', by using the comments of the corresponding DB columns.',
]; ];
@ -237,10 +237,10 @@ class Generator extends \yii\gii\Generator
$rules = []; $rules = [];
foreach ($types as $type => $columns) { foreach ($types as $type => $columns) {
$rules[] = "['" . implode(', ', $columns) . "', '$type']"; $rules[] = "[['" . implode("', '", $columns) . "'], '$type']";
} }
foreach ($lengths as $length => $columns) { foreach ($lengths as $length => $columns) {
$rules[] = "['" . implode(', ', $columns) . "', 'string', 'max' => $length]"; $rules[] = "[['" . implode("', '", $columns) . "'], 'string', 'max' => $length]";
} }
return $rules; return $rules;

12
extensions/gii/generators/module/Generator.php

@ -45,11 +45,11 @@ class Generator extends \yii\gii\Generator
public function rules() public function rules()
{ {
return array_merge(parent::rules(), [ return array_merge(parent::rules(), [
['moduleID, moduleClass', 'filter', 'filter' => 'trim'], [['moduleID', 'moduleClass'], 'filter', 'filter' => 'trim'],
['moduleID, moduleClass', 'required'], [['moduleID', 'moduleClass'], 'required'],
['moduleID', 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], [['moduleID'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'],
['moduleClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], [['moduleClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
['moduleClass', 'validateModuleClass'], [['moduleClass'], 'validateModuleClass'],
]); ]);
} }
@ -139,7 +139,7 @@ EOD;
*/ */
public function validateModuleClass() public function validateModuleClass()
{ {
if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass)) === false) { if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass), false) === false) {
$this->addError('moduleClass', 'Module class must be properly namespaced.'); $this->addError('moduleClass', 'Module class must be properly namespaced.');
} }
if (substr($this->moduleClass, -1, 1) == '\\') { if (substr($this->moduleClass, -1, 1) == '\\') {

8
extensions/gii/views/default/index.php

@ -2,10 +2,10 @@
use yii\helpers\Html; use yii\helpers\Html;
/** /**
* @var $this \yii\web\View * @var \yii\web\View $this
* @var $content string * @var \yii\gii\Generator[] $generators
* @var yii\gii\Generator[] $generators * @var \yii\gii\Generator $activeGenerator
* @var yii\gii\Generator $activeGenerator * @var string $content
*/ */
$generators = Yii::$app->controller->module->generators; $generators = Yii::$app->controller->module->generators;
$activeGenerator = Yii::$app->controller->generator; $activeGenerator = Yii::$app->controller->generator;

4
extensions/gii/views/default/view/files.php

@ -4,8 +4,8 @@ use yii\helpers\Html;
use yii\gii\CodeFile; use yii\gii\CodeFile;
/** /**
* @var $this \yii\web\View * @var \yii\web\View $this
* @var $generator \yii\gii\Generator * @var \yii\gii\Generator $generator
* @var CodeFile[] $files * @var CodeFile[] $files
* @var array $answers * @var array $answers
*/ */

8
extensions/gii/views/layouts/generator.php

@ -2,10 +2,10 @@
use yii\helpers\Html; use yii\helpers\Html;
/** /**
* @var $this \yii\web\View * @var \yii\web\View $this
* @var $content string * @var \yii\gii\Generator[] $generators
* @var yii\gii\Generator[] $generators * @var \yii\gii\Generator $activeGenerator
* @var yii\gii\Generator $activeGenerator * @var string $content
*/ */
$generators = Yii::$app->controller->module->generators; $generators = Yii::$app->controller->module->generators;
$activeGenerator = Yii::$app->controller->generator; $activeGenerator = Yii::$app->controller->generator;

6
extensions/gii/views/layouts/main.php

@ -4,8 +4,8 @@ use yii\bootstrap\Nav;
use yii\helpers\Html; use yii\helpers\Html;
/** /**
* @var $this \yii\web\View * @var \yii\web\View $this
* @var $content string * @var string $content
*/ */
$asset = yii\gii\GiiAsset::register($this); $asset = yii\gii\GiiAsset::register($this);
?> ?>
@ -26,7 +26,7 @@ NavBar::begin([
'options' => ['class' => 'navbar-inverse navbar-fixed-top'], 'options' => ['class' => 'navbar-inverse navbar-fixed-top'],
]); ]);
echo Nav::widget([ echo Nav::widget([
'options' => ['class' => 'nav navbar-nav pull-right'], 'options' => ['class' => 'nav navbar-nav navbar-right'],
'items' => [ 'items' => [
['label' => 'Home', 'url' => ['default/index']], ['label' => 'Home', 'url' => ['default/index']],
['label' => 'Help', 'url' => 'http://www.yiiframework.com/doc/guide/topics.gii'], ['label' => 'Help', 'url' => 'http://www.yiiframework.com/doc/guide/topics.gii'],

7
extensions/jui/composer.json

@ -11,7 +11,12 @@
"irc": "irc://irc.freenode.net/yii", "irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2" "source": "https://github.com/yiisoft/yii2"
}, },
"minimum-stability": "dev", "authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"require": { "require": {
"yiisoft/yii2": "*" "yiisoft/yii2": "*"
}, },

3
extensions/smarty/composer.json

@ -17,10 +17,9 @@
"email": "sam@rmcreative.ru" "email": "sam@rmcreative.ru"
} }
], ],
"minimum-stability": "dev",
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"smarty/smarty": ">=v3.1.13" "smarty/smarty": "*"
}, },
"autoload": { "autoload": {
"psr-0": { "yii\\smarty\\": "" } "psr-0": { "yii\\smarty\\": "" }

8
extensions/swiftmailer/Mailer.php

@ -104,13 +104,13 @@ class Mailer extends BaseMailer
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function send($message) protected function sendMessage($message)
{ {
$address = $message->getTo(); $address = $message->getTo();
if (is_array($address)) { if (is_array($address)) {
$address = implode(', ', $address); $address = implode(', ', array_keys($address));
} }
Yii::trace('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__); Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__);
return $this->getSwiftMailer()->send($message->getSwiftMessage()) > 0; return $this->getSwiftMailer()->send($message->getSwiftMessage()) > 0;
} }
@ -145,7 +145,7 @@ class Mailer extends BaseMailer
$transport->$name = $value; $transport->$name = $value;
} else { } else {
$setter = 'set' . $name; $setter = 'set' . $name;
if (method_exists($transport, $setter)) { if (method_exists($transport, $setter) || method_exists($transport, '__call')) {
$transport->$setter($value); $transport->$setter($value);
} else { } else {
throw new InvalidConfigException('Setting unknown property: ' . get_class($transport) . '::' . $name); throw new InvalidConfigException('Setting unknown property: ' . get_class($transport) . '::' . $name);

16
extensions/swiftmailer/Message.php

@ -44,7 +44,7 @@ class Message extends BaseMessage
*/ */
public function getCharset() public function getCharset()
{ {
$this->getSwiftMessage()->getCharset(); return $this->getSwiftMessage()->getCharset();
} }
/** /**
@ -61,7 +61,7 @@ class Message extends BaseMessage
*/ */
public function getFrom() public function getFrom()
{ {
$this->getSwiftMessage()->getFrom(); return $this->getSwiftMessage()->getFrom();
} }
/** /**
@ -78,7 +78,7 @@ class Message extends BaseMessage
*/ */
public function getReplyTo() public function getReplyTo()
{ {
$this->getSwiftMessage()->getReplyTo(); return $this->getSwiftMessage()->getReplyTo();
} }
/** /**
@ -95,7 +95,7 @@ class Message extends BaseMessage
*/ */
public function getTo() public function getTo()
{ {
$this->getSwiftMessage()->getTo(); return $this->getSwiftMessage()->getTo();
} }
/** /**
@ -112,7 +112,7 @@ class Message extends BaseMessage
*/ */
public function getCc() public function getCc()
{ {
$this->getSwiftMessage()->getCc(); return $this->getSwiftMessage()->getCc();
} }
/** /**
@ -129,7 +129,7 @@ class Message extends BaseMessage
*/ */
public function getBcc() public function getBcc()
{ {
$this->getSwiftMessage()->getBcc(); return $this->getSwiftMessage()->getBcc();
} }
/** /**
@ -146,7 +146,7 @@ class Message extends BaseMessage
*/ */
public function getSubject() public function getSubject()
{ {
$this->getSwiftMessage()->getSubject(); return $this->getSwiftMessage()->getSubject();
} }
/** /**
@ -192,7 +192,7 @@ class Message extends BaseMessage
$partFound = false; $partFound = false;
foreach ($parts as $key => $part) { foreach ($parts as $key => $part) {
if (!($part instanceof \Swift_Mime_Attachment)) { if (!($part instanceof \Swift_Mime_Attachment)) {
/* @var $part \Swift_Mime_MimePart */ /* @var \Swift_Mime_MimePart $part */
if ($part->getContentType() == $contentType) { if ($part->getContentType() == $contentType) {
unset($parts[$key]); unset($parts[$key]);
$partFound = true; $partFound = true;

3
extensions/swiftmailer/composer.json

@ -17,10 +17,9 @@
"email": "klimov.paul@gmail.com" "email": "klimov.paul@gmail.com"
} }
], ],
"minimum-stability": "dev",
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"swiftmailer/swiftmailer": "@stable" "swiftmailer/swiftmailer": "*"
}, },
"autoload": { "autoload": {
"psr-0": { "yii\\swiftmailer\\": "" } "psr-0": { "yii\\swiftmailer\\": "" }

3
extensions/twig/composer.json

@ -17,10 +17,9 @@
"email": "sam@rmcreative.ru" "email": "sam@rmcreative.ru"
} }
], ],
"minimum-stability": "dev",
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"twig/twig": "1.13.*" "twig/twig": "*"
}, },
"autoload": { "autoload": {
"psr-0": { "yii\\twig\\": "" } "psr-0": { "yii\\twig\\": "" }

12
framework/composer.json

@ -65,17 +65,15 @@
}, },
"require": { "require": {
"php": ">=5.4.0", "php": ">=5.4.0",
"yiisoft/yii2-composer": "*",
"yiisoft/jquery": "1.10.*",
"ext-mbstring": "*", "ext-mbstring": "*",
"lib-pcre": "*", "lib-pcre": "*",
"phpspec/php-diff": "dev-master", "yiisoft/yii2-composer": "*",
"ezyang/htmlpurifier": "4.5.*" "yiisoft/jquery": "1.10.*",
"phpspec/php-diff": ">=1.0.2",
"ezyang/htmlpurifier": "4.5.*",
"michelf/php-markdown": "1.3.*"
}, },
"autoload": { "autoload": {
"psr-0": { "yii\\": "/" } "psr-0": { "yii\\": "/" }
},
"suggest": {
"michelf/php-markdown": "Required by Markdown."
} }
} }

19
framework/yii/BaseYii.php

@ -63,8 +63,7 @@ class BaseYii
* The array keys are the class names (without leading backslashes), and the array values * The array keys are the class names (without leading backslashes), and the array values
* are the corresponding class file paths (or path aliases). This property mainly affects * are the corresponding class file paths (or path aliases). This property mainly affects
* how [[autoload()]] works. * how [[autoload()]] works.
* @see import * @see autoload()
* @see autoload
*/ */
public static $classMap = []; public static $classMap = [];
/** /**
@ -73,8 +72,8 @@ class BaseYii
public static $app; public static $app;
/** /**
* @var array registered path aliases * @var array registered path aliases
* @see getAlias * @see getAlias()
* @see setAlias * @see setAlias()
*/ */
public static $aliases = ['@yii' => __DIR__]; public static $aliases = ['@yii' => __DIR__];
/** /**
@ -95,7 +94,7 @@ class BaseYii
* ] * ]
* ~~~ * ~~~
* *
* @see createObject * @see createObject()
*/ */
public static $objectConfig = []; public static $objectConfig = [];
@ -136,7 +135,7 @@ class BaseYii
* If this is false and an invalid alias is given, false will be returned by this method. * If this is false and an invalid alias is given, false will be returned by this method.
* @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered.
* @throws InvalidParamException if the alias is invalid while $throwException is true. * @throws InvalidParamException if the alias is invalid while $throwException is true.
* @see setAlias * @see setAlias()
*/ */
public static function getAlias($alias, $throwException = true) public static function getAlias($alias, $throwException = true)
{ {
@ -219,7 +218,7 @@ class BaseYii
* actual path first by calling [[getAlias()]]. * actual path first by calling [[getAlias()]].
* *
* @throws InvalidParamException if $path is an invalid alias. * @throws InvalidParamException if $path is an invalid alias.
* @see getAlias * @see getAlias()
*/ */
public static function setAlias($alias, $path) public static function setAlias($alias, $path)
{ {
@ -368,7 +367,7 @@ class BaseYii
} }
if (($n = func_num_args()) > 1) { if (($n = func_num_args()) > 1) {
/** @var $reflection \ReflectionClass */ /** @var \ReflectionClass $reflection */
if (isset($reflections[$class])) { if (isset($reflections[$class])) {
$reflection = $reflections[$class]; $reflection = $reflections[$class];
} else { } else {
@ -450,7 +449,7 @@ class BaseYii
* ~~~ * ~~~
* @param string $token token for the code block * @param string $token token for the code block
* @param string $category the category of this log message * @param string $category the category of this log message
* @see endProfile * @see endProfile()
*/ */
public static function beginProfile($token, $category = 'application') public static function beginProfile($token, $category = 'application')
{ {
@ -462,7 +461,7 @@ class BaseYii
* This has to be matched with a previous call to [[beginProfile]] with the same category name. * This has to be matched with a previous call to [[beginProfile]] with the same category name.
* @param string $token token for the code block * @param string $token token for the code block
* @param string $category the category of this log message * @param string $category the category of this log message
* @see beginProfile * @see beginProfile()
*/ */
public static function endProfile($token, $category = 'application') public static function endProfile($token, $category = 'application')
{ {

5
framework/yii/base/ActionFilter.php

@ -8,6 +8,11 @@
namespace yii\base; namespace yii\base;
/** /**
* ActionFilter provides a base implementation for action filters that can be added to a controller
* to handle the `beforeAction` event.
*
* Check implementation of [[AccessControl]], [[PageCache]] and [[HttpCache]] as examples on how to use it.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

8
framework/yii/base/Application.php

@ -15,6 +15,7 @@ use yii\web\HttpException;
* Application is the base class for all application classes. * Application is the base class for all application classes.
* *
* @property \yii\rbac\Manager $authManager The auth manager for this application. This property is read-only. * @property \yii\rbac\Manager $authManager The auth manager for this application. This property is read-only.
* @property string $basePath The root directory of the application.
* @property \yii\caching\Cache $cache The cache application component. Null if the component is not enabled. * @property \yii\caching\Cache $cache The cache application component. Null if the component is not enabled.
* This property is read-only. * This property is read-only.
* @property \yii\db\Connection $db The database connection. This property is read-only. * @property \yii\db\Connection $db The database connection. This property is read-only.
@ -258,9 +259,10 @@ abstract class Application extends Module
} }
/** /**
* Sets the root directory of the applicaition and the @app alias. * Sets the root directory of the application and the @app alias.
* This method can only be invoked at the beginning of the constructor. * This method can only be invoked at the beginning of the constructor.
* @param string $path the root directory of the application. * @param string $path the root directory of the application.
* @property string the root directory of the application.
* @throws InvalidParamException if the directory does not exist. * @throws InvalidParamException if the directory does not exist.
*/ */
public function setBasePath($path) public function setBasePath($path)
@ -425,7 +427,7 @@ abstract class Application extends Module
/** /**
* Returns the view object. * Returns the view object.
* @return View the view object that is used to render various view files. * @return View|\yii\web\View the view object that is used to render various view files.
*/ */
public function getView() public function getView()
{ {
@ -614,10 +616,8 @@ abstract class Application extends Module
{ {
$category = get_class($exception); $category = get_class($exception);
if ($exception instanceof HttpException) { if ($exception instanceof HttpException) {
/** @var $exception HttpException */
$category .= '\\' . $exception->statusCode; $category .= '\\' . $exception->statusCode;
} elseif ($exception instanceof \ErrorException) { } elseif ($exception instanceof \ErrorException) {
/** @var $exception \ErrorException */
$category .= '\\' . $exception->getSeverity(); $category .= '\\' . $exception->getSeverity();
} }
Yii::error((string)$exception, $category); Yii::error((string)$exception, $category);

18
framework/yii/base/Component.php

@ -10,6 +10,8 @@ namespace yii\base;
use Yii; use Yii;
/** /**
* Component is the base class that implements the *property*, *event* and *behavior* features.
*
* @include @yii/base/Component.md * @include @yii/base/Component.md
* *
* @property Behavior[] $behaviors List of behaviors attached to this component. This property is read-only. * @property Behavior[] $behaviors List of behaviors attached to this component. This property is read-only.
@ -41,7 +43,7 @@ class Component extends Object
* @return mixed the property value or the value of a behavior's property * @return mixed the property value or the value of a behavior's property
* @throws UnknownPropertyException if the property is not defined * @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is write-only. * @throws InvalidCallException if the property is write-only.
* @see __set * @see __set()
*/ */
public function __get($name) public function __get($name)
{ {
@ -80,7 +82,7 @@ class Component extends Object
* @param mixed $value the property value * @param mixed $value the property value
* @throws UnknownPropertyException if the property is not defined * @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is read-only. * @throws InvalidCallException if the property is read-only.
* @see __get * @see __get()
*/ */
public function __set($name, $value) public function __set($name, $value)
{ {
@ -225,8 +227,8 @@ class Component extends Object
* @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkVars whether to treat member variables as properties
* @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return boolean whether the property is defined * @return boolean whether the property is defined
* @see canGetProperty * @see canGetProperty()
* @see canSetProperty * @see canSetProperty()
*/ */
public function hasProperty($name, $checkVars = true, $checkBehaviors = true) public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
{ {
@ -246,7 +248,7 @@ class Component extends Object
* @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkVars whether to treat member variables as properties
* @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return boolean whether the property can be read * @return boolean whether the property can be read
* @see canSetProperty * @see canSetProperty()
*/ */
public function canGetProperty($name, $checkVars = true, $checkBehaviors = true) public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
{ {
@ -276,7 +278,7 @@ class Component extends Object
* @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkVars whether to treat member variables as properties
* @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
* @return boolean whether the property can be written * @return boolean whether the property can be written
* @see canGetProperty * @see canGetProperty()
*/ */
public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
{ {
@ -492,7 +494,7 @@ class Component extends Object
* - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object. * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
* *
* @return Behavior the behavior object * @return Behavior the behavior object
* @see detachBehavior * @see detachBehavior()
*/ */
public function attachBehavior($name, $behavior) public function attachBehavior($name, $behavior)
{ {
@ -505,7 +507,7 @@ class Component extends Object
* Each behavior is indexed by its name and should be a [[Behavior]] object, * Each behavior is indexed by its name and should be a [[Behavior]] object,
* a string specifying the behavior class, or an configuration array for creating the behavior. * a string specifying the behavior class, or an configuration array for creating the behavior.
* @param array $behaviors list of behaviors to be attached to the component * @param array $behaviors list of behaviors to be attached to the component
* @see attachBehavior * @see attachBehavior()
*/ */
public function attachBehaviors($behaviors) public function attachBehaviors($behaviors)
{ {

5
framework/yii/base/Controller.php

@ -111,7 +111,7 @@ class Controller extends Component implements ViewContextInterface
* @param array $params the parameters (name-value pairs) to be passed to the action. * @param array $params the parameters (name-value pairs) to be passed to the action.
* @return mixed the result of the action * @return mixed the result of the action
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
* @see createAction * @see createAction()
*/ */
public function runAction($id, $params = []) public function runAction($id, $params = [])
{ {
@ -149,8 +149,7 @@ class Controller extends Component implements ViewContextInterface
* @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'. * @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
* @param array $params the parameters to be passed to the action. * @param array $params the parameters to be passed to the action.
* @return mixed the result of the action * @return mixed the result of the action
* @see runAction * @see runAction()
* @see forward
*/ */
public function run($route, $params = []) public function run($route, $params = [])
{ {

3
framework/yii/base/ErrorHandler.php

@ -16,6 +16,9 @@ use yii\web\HttpException;
* ErrorHandler displays these errors using appropriate views based on the * ErrorHandler displays these errors using appropriate views based on the
* nature of the errors and the mode the application runs at. * nature of the errors and the mode the application runs at.
* *
* ErrorHandler is configured as an application component in [[yii\base\Application]] by default.
* You can access that instance via `Yii::$app->errorHandler`.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Timur Ruziev <resurtm@gmail.com> * @author Timur Ruziev <resurtm@gmail.com>
* @since 2.0 * @since 2.0

3
framework/yii/base/Formatter.php

@ -19,6 +19,9 @@ use yii\helpers\Html;
* The behavior of some of them may be configured via the properties of Formatter. For example, * The behavior of some of them may be configured via the properties of Formatter. For example,
* by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string. * by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
* *
* Formatter is configured as an application component in [[yii\base\Application]] by default.
* You can access that instance via `Yii::$app->formatter`.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

38
framework/yii/base/Model.php

@ -46,7 +46,8 @@ use yii\validators\Validator;
* @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is
* read-only. * read-only.
* @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. * @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]].
* @property ArrayObject $validators All the validators declared in the model. This property is read-only. * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model.
* This property is read-only.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
@ -91,19 +92,19 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* *
* ~~~ * ~~~
* [ * [
* 'attribute list', * ['attribute1', 'attribute2'],
* 'validator type', * 'validator type',
* 'on' => 'scenario name', * 'on' => ['scenario1', 'scenario2'],
* ...other parameters... * ...other parameters...
* ] * ]
* ~~~ * ~~~
* *
* where * where
* *
* - attribute list: required, specifies the attributes (separated by commas) to be validated; * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string;
* - validator type: required, specifies the validator to be used. It can be the name of a model * - validator type: required, specifies the validator to be used. It can be the name of a model
* class method, the name of a built-in validator, or a validator class name (or its path alias). * class method, the name of a built-in validator, or a validator class name (or its path alias).
* - on: optional, specifies the [[scenario|scenarios]] (separated by commas) when the validation * - on: optional, specifies the [[scenario|scenarios]] array when the validation
* rule can be applied. If this option is not set, the rule will apply to all scenarios. * rule can be applied. If this option is not set, the rule will apply to all scenarios.
* - additional name-value pairs can be specified to initialize the corresponding validator properties. * - additional name-value pairs can be specified to initialize the corresponding validator properties.
* Please refer to individual validator class API for possible properties. * Please refer to individual validator class API for possible properties.
@ -128,7 +129,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* ~~~ * ~~~
* [ * [
* // built-in "required" validator * // built-in "required" validator
* ['username', 'required'], * [['username', 'password'], 'required'],
* // built-in "string" validator customized with "min" and "max" properties * // built-in "string" validator customized with "min" and "max" properties
* ['username', 'string', 'min' => 3, 'max' => 12], * ['username', 'string', 'min' => 3, 'max' => 12],
* // built-in "compare" validator that is used in "register" scenario only * // built-in "compare" validator that is used in "register" scenario only
@ -144,7 +145,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* merge the parent rules with child rules using functions such as `array_merge()`. * merge the parent rules with child rules using functions such as `array_merge()`.
* *
* @return array validation rules * @return array validation rules
* @see scenarios * @see scenarios()
*/ */
public function rules() public function rules()
{ {
@ -255,7 +256,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* merge the parent labels with child labels using functions such as `array_merge()`. * merge the parent labels with child labels using functions such as `array_merge()`.
* *
* @return array attribute labels (name => label) * @return array attribute labels (name => label)
* @see generateAttributeLabel * @see generateAttributeLabel()
*/ */
public function attributeLabels() public function attributeLabels()
{ {
@ -349,7 +350,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* $model->validators[] = $newValidator; * $model->validators[] = $newValidator;
* ~~~ * ~~~
* *
* @return ArrayObject all the validators declared in the model. * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model.
*/ */
public function getValidators() public function getValidators()
{ {
@ -369,7 +370,6 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
{ {
$validators = []; $validators = [];
$scenario = $this->getScenario(); $scenario = $this->getScenario();
/** @var $validator Validator */
foreach ($this->getValidators() as $validator) { foreach ($this->getValidators() as $validator) {
if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) { if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
$validators[] = $validator; $validators[] = $validator;
@ -391,7 +391,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
if ($rule instanceof Validator) { if ($rule instanceof Validator) {
$validators->append($rule); $validators->append($rule);
} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
$validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2)); $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
$validators->append($validator); $validators->append($validator);
} else { } else {
throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.'); throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
@ -444,8 +444,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* Returns the text label for the specified attribute. * Returns the text label for the specified attribute.
* @param string $attribute the attribute name * @param string $attribute the attribute name
* @return string the attribute label * @return string the attribute label
* @see generateAttributeLabel * @see generateAttributeLabel()
* @see attributeLabels * @see attributeLabels()
*/ */
public function getAttributeLabel($attribute) public function getAttributeLabel($attribute)
{ {
@ -483,8 +483,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* ] * ]
* ~~~ * ~~~
* *
* @see getFirstErrors * @see getFirstErrors()
* @see getFirstError * @see getFirstError()
*/ */
public function getErrors($attribute = null) public function getErrors($attribute = null)
{ {
@ -498,8 +498,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
/** /**
* Returns the first error of every attribute in the model. * Returns the first error of every attribute in the model.
* @return array the first errors. An empty array will be returned if there is no error. * @return array the first errors. An empty array will be returned if there is no error.
* @see getErrors * @see getErrors()
* @see getFirstError * @see getFirstError()
*/ */
public function getFirstErrors() public function getFirstErrors()
{ {
@ -520,8 +520,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* Returns the first error of the specified attribute. * Returns the first error of the specified attribute.
* @param string $attribute attribute name. * @param string $attribute attribute name.
* @return string the error message. Null is returned if no error. * @return string the error message. Null is returned if no error.
* @see getErrors * @see getErrors()
* @see getFirstErrors * @see getFirstErrors()
*/ */
public function getFirstError($attribute) public function getFirstError($attribute)
{ {

2
framework/yii/base/Module.php

@ -578,7 +578,7 @@ abstract class Module extends Component
{ {
$parts = $this->createController($route); $parts = $this->createController($route);
if (is_array($parts)) { if (is_array($parts)) {
/** @var $controller Controller */ /** @var Controller $controller */
list($controller, $actionID) = $parts; list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller; $oldController = Yii::$app->controller;
Yii::$app->controller = $controller; Yii::$app->controller = $controller;

15
framework/yii/base/Object.php

@ -10,7 +10,10 @@ namespace yii\base;
use Yii; use Yii;
/** /**
* Object is the base class that implements the *property* feature.
*
* @include @yii/base/Object.md * @include @yii/base/Object.md
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
@ -64,7 +67,7 @@ class Object implements Arrayable
* @return mixed the property value * @return mixed the property value
* @throws UnknownPropertyException if the property is not defined * @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is write-only * @throws InvalidCallException if the property is write-only
* @see __set * @see __set()
*/ */
public function __get($name) public function __get($name)
{ {
@ -87,7 +90,7 @@ class Object implements Arrayable
* @param mixed $value the property value * @param mixed $value the property value
* @throws UnknownPropertyException if the property is not defined * @throws UnknownPropertyException if the property is not defined
* @throws InvalidCallException if the property is read-only * @throws InvalidCallException if the property is read-only
* @see __get * @see __get()
*/ */
public function __set($name, $value) public function __set($name, $value)
{ {
@ -168,8 +171,8 @@ class Object implements Arrayable
* @param string $name the property name * @param string $name the property name
* @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkVars whether to treat member variables as properties
* @return boolean whether the property is defined * @return boolean whether the property is defined
* @see canGetProperty * @see canGetProperty()
* @see canSetProperty * @see canSetProperty()
*/ */
public function hasProperty($name, $checkVars = true) public function hasProperty($name, $checkVars = true)
{ {
@ -187,7 +190,7 @@ class Object implements Arrayable
* @param string $name the property name * @param string $name the property name
* @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkVars whether to treat member variables as properties
* @return boolean whether the property can be read * @return boolean whether the property can be read
* @see canSetProperty * @see canSetProperty()
*/ */
public function canGetProperty($name, $checkVars = true) public function canGetProperty($name, $checkVars = true)
{ {
@ -205,7 +208,7 @@ class Object implements Arrayable
* @param string $name the property name * @param string $name the property name
* @param boolean $checkVars whether to treat member variables as properties * @param boolean $checkVars whether to treat member variables as properties
* @return boolean whether the property can be written * @return boolean whether the property can be written
* @see canGetProperty * @see canGetProperty()
*/ */
public function canSetProperty($name, $checkVars = true) public function canSetProperty($name, $checkVars = true)
{ {

1
framework/yii/base/Request.php

@ -8,6 +8,7 @@
namespace yii\base; namespace yii\base;
/** /**
* Request represents a request that is handled by an [[Application]].
* *
* @property boolean $isConsoleRequest The value indicating whether the current request is made via console. * @property boolean $isConsoleRequest The value indicating whether the current request is made via console.
* @property string $scriptFile Entry script file path (processed w/ realpath()). * @property string $scriptFile Entry script file path (processed w/ realpath()).

2
framework/yii/base/Response.php

@ -8,6 +8,8 @@
namespace yii\base; namespace yii\base;
/** /**
* Response represents the response of an [[Application]] to a [[Request]].
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

4
framework/yii/base/View.php

@ -123,7 +123,7 @@ class View extends Component
* existing [[context]] will be used. * existing [[context]] will be used.
* @return string the rendering result * @return string the rendering result
* @throws InvalidParamException if the view cannot be resolved or the view file does not exist. * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
* @see renderFile * @see renderFile()
*/ */
public function render($view, $params = [], $context = null) public function render($view, $params = [], $context = null)
{ {
@ -410,7 +410,7 @@ class View extends Component
{ {
$properties['id'] = $id; $properties['id'] = $id;
$properties['view'] = $this; $properties['view'] = $this;
/** @var $cache FragmentCache */ /** @var FragmentCache $cache */
$cache = FragmentCache::begin($properties); $cache = FragmentCache::begin($properties);
if ($cache->getCachedContent() !== false) { if ($cache->getCachedContent() !== false) {
$this->endCache(); $this->endCache();

2
framework/yii/base/ViewEvent.php

@ -8,6 +8,8 @@
namespace yii\base; namespace yii\base;
/** /**
* ViewEvent represents events triggered by the [[View]] component.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

14
framework/yii/caching/ChainedDependency.php

@ -23,7 +23,7 @@ class ChainedDependency extends Dependency
* @var Dependency[] list of dependencies that this dependency is composed of. * @var Dependency[] list of dependencies that this dependency is composed of.
* Each array element must be a dependency object. * Each array element must be a dependency object.
*/ */
public $dependencies; public $dependencies = [];
/** /**
* @var boolean whether this dependency is depending on every dependency in [[dependencies]]. * @var boolean whether this dependency is depending on every dependency in [[dependencies]].
* Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed. * Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed.
@ -33,18 +33,6 @@ class ChainedDependency extends Dependency
public $dependOnAll = true; public $dependOnAll = true;
/** /**
* Constructor.
* @param Dependency[] $dependencies list of dependencies that this dependency is composed of.
* Each array element should be a dependency object.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($dependencies = [], $config = [])
{
$this->dependencies = $dependencies;
parent::__construct($config);
}
/**
* Evaluates the dependency by generating and saving the data related with dependency. * Evaluates the dependency by generating and saving the data related with dependency.
* @param Cache $cache the cache component that is currently evaluating this dependency * @param Cache $cache the cache component that is currently evaluating this dependency
*/ */

18
framework/yii/caching/DbDependency.php

@ -34,20 +34,7 @@ class DbDependency extends Dependency
/** /**
* @var array the parameters (name => value) to be bound to the SQL statement specified by [[sql]]. * @var array the parameters (name => value) to be bound to the SQL statement specified by [[sql]].
*/ */
public $params; public $params = [];
/**
* Constructor.
* @param string $sql the SQL query whose result is used to determine if the dependency has been changed.
* @param array $params the parameters (name => value) to be bound to the SQL statement specified by [[sql]].
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($sql, $params = [], $config = [])
{
$this->sql = $sql;
$this->params = $params;
parent::__construct($config);
}
/** /**
* Generates the data needed to determine if dependency has been changed. * Generates the data needed to determine if dependency has been changed.
@ -62,6 +49,9 @@ class DbDependency extends Dependency
if (!$db instanceof Connection) { if (!$db instanceof Connection) {
throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection."); throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection.");
} }
if ($this->sql === null) {
throw new InvalidConfigException("DbDependency::sql must be set.");
}
if ($db->enableQueryCache) { if ($db->enableQueryCache) {
// temporarily disable and re-enable query caching // temporarily disable and re-enable query caching

15
framework/yii/caching/ExpressionDependency.php

@ -27,7 +27,7 @@ class ExpressionDependency extends Dependency
* A PHP expression can be any PHP code that evaluates to a value. To learn more about what an expression is, * A PHP expression can be any PHP code that evaluates to a value. To learn more about what an expression is,
* please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php). * please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php).
*/ */
public $expression; public $expression = 'true';
/** /**
* @var mixed custom parameters associated with this dependency. You may get the value * @var mixed custom parameters associated with this dependency. You may get the value
* of this property in [[expression]] using `$this->params`. * of this property in [[expression]] using `$this->params`.
@ -35,19 +35,6 @@ class ExpressionDependency extends Dependency
public $params; public $params;
/** /**
* Constructor.
* @param string $expression the PHP expression whose result is used to determine the dependency.
* @param mixed $params the custom parameters associated with this dependency
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($expression = 'true', $params = null, $config = [])
{
$this->expression = $expression;
$this->params = $params;
parent::__construct($config);
}
/**
* Generates the data needed to determine if dependency has been changed. * Generates the data needed to determine if dependency has been changed.
* This method returns the result of the PHP expression. * This method returns the result of the PHP expression.
* @param Cache $cache the cache component that is currently evaluating this dependency * @param Cache $cache the cache component that is currently evaluating this dependency

16
framework/yii/caching/FileDependency.php

@ -6,6 +6,7 @@
*/ */
namespace yii\caching; namespace yii\caching;
use yii\base\InvalidConfigException;
/** /**
* FileDependency represents a dependency based on a file's last modification time. * FileDependency represents a dependency based on a file's last modification time.
@ -25,24 +26,17 @@ class FileDependency extends Dependency
public $fileName; public $fileName;
/** /**
* Constructor.
* @param string $fileName name of the file whose change is to be checked.
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($fileName = null, $config = [])
{
$this->fileName = $fileName;
parent::__construct($config);
}
/**
* Generates the data needed to determine if dependency has been changed. * Generates the data needed to determine if dependency has been changed.
* This method returns the file's last modification time. * This method returns the file's last modification time.
* @param Cache $cache the cache component that is currently evaluating this dependency * @param Cache $cache the cache component that is currently evaluating this dependency
* @return mixed the data needed to determine if dependency has been changed. * @return mixed the data needed to determine if dependency has been changed.
* @throws InvalidConfigException if [[fileName]] is not set
*/ */
protected function generateDependencyData($cache) protected function generateDependencyData($cache)
{ {
if ($this->fileName === null) {
throw new InvalidConfigException('FileDependency::fileName must be set');
}
return @filemtime($this->fileName); return @filemtime($this->fileName);
} }
} }

22
framework/yii/caching/GroupDependency.php

@ -6,6 +6,7 @@
*/ */
namespace yii\caching; namespace yii\caching;
use yii\base\InvalidConfigException;
/** /**
* GroupDependency marks a cached data item with a group name. * GroupDependency marks a cached data item with a group name.
@ -19,29 +20,22 @@ namespace yii\caching;
class GroupDependency extends Dependency class GroupDependency extends Dependency
{ {
/** /**
* @var string the group name * @var string the group name. This property must be set.
*/ */
public $group; public $group;
/** /**
* Constructor.
* @param string $group the group name
* @param array $config name-value pairs that will be used to initialize the object properties
*/
public function __construct($group, $config = [])
{
$this->group = $group;
parent::__construct($config);
}
/**
* Generates the data needed to determine if dependency has been changed. * Generates the data needed to determine if dependency has been changed.
* This method does nothing in this class. * This method does nothing in this class.
* @param Cache $cache the cache component that is currently evaluating this dependency * @param Cache $cache the cache component that is currently evaluating this dependency
* @return mixed the data needed to determine if dependency has been changed. * @return mixed the data needed to determine if dependency has been changed.
* @throws InvalidConfigException if [[group]] is not set.
*/ */
protected function generateDependencyData($cache) protected function generateDependencyData($cache)
{ {
if ($this->group === null) {
throw new InvalidConfigException('GroupDependency::group must be set');
}
$version = $cache->get([__CLASS__, $this->group]); $version = $cache->get([__CLASS__, $this->group]);
if ($version === false) { if ($version === false) {
$version = $this->invalidate($cache, $this->group); $version = $this->invalidate($cache, $this->group);
@ -53,9 +47,13 @@ class GroupDependency extends Dependency
* Performs the actual dependency checking. * Performs the actual dependency checking.
* @param Cache $cache the cache component that is currently evaluating this dependency * @param Cache $cache the cache component that is currently evaluating this dependency
* @return boolean whether the dependency is changed or not. * @return boolean whether the dependency is changed or not.
* @throws InvalidConfigException if [[group]] is not set.
*/ */
public function getHasChanged($cache) public function getHasChanged($cache)
{ {
if ($this->group === null) {
throw new InvalidConfigException('GroupDependency::group must be set');
}
$version = $cache->get([__CLASS__, $this->group]); $version = $cache->get([__CLASS__, $this->group]);
return $version === false || $version !== $this->data; return $version === false || $version !== $this->data;
} }

2
framework/yii/caching/MemCache.php

@ -31,7 +31,7 @@ use yii\base\InvalidConfigException;
* [ * [
* 'components' => [ * 'components' => [
* 'cache' => [ * 'cache' => [
* 'class' => 'MemCache', * 'class' => 'yii\caching\MemCache',
* 'servers' => [ * 'servers' => [
* [ * [
* 'host' => 'server1', * 'host' => 'server1',

2
framework/yii/captcha/CaptchaAsset.php

@ -10,6 +10,8 @@ namespace yii\captcha;
use yii\web\AssetBundle; use yii\web\AssetBundle;
/** /**
* This asset bundle provides the javascript files needed for the [[Captcha]] widget.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

4
framework/yii/console/Request.php

@ -8,6 +8,10 @@
namespace yii\console; namespace yii\console;
/** /**
* The console Request represents the environment information for a console application.
*
* It is a wrapper for the PHP `$_SERVER` variable which holds information about the
* currently running PHP script and the command line arguments given to it.
* *
* @property array $params The command line arguments. It does not include the entry script name. * @property array $params The command line arguments. It does not include the entry script name.
* *

2
framework/yii/console/Response.php

@ -8,6 +8,8 @@
namespace yii\console; namespace yii\console;
/** /**
* The console Response represents the result of a console application by holding the [[exitCode]].
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

2
framework/yii/console/controllers/CacheController.php

@ -52,7 +52,7 @@ class CacheController extends Controller
*/ */
public function actionFlush($component = 'cache') public function actionFlush($component = 'cache')
{ {
/** @var $cache Cache */ /** @var Cache $cache */
$cache = Yii::$app->getComponent($component); $cache = Yii::$app->getComponent($component);
if (!$cache || !$cache instanceof Cache) { if (!$cache || !$cache instanceof Cache) {
throw new Exception('Application component "'.$component.'" is not defined or not a cache.'); throw new Exception('Application component "'.$component.'" is not defined or not a cache.');

20
framework/yii/data/ActiveDataProvider.php

@ -8,11 +8,11 @@
namespace yii\data; namespace yii\data;
use Yii; use Yii;
use yii\db\ActiveQueryInterface;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\Model; use yii\base\Model;
use yii\db\Query;
use yii\db\ActiveQuery;
use yii\db\Connection; use yii\db\Connection;
use yii\db\QueryInterface;
/** /**
* ActiveDataProvider implements a data provider based on [[Query]] and [[ActiveQuery]]. * ActiveDataProvider implements a data provider based on [[Query]] and [[ActiveQuery]].
@ -54,7 +54,7 @@ use yii\db\Connection;
class ActiveDataProvider extends BaseDataProvider class ActiveDataProvider extends BaseDataProvider
{ {
/** /**
* @var Query the query that is used to fetch data models and [[totalCount]] * @var QueryInterface the query that is used to fetch data models and [[totalCount]]
* if it is not explicitly set. * if it is not explicitly set.
*/ */
public $query; public $query;
@ -97,8 +97,8 @@ class ActiveDataProvider extends BaseDataProvider
*/ */
protected function prepareModels() protected function prepareModels()
{ {
if (!$this->query instanceof Query) { if (!$this->query instanceof QueryInterface) {
throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
} }
if (($pagination = $this->getPagination()) !== false) { if (($pagination = $this->getPagination()) !== false) {
$pagination->totalCount = $this->getTotalCount(); $pagination->totalCount = $this->getTotalCount();
@ -125,7 +125,7 @@ class ActiveDataProvider extends BaseDataProvider
} }
} }
return $keys; return $keys;
} elseif ($this->query instanceof ActiveQuery) { } elseif ($this->query instanceof ActiveQueryInterface) {
/** @var \yii\db\ActiveRecord $class */ /** @var \yii\db\ActiveRecord $class */
$class = $this->query->modelClass; $class = $this->query->modelClass;
$pks = $class::primaryKey(); $pks = $class::primaryKey();
@ -154,11 +154,11 @@ class ActiveDataProvider extends BaseDataProvider
*/ */
protected function prepareTotalCount() protected function prepareTotalCount()
{ {
if (!$this->query instanceof Query) { if (!$this->query instanceof QueryInterface) {
throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
} }
$query = clone $this->query; $query = clone $this->query;
return (int) $query->limit(-1)->offset(-1)->count('*', $this->db); return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db);
} }
/** /**
@ -167,7 +167,7 @@ class ActiveDataProvider extends BaseDataProvider
public function setSort($value) public function setSort($value)
{ {
parent::setSort($value); parent::setSort($value);
if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQuery) { if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQueryInterface) {
/** @var Model $model */ /** @var Model $model */
$model = new $this->query->modelClass; $model = new $this->query->modelClass;
foreach ($model->attributes() as $attribute) { foreach ($model->attributes() as $attribute) {

205
framework/yii/db/ActiveQuery.php

@ -11,8 +11,7 @@ namespace yii\db;
/** /**
* ActiveQuery represents a DB query associated with an Active Record class. * ActiveQuery represents a DB query associated with an Active Record class.
* *
* ActiveQuery instances are usually created by [[ActiveRecord::find()]], [[ActiveRecord::findBySql()]] * ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]].
* and [[ActiveRecord::count()]].
* *
* ActiveQuery mainly provides the following methods to retrieve the query results: * ActiveQuery mainly provides the following methods to retrieve the query results:
* *
@ -42,23 +41,13 @@ namespace yii\db;
* ~~~ * ~~~
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ActiveQuery extends Query class ActiveQuery extends Query implements ActiveQueryInterface
{ {
/** use ActiveQueryTrait;
* @var string the name of the ActiveRecord class.
*/
public $modelClass;
/**
* @var array list of relations that this query should be performed with
*/
public $with;
/**
* @var boolean whether to return each record as an array. If false (default), an object
* of [[modelClass]] will be created to represent each record.
*/
public $asArray;
/** /**
* @var string the SQL statement to be executed for retrieving AR records. * @var string the SQL statement to be executed for retrieving AR records.
* This is set by [[ActiveRecord::findBySql()]]. * This is set by [[ActiveRecord::findBySql()]].
@ -67,25 +56,6 @@ class ActiveQuery extends Query
/** /**
* PHP magic method.
* This method allows calling static method defined in [[modelClass]] via this query object.
* It is mainly implemented for supporting the feature of scope.
* @param string $name the method name to be called
* @param array $params the parameters passed to the method
* @return mixed the method return result
*/
public function __call($name, $params)
{
if (method_exists($this->modelClass, $name)) {
array_unshift($params, $this);
call_user_func_array([$this->modelClass, $name], $params);
return $this;
} else {
return parent::__call($name, $params);
}
}
/**
* Executes query and returns all results as an array. * Executes query and returns all results as an array.
* @param Connection $db the DB connection used to create the DB command. * @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used. * If null, the DB connection returned by [[modelClass]] will be used.
@ -122,7 +92,7 @@ class ActiveQuery extends Query
if ($this->asArray) { if ($this->asArray) {
$model = $row; $model = $row;
} else { } else {
/** @var $class ActiveRecord */ /** @var ActiveRecord $class */
$class = $this->modelClass; $class = $this->modelClass;
$model = $class::create($row); $model = $class::create($row);
} }
@ -145,7 +115,7 @@ class ActiveQuery extends Query
*/ */
public function createCommand($db = null) public function createCommand($db = null)
{ {
/** @var $modelClass ActiveRecord */ /** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass; $modelClass = $this->modelClass;
if ($db === null) { if ($db === null) {
$db = $modelClass::getDb(); $db = $modelClass::getDb();
@ -164,165 +134,4 @@ class ActiveQuery extends Query
} }
return $db->createCommand($this->sql, $params); return $db->createCommand($this->sql, $params);
} }
/**
* Sets the [[asArray]] property.
* @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
* @return static the query object itself
*/
public function asArray($value = true)
{
$this->asArray = $value;
return $this;
}
/**
* Specifies the relations with which this query should be performed.
*
* The parameters to this method can be either one or multiple strings, or a single array
* of relation names and the optional callbacks to customize the relations.
*
* A relation name can refer to a relation defined in [[modelClass]]
* or a sub-relation that stands for a relation of a related record.
* For example, `orders.address` means the `address` relation defined
* in the model class corresponding to the `orders` relation.
*
* The followings are some usage examples:
*
* ~~~
* // find customers together with their orders and country
* Customer::find()->with('orders', 'country')->all();
* // find customers together with their orders and the orders' shipping address
* Customer::find()->with('orders.address')->all();
* // find customers together with their country and orders of status 1
* Customer::find()->with([
* 'orders' => function($query) {
* $query->andWhere('status = 1');
* },
* 'country',
* ])->all();
* ~~~
*
* @return static the query object itself
*/
public function with()
{
$this->with = func_get_args();
if (isset($this->with[0]) && is_array($this->with[0])) {
// the parameter is given as an array
$this->with = $this->with[0];
}
return $this;
}
/**
* Sets the [[indexBy]] property.
* @param string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row or model data. The signature of the callable should be:
*
* ~~~
* // $model is an AR instance when `asArray` is false,
* // or an array of column values when `asArray` is true.
* function ($model)
* {
* // return the index value corresponding to $model
* }
* ~~~
*
* @return static the query object itself
*/
public function indexBy($column)
{
return parent::indexBy($column);
}
private function createModels($rows)
{
$models = [];
if ($this->asArray) {
if ($this->indexBy === null) {
return $rows;
}
foreach ($rows as $row) {
if (is_string($this->indexBy)) {
$key = $row[$this->indexBy];
} else {
$key = call_user_func($this->indexBy, $row);
}
$models[$key] = $row;
}
} else {
/** @var $class ActiveRecord */
$class = $this->modelClass;
if ($this->indexBy === null) {
foreach ($rows as $row) {
$models[] = $class::create($row);
}
} else {
foreach ($rows as $row) {
$model = $class::create($row);
if (is_string($this->indexBy)) {
$key = $model->{$this->indexBy};
} else {
$key = call_user_func($this->indexBy, $model);
}
$models[$key] = $model;
}
}
}
return $models;
}
private function populateRelations(&$models, $with)
{
$primaryModel = new $this->modelClass;
$relations = $this->normalizeRelations($primaryModel, $with);
foreach ($relations as $name => $relation) {
if ($relation->asArray === null) {
// inherit asArray from primary query
$relation->asArray = $this->asArray;
}
$relation->findWith($name, $models);
}
}
/**
* @param ActiveRecord $model
* @param array $with
* @return ActiveRelation[]
*/
private function normalizeRelations($model, $with)
{
$relations = [];
foreach ($with as $name => $callback) {
if (is_integer($name)) {
$name = $callback;
$callback = null;
}
if (($pos = strpos($name, '.')) !== false) {
// with sub-relations
$childName = substr($name, $pos + 1);
$name = substr($name, 0, $pos);
} else {
$childName = null;
}
$t = strtolower($name);
if (!isset($relations[$t])) {
$relation = $model->getRelation($name);
$relation->primaryModel = null;
$relations[$t] = $relation;
} else {
$relation = $relations[$t];
}
if (isset($childName)) {
$relation->with[$childName] = $callback;
} elseif ($callback !== null) {
call_user_func($callback, $relation);
}
}
return $relations;
}
} }

77
framework/yii/db/ActiveQueryInterface.php

@ -0,0 +1,77 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* ActiveQueryInterface defines the common interface to be implemented by active record query classes.
*
* A class implementing this interface should also use [[ActiveQueryTrait]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
interface ActiveQueryInterface extends QueryInterface
{
/**
* Sets the [[asArray]] property.
* @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
* @return static the query object itself
*/
public function asArray($value = true);
/**
* Sets the [[indexBy]] property.
* @param string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row or model data. The signature of the callable should be:
*
* ~~~
* // $model is an AR instance when `asArray` is false,
* // or an array of column values when `asArray` is true.
* function ($model)
* {
* // return the index value corresponding to $model
* }
* ~~~
*
* @return static the query object itself
*/
public function indexBy($column);
/**
* Specifies the relations with which this query should be performed.
*
* The parameters to this method can be either one or multiple strings, or a single array
* of relation names and the optional callbacks to customize the relations.
*
* A relation name can refer to a relation defined in [[modelClass]]
* or a sub-relation that stands for a relation of a related record.
* For example, `orders.address` means the `address` relation defined
* in the model class corresponding to the `orders` relation.
*
* The followings are some usage examples:
*
* ~~~
* // find customers together with their orders and country
* Customer::find()->with('orders', 'country')->all();
* // find customers together with their orders and the orders' shipping address
* Customer::find()->with('orders.address')->all();
* // find customers together with their country and orders of status 1
* Customer::find()->with([
* 'orders' => function($query) {
* $query->andWhere('status = 1');
* },
* 'country',
* ])->all();
* ~~~
*
* @return static the query object itself
*/
public function with();
}

199
framework/yii/db/ActiveQueryTrait.php

@ -0,0 +1,199 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* ActiveQueryTrait implements the common methods and properties for active record query classes.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
trait ActiveQueryTrait
{
/**
* @var string the name of the ActiveRecord class.
*/
public $modelClass;
/**
* @var array list of relations that this query should be performed with
*/
public $with;
/**
* @var boolean whether to return each record as an array. If false (default), an object
* of [[modelClass]] will be created to represent each record.
*/
public $asArray;
/**
* PHP magic method.
* This method allows calling static method defined in [[modelClass]] via this query object.
* It is mainly implemented for supporting the feature of scope.
* @param string $name the method name to be called
* @param array $params the parameters passed to the method
* @return mixed the method return result
*/
public function __call($name, $params)
{
if (method_exists($this->modelClass, $name)) {
array_unshift($params, $this);
call_user_func_array([$this->modelClass, $name], $params);
return $this;
} else {
return parent::__call($name, $params);
}
}
/**
* Sets the [[asArray]] property.
* @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
* @return static the query object itself
*/
public function asArray($value = true)
{
$this->asArray = $value;
return $this;
}
/**
* Specifies the relations with which this query should be performed.
*
* The parameters to this method can be either one or multiple strings, or a single array
* of relation names and the optional callbacks to customize the relations.
*
* A relation name can refer to a relation defined in [[modelClass]]
* or a sub-relation that stands for a relation of a related record.
* For example, `orders.address` means the `address` relation defined
* in the model class corresponding to the `orders` relation.
*
* The followings are some usage examples:
*
* ~~~
* // find customers together with their orders and country
* Customer::find()->with('orders', 'country')->all();
* // find customers together with their orders and the orders' shipping address
* Customer::find()->with('orders.address')->all();
* // find customers together with their country and orders of status 1
* Customer::find()->with([
* 'orders' => function($query) {
* $query->andWhere('status = 1');
* },
* 'country',
* ])->all();
* ~~~
*
* @return static the query object itself
*/
public function with()
{
$this->with = func_get_args();
if (isset($this->with[0]) && is_array($this->with[0])) {
// the parameter is given as an array
$this->with = $this->with[0];
}
return $this;
}
/**
* Converts found rows into model instances
* @param array $rows
* @return array|ActiveRecord[]
*/
private function createModels($rows)
{
$models = [];
if ($this->asArray) {
if ($this->indexBy === null) {
return $rows;
}
foreach ($rows as $row) {
if (is_string($this->indexBy)) {
$key = $row[$this->indexBy];
} else {
$key = call_user_func($this->indexBy, $row);
}
$models[$key] = $row;
}
} else {
/** @var ActiveRecord $class */
$class = $this->modelClass;
if ($this->indexBy === null) {
foreach ($rows as $row) {
$models[] = $class::create($row);
}
} else {
foreach ($rows as $row) {
$model = $class::create($row);
if (is_string($this->indexBy)) {
$key = $model->{$this->indexBy};
} else {
$key = call_user_func($this->indexBy, $model);
}
$models[$key] = $model;
}
}
}
return $models;
}
/**
* @param ActiveRecord[] $models
* @param array $with
*/
private function populateRelations(&$models, $with)
{
$primaryModel = new $this->modelClass;
$relations = $this->normalizeRelations($primaryModel, $with);
foreach ($relations as $name => $relation) {
if ($relation->asArray === null) {
// inherit asArray from primary query
$relation->asArray = $this->asArray;
}
$relation->findWith($name, $models);
}
}
/**
* @param ActiveRecord $model
* @param array $with
* @return ActiveRelationInterface[]
*/
private function normalizeRelations($model, $with)
{
$relations = [];
foreach ($with as $name => $callback) {
if (is_integer($name)) {
$name = $callback;
$callback = null;
}
if (($pos = strpos($name, '.')) !== false) {
// with sub-relations
$childName = substr($name, $pos + 1);
$name = substr($name, 0, $pos);
} else {
$childName = null;
}
if (!isset($relations[$name])) {
$relation = $model->getRelation($name);
$relation->primaryModel = null;
$relations[$name] = $relation;
} else {
$relation = $relations[$name];
}
if (isset($childName)) {
$relation->with[$childName] = $callback;
} elseif ($callback !== null) {
call_user_func($callback, $relation);
}
}
return $relations;
}
}

47
framework/yii/db/ActiveRecord.php

@ -29,11 +29,14 @@ use yii\helpers\Inflector;
* @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is
* returned if the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be * returned if the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be
* returned if the key value is null). This property is read-only. * returned if the key value is null). This property is read-only.
* @property array $populatedRelations An array of relation data indexed by relation names. This property is
* read-only.
* @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if
* the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be returned if * the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
* the key value is null). This property is read-only. * the key value is null). This property is read-only.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ActiveRecord extends Model class ActiveRecord extends Model
@ -250,7 +253,7 @@ class ActiveRecord extends Model
/** /**
* Creates an [[ActiveQuery]] instance. * Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query. * This method is called by [[find()]], [[findBySql()]] to start a SELECT query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified * You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.) * written for querying `Customer` purpose.)
* @return ActiveQuery the newly created [[ActiveQuery]] instance. * @return ActiveQuery the newly created [[ActiveQuery]] instance.
@ -370,7 +373,7 @@ class ActiveRecord extends Model
* This method is overridden so that attributes and related objects can be accessed like properties. * This method is overridden so that attributes and related objects can be accessed like properties.
* @param string $name property name * @param string $name property name
* @return mixed property value * @return mixed property value
* @see getAttribute * @see getAttribute()
*/ */
public function __get($name) public function __get($name)
{ {
@ -383,7 +386,7 @@ class ActiveRecord extends Model
return $this->_related[$name]; return $this->_related[$name];
} }
$value = parent::__get($name); $value = parent::__get($name);
if ($value instanceof ActiveRelation) { if ($value instanceof ActiveRelationInterface) {
return $this->_related[$name] = $value->multiple ? $value->all() : $value->one(); return $this->_related[$name] = $value->multiple ? $value->all() : $value->one();
} else { } else {
return $value; return $value;
@ -472,7 +475,7 @@ class ActiveRecord extends Model
*/ */
public function hasOne($class, $link) public function hasOne($class, $link)
{ {
return new ActiveRelation([ return $this->createActiveRelation([
'modelClass' => $class, 'modelClass' => $class,
'primaryModel' => $this, 'primaryModel' => $this,
'link' => $link, 'link' => $link,
@ -510,7 +513,7 @@ class ActiveRecord extends Model
*/ */
public function hasMany($class, $link) public function hasMany($class, $link)
{ {
return new ActiveRelation([ return $this->createActiveRelation([
'modelClass' => $class, 'modelClass' => $class,
'primaryModel' => $this, 'primaryModel' => $this,
'link' => $link, 'link' => $link,
@ -519,6 +522,18 @@ class ActiveRecord extends Model
} }
/** /**
* Creates an [[ActiveRelation]] instance.
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
* You may override this method to return a customized relation.
* @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelation the newly created [[ActiveRelation]] instance.
*/
protected function createActiveRelation($config = [])
{
return new ActiveRelation($config);
}
/**
* Populates the named relation with the related records. * Populates the named relation with the related records.
* Note that this method does not check if the relation exists or not. * Note that this method does not check if the relation exists or not.
* @param string $name the relation name (case-sensitive) * @param string $name the relation name (case-sensitive)
@ -574,7 +589,7 @@ class ActiveRecord extends Model
* null will be returned. * null will be returned.
* @param string $name the attribute name * @param string $name the attribute name
* @return mixed the attribute value. Null if the attribute is not set or does not exist. * @return mixed the attribute value. Null if the attribute is not set or does not exist.
* @see hasAttribute * @see hasAttribute()
*/ */
public function getAttribute($name) public function getAttribute($name)
{ {
@ -586,7 +601,7 @@ class ActiveRecord extends Model
* @param string $name the attribute name * @param string $name the attribute name
* @param mixed $value the attribute value. * @param mixed $value the attribute value.
* @throws InvalidParamException if the named attribute does not exist. * @throws InvalidParamException if the named attribute does not exist.
* @see hasAttribute * @see hasAttribute()
*/ */
public function setAttribute($name, $value) public function setAttribute($name, $value)
{ {
@ -623,7 +638,7 @@ class ActiveRecord extends Model
* @param string $name the attribute name * @param string $name the attribute name
* @return mixed the old attribute value. Null if the attribute is not loaded before * @return mixed the old attribute value. Null if the attribute is not loaded before
* or does not exist. * or does not exist.
* @see hasAttribute * @see hasAttribute()
*/ */
public function getOldAttribute($name) public function getOldAttribute($name)
{ {
@ -635,7 +650,7 @@ class ActiveRecord extends Model
* @param string $name the attribute name * @param string $name the attribute name
* @param mixed $value the old attribute value. * @param mixed $value the old attribute value.
* @throws InvalidParamException if the named attribute does not exist. * @throws InvalidParamException if the named attribute does not exist.
* @see hasAttribute * @see hasAttribute()
*/ */
public function setOldAttribute($name, $value) public function setOldAttribute($name, $value)
{ {
@ -1028,7 +1043,7 @@ class ActiveRecord extends Model
/** /**
* Sets the value indicating whether the record is new. * Sets the value indicating whether the record is new.
* @param boolean $value whether the record is new and should be inserted when calling [[save()]]. * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
* @see getIsNewRecord * @see getIsNewRecord()
*/ */
public function setIsNewRecord($value) public function setIsNewRecord($value)
{ {
@ -1281,7 +1296,7 @@ class ActiveRecord extends Model
$getter = 'get' . $name; $getter = 'get' . $name;
try { try {
$relation = $this->$getter(); $relation = $this->$getter();
if ($relation instanceof ActiveRelation) { if ($relation instanceof ActiveRelationInterface) {
return $relation; return $relation;
} else { } else {
return null; return null;
@ -1319,9 +1334,9 @@ class ActiveRecord extends Model
throw new InvalidCallException('Unable to link models: both models must NOT be newly created.'); throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
} }
if (is_array($relation->via)) { if (is_array($relation->via)) {
/** @var $viaRelation ActiveRelation */ /** @var ActiveRelation $viaRelation */
list($viaName, $viaRelation) = $relation->via; list($viaName, $viaRelation) = $relation->via;
/** @var $viaClass ActiveRecord */ /** @var ActiveRecord $viaClass */
$viaClass = $viaRelation->modelClass; $viaClass = $viaRelation->modelClass;
$viaTable = $viaClass::tableName(); $viaTable = $viaClass::tableName();
// unset $viaName so that it can be reloaded to reflect the change // unset $viaName so that it can be reloaded to reflect the change
@ -1394,9 +1409,9 @@ class ActiveRecord extends Model
if ($relation->via !== null) { if ($relation->via !== null) {
if (is_array($relation->via)) { if (is_array($relation->via)) {
/** @var $viaRelation ActiveRelation */ /** @var ActiveRelation $viaRelation */
list($viaName, $viaRelation) = $relation->via; list($viaName, $viaRelation) = $relation->via;
/** @var $viaClass ActiveRecord */ /** @var ActiveRecord $viaClass */
$viaClass = $viaRelation->modelClass; $viaClass = $viaRelation->modelClass;
$viaTable = $viaClass::tableName(); $viaTable = $viaClass::tableName();
unset($this->_related[$viaName]); unset($this->_related[$viaName]);
@ -1442,7 +1457,7 @@ class ActiveRecord extends Model
if (!$relation->multiple) { if (!$relation->multiple) {
unset($this->_related[$name]); unset($this->_related[$name]);
} elseif (isset($this->_related[$name])) { } elseif (isset($this->_related[$name])) {
/** @var $b ActiveRecord */ /** @var ActiveRecord $b */
foreach ($this->_related[$name] as $a => $b) { foreach ($this->_related[$name] as $a => $b) {
if ($model->getPrimaryKey() == $b->getPrimaryKey()) { if ($model->getPrimaryKey() == $b->getPrimaryKey()) {
unset($this->_related[$name][$a]); unset($this->_related[$name][$a]);

236
framework/yii/db/ActiveRelation.php

@ -8,8 +8,6 @@
namespace yii\db; namespace yii\db;
use yii\base\InvalidConfigException;
/** /**
* ActiveRelation represents a relation between two Active Record classes. * ActiveRelation represents a relation between two Active Record classes.
* *
@ -22,61 +20,16 @@ use yii\base\InvalidConfigException;
* *
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method. * If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
* *
* @property array|ActiveRelation $via the query associated with the pivot table. Please call [[via()]]
* or [[viaTable()]] to set this property instead of directly setting it.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ActiveRelation extends ActiveQuery class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{ {
/** use ActiveRelationTrait;
* @var boolean whether this relation should populate all query results into AR instances.
* If false, only the first row of the results will be retrieved.
*/
public $multiple;
/**
* @var ActiveRecord the primary model that this relation is associated with.
* This is used only in lazy loading with dynamic query options.
*/
public $primaryModel;
/**
* @var array the columns of the primary and foreign tables that establish the relation.
* The array keys must be columns of the table for this relation, and the array values
* must be the corresponding columns from the primary table.
* Do not prefix or quote the column names as they will be done automatically by Yii.
*/
public $link;
/**
* @var array|ActiveRelation the query associated with the pivot table. Please call [[via()]]
* or [[viaTable()]] to set this property instead of directly setting it.
*/
public $via;
/**
* Clones internal objects.
*/
public function __clone()
{
if (is_object($this->via)) {
// make a clone of "via" object so that the same query object can be reused multiple times
$this->via = clone $this->via;
}
}
/**
* Specifies the relation associated with the pivot table.
* @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return static the relation object itself.
*/
public function via($relationName, $callable = null)
{
$relation = $this->primaryModel->getRelation($relationName);
$this->via = [$relationName, $relation];
if ($callable !== null) {
call_user_func($callable, $relation);
}
return $this;
}
/** /**
* Specifies the pivot table. * Specifies the pivot table.
@ -120,7 +73,7 @@ class ActiveRelation extends ActiveQuery
$this->filterByModels($viaModels); $this->filterByModels($viaModels);
} elseif (is_array($this->via)) { } elseif (is_array($this->via)) {
// via relation // via relation
/** @var $viaQuery ActiveRelation */ /** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via; list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) { if ($viaQuery->multiple) {
$viaModels = $viaQuery->all(); $viaModels = $viaQuery->all();
@ -137,179 +90,4 @@ class ActiveRelation extends ActiveQuery
} }
return parent::createCommand($db); return parent::createCommand($db);
} }
/**
* Finds the related records and populates them into the primary models.
* This method is internally used by [[ActiveQuery]]. Do not call it directly.
* @param string $name the relation name
* @param array $primaryModels primary models
* @return array the related models
* @throws InvalidConfigException
*/
public function findWith($name, &$primaryModels)
{
if (!is_array($this->link)) {
throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
}
if ($this->via instanceof self) {
// via pivot table
/** @var $viaQuery ActiveRelation */
$viaQuery = $this->via;
$viaModels = $viaQuery->findPivotRows($primaryModels);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var $viaQuery ActiveRelation */
list($viaName, $viaQuery) = $this->via;
$viaQuery->primaryModel = null;
$viaModels = $viaQuery->findWith($viaName, $primaryModels);
$this->filterByModels($viaModels);
} else {
$this->filterByModels($primaryModels);
}
if (count($primaryModels) === 1 && !$this->multiple) {
$model = $this->one();
foreach ($primaryModels as $i => $primaryModel) {
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $model);
} else {
$primaryModels[$i][$name] = $model;
}
}
return [$model];
} else {
$models = $this->all();
if (isset($viaModels, $viaQuery)) {
$buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
} else {
$buckets = $this->buildBuckets($models, $this->link);
}
$link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
foreach ($primaryModels as $i => $primaryModel) {
$key = $this->getModelKey($primaryModel, $link);
$value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null);
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $value);
} else {
$primaryModels[$i][$name] = $value;
}
}
return $models;
}
}
/**
* @param array $models
* @param array $link
* @param array $viaModels
* @param array $viaLink
* @return array
*/
private function buildBuckets($models, $link, $viaModels = null, $viaLink = null)
{
$buckets = [];
$linkKeys = array_keys($link);
foreach ($models as $i => $model) {
$key = $this->getModelKey($model, $linkKeys);
if ($this->indexBy !== null) {
$buckets[$key][$i] = $model;
} else {
$buckets[$key][] = $model;
}
}
if ($viaModels !== null) {
$viaBuckets = [];
$viaLinkKeys = array_keys($viaLink);
$linkValues = array_values($link);
foreach ($viaModels as $viaModel) {
$key1 = $this->getModelKey($viaModel, $viaLinkKeys);
$key2 = $this->getModelKey($viaModel, $linkValues);
if (isset($buckets[$key2])) {
foreach ($buckets[$key2] as $i => $bucket) {
if ($this->indexBy !== null) {
$viaBuckets[$key1][$i] = $bucket;
} else {
$viaBuckets[$key1][] = $bucket;
}
}
}
}
$buckets = $viaBuckets;
}
if (!$this->multiple) {
foreach ($buckets as $i => $bucket) {
$buckets[$i] = reset($bucket);
}
}
return $buckets;
}
/**
* @param ActiveRecord|array $model
* @param array $attributes
* @return string
*/
private function getModelKey($model, $attributes)
{
if (count($attributes) > 1) {
$key = [];
foreach ($attributes as $attribute) {
$key[] = $model[$attribute];
}
return serialize($key);
} else {
$attribute = reset($attributes);
return $model[$attribute];
}
}
/**
* @param array $models
*/
private function filterByModels($models)
{
$attributes = array_keys($this->link);
$values = [];
if (count($attributes) === 1) {
// single key
$attribute = reset($this->link);
foreach ($models as $model) {
if (($value = $model[$attribute]) !== null) {
$values[] = $value;
}
}
} else {
// composite keys
foreach ($models as $model) {
$v = [];
foreach ($this->link as $attribute => $link) {
$v[$attribute] = $model[$link];
}
$values[] = $v;
}
}
$this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);
}
/**
* @param ActiveRecord[] $primaryModels
* @return array
*/
private function findPivotRows($primaryModels)
{
if (empty($primaryModels)) {
return [];
}
$this->filterByModels($primaryModels);
/** @var $primaryModel ActiveRecord */
$primaryModel = reset($primaryModels);
$db = $primaryModel->getDb();
list ($sql, $params) = $db->getQueryBuilder()->build($this);
return $db->createCommand($sql, $params)->queryAll();
}
} }

29
framework/yii/db/ActiveRelationInterface.php

@ -0,0 +1,29 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* ActiveRelationInterface defines the common interface to be implemented by active record relation classes.
*
* A class implementing this interface should also use [[ActiveRelationTrait]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
interface ActiveRelationInterface extends ActiveQueryInterface
{
/**
* Specifies the relation associated with the pivot table.
* @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return static the relation object itself.
*/
public function via($relationName, $callable = null);
}

246
framework/yii/db/ActiveRelationTrait.php

@ -0,0 +1,246 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
use yii\base\InvalidConfigException;
/**
* ActiveRelationTrait implements the common methods and properties for active record relation classes.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
trait ActiveRelationTrait
{
/**
* @var boolean whether this relation should populate all query results into AR instances.
* If false, only the first row of the results will be retrieved.
*/
public $multiple;
/**
* @var ActiveRecord the primary model that this relation is associated with.
* This is used only in lazy loading with dynamic query options.
*/
public $primaryModel;
/**
* @var array the columns of the primary and foreign tables that establish the relation.
* The array keys must be columns of the table for this relation, and the array values
* must be the corresponding columns from the primary table.
* Do not prefix or quote the column names as this will be done automatically by Yii.
*/
public $link;
/**
* @var array the query associated with the pivot table. Please call [[via()]]
* to set this property instead of directly setting it.
*/
public $via;
/**
* Clones internal objects.
*/
public function __clone()
{
// make a clone of "via" object so that the same query object can be reused multiple times
if (is_object($this->via)) {
$this->via = clone $this->via;
} elseif (is_array($this->via)) {
$this->via = [$this->via[0], clone $this->via[1]];
}
}
/**
* Specifies the relation associated with the pivot table.
* @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return static the relation object itself.
*/
public function via($relationName, $callable = null)
{
$relation = $this->primaryModel->getRelation($relationName);
$this->via = [$relationName, $relation];
if ($callable !== null) {
call_user_func($callable, $relation);
}
return $this;
}
/**
* Finds the related records and populates them into the primary models.
* This method is internally used by [[ActiveQuery]]. Do not call it directly.
* @param string $name the relation name
* @param array $primaryModels primary models
* @return array the related models
* @throws InvalidConfigException
*/
public function findWith($name, &$primaryModels)
{
if (!is_array($this->link)) {
throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
}
if ($this->via instanceof self) {
// via pivot table
/** @var ActiveRelationTrait $viaQuery */
$viaQuery = $this->via;
$viaModels = $viaQuery->findPivotRows($primaryModels);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveRelationTrait $viaQuery */
list($viaName, $viaQuery) = $this->via;
$viaQuery->primaryModel = null;
$viaModels = $viaQuery->findWith($viaName, $primaryModels);
$this->filterByModels($viaModels);
} else {
$this->filterByModels($primaryModels);
}
if (count($primaryModels) === 1 && !$this->multiple) {
$model = $this->one();
foreach ($primaryModels as $i => $primaryModel) {
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $model);
} else {
$primaryModels[$i][$name] = $model;
}
}
return [$model];
} else {
$models = $this->all();
if (isset($viaModels, $viaQuery)) {
$buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
} else {
$buckets = $this->buildBuckets($models, $this->link);
}
$link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
foreach ($primaryModels as $i => $primaryModel) {
$key = $this->getModelKey($primaryModel, $link);
$value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null);
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $value);
} else {
$primaryModels[$i][$name] = $value;
}
}
return $models;
}
}
/**
* @param array $models
* @param array $link
* @param array $viaModels
* @param array $viaLink
* @return array
*/
private function buildBuckets($models, $link, $viaModels = null, $viaLink = null)
{
$buckets = [];
$linkKeys = array_keys($link);
foreach ($models as $i => $model) {
$key = $this->getModelKey($model, $linkKeys);
if ($this->indexBy !== null) {
$buckets[$key][$i] = $model;
} else {
$buckets[$key][] = $model;
}
}
if ($viaModels !== null) {
$viaBuckets = [];
$viaLinkKeys = array_keys($viaLink);
$linkValues = array_values($link);
foreach ($viaModels as $viaModel) {
$key1 = $this->getModelKey($viaModel, $viaLinkKeys);
$key2 = $this->getModelKey($viaModel, $linkValues);
if (isset($buckets[$key2])) {
foreach ($buckets[$key2] as $i => $bucket) {
if ($this->indexBy !== null) {
$viaBuckets[$key1][$i] = $bucket;
} else {
$viaBuckets[$key1][] = $bucket;
}
}
}
}
$buckets = $viaBuckets;
}
if (!$this->multiple) {
foreach ($buckets as $i => $bucket) {
$buckets[$i] = reset($bucket);
}
}
return $buckets;
}
/**
* @param ActiveRecord|array $model
* @param array $attributes
* @return string
*/
private function getModelKey($model, $attributes)
{
if (count($attributes) > 1) {
$key = [];
foreach ($attributes as $attribute) {
$key[] = $model[$attribute];
}
return serialize($key);
} else {
$attribute = reset($attributes);
return $model[$attribute];
}
}
/**
* @param array $models
*/
private function filterByModels($models)
{
$attributes = array_keys($this->link);
$values = [];
if (count($attributes) === 1) {
// single key
$attribute = reset($this->link);
foreach ($models as $model) {
if (($value = $model[$attribute]) !== null) {
$values[] = $value;
}
}
} else {
// composite keys
foreach ($models as $model) {
$v = [];
foreach ($this->link as $attribute => $link) {
$v[$attribute] = $model[$link];
}
$values[] = $v;
}
}
$this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);
}
/**
* @param ActiveRecord[] $primaryModels
* @return array
*/
private function findPivotRows($primaryModels)
{
if (empty($primaryModels)) {
return [];
}
$this->filterByModels($primaryModels);
/** @var ActiveRecord $primaryModel */
$primaryModel = reset($primaryModels);
return $this->asArray()->all($primaryModel->getDb());
}
}

6
framework/yii/db/Command.php

@ -260,7 +260,7 @@ class Command extends \yii\base\Component
$rawSql = $this->getRawSql(); $rawSql = $this->getRawSql();
Yii::trace($rawSql, __METHOD__); Yii::info($rawSql, __METHOD__);
if ($sql == '') { if ($sql == '') {
return 0; return 0;
@ -364,9 +364,9 @@ class Command extends \yii\base\Component
$db = $this->db; $db = $this->db;
$rawSql = $this->getRawSql(); $rawSql = $this->getRawSql();
Yii::trace($rawSql, __METHOD__); Yii::info($rawSql, __METHOD__);
/** @var $cache \yii\caching\Cache */ /** @var \yii\caching\Cache $cache */
if ($db->enableQueryCache && $method !== '') { if ($db->enableQueryCache && $method !== '') {
$cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache; $cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache;
} }

142
framework/yii/db/Query.php

@ -33,20 +33,12 @@ use yii\base\Component;
* ~~~ * ~~~
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class Query extends Component class Query extends Component implements QueryInterface
{ {
/** use QueryTrait;
* Sort ascending
* @see orderBy
*/
const SORT_ASC = false;
/**
* Sort descending
* @see orderBy
*/
const SORT_DESC = true;
/** /**
* @var array the columns being selected. For example, `['id', 'name']`. * @var array the columns being selected. For example, `['id', 'name']`.
@ -71,28 +63,6 @@ class Query extends Component
*/ */
public $from; public $from;
/** /**
* @var string|array query condition. This refers to the WHERE clause in a SQL statement.
* For example, `age > 31 AND team = 1`.
* @see where()
*/
public $where;
/**
* @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
*/
public $limit;
/**
* @var integer zero-based offset from where the records are to be returned. If not set or
* less than 0, it means starting from the beginning.
*/
public $offset;
/**
* @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
* The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
* can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects.
* If that is the case, the expressions will be converted into strings without any change.
*/
public $orderBy;
/**
* @var array how to group the query results. For example, `['company', 'department']`. * @var array how to group the query results. For example, `['company', 'department']`.
* This is used to construct the GROUP BY clause in a SQL statement. * This is used to construct the GROUP BY clause in a SQL statement.
*/ */
@ -130,12 +100,6 @@ class Query extends Component
* For example, `[':name' => 'Dan', ':age' => 31]`. * For example, `[':name' => 'Dan', ':age' => 31]`.
*/ */
public $params; public $params;
/**
* @var string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row data. For more details, see [[indexBy()]]. This property is only used by [[all()]].
*/
public $indexBy;
/** /**
@ -154,27 +118,6 @@ class Query extends Component
} }
/** /**
* Sets the [[indexBy]] property.
* @param string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row data. The signature of the callable should be:
*
* ~~~
* function ($row)
* {
* // return the index value corresponding to $row
* }
* ~~~
*
* @return static the query object itself
*/
public function indexBy($column)
{
$this->indexBy = $column;
return $this;
}
/**
* Executes the query and returns all results as an array. * Executes the query and returns all results as an array.
* @param Connection $db the database connection used to generate the SQL statement. * @param Connection $db the database connection used to generate the SQL statement.
* If this parameter is not given, the `db` application component will be used. * If this parameter is not given, the `db` application component will be used.
@ -239,7 +182,7 @@ class Query extends Component
* @param string $q the COUNT expression. Defaults to '*'. * @param string $q the COUNT expression. Defaults to '*'.
* Make sure you properly quote column names in the expression. * Make sure you properly quote column names in the expression.
* @param Connection $db the database connection used to generate the SQL statement. * @param Connection $db the database connection used to generate the SQL statement.
* If this parameter is not given, the `db` application component will be used. * If this parameter is not given (or null), the `db` application component will be used.
* @return integer number of records * @return integer number of records
*/ */
public function count($q = '*', $db = null) public function count($q = '*', $db = null)
@ -653,83 +596,6 @@ class Query extends Component
} }
/** /**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return static the query object itself
* @see addOrderBy()
*/
public function orderBy($columns)
{
$this->orderBy = $this->normalizeOrderBy($columns);
return $this;
}
/**
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `['id' => Query::SORT_ASC, 'name' => Query::SORT_DESC]`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return static the query object itself
* @see orderBy()
*/
public function addOrderBy($columns)
{
$columns = $this->normalizeOrderBy($columns);
if ($this->orderBy === null) {
$this->orderBy = $columns;
} else {
$this->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
protected function normalizeOrderBy($columns)
{
if (is_array($columns)) {
return $columns;
} else {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
$result = [];
foreach ($columns as $column) {
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? self::SORT_ASC : self::SORT_DESC;
} else {
$result[$column] = self::SORT_ASC;
}
}
return $result;
}
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit. Use null or negative value to disable limit.
* @return static the query object itself
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset. Use null or negative value to disable offset.
* @return static the query object itself
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* Appends a SQL statement using UNION operator. * Appends a SQL statement using UNION operator.
* @param string|Query $sql the SQL statement to be appended using UNION * @param string|Query $sql the SQL statement to be appended using UNION
* @return static the query object itself * @return static the query object itself

2
framework/yii/db/QueryBuilder.php

@ -683,7 +683,7 @@ class QueryBuilder extends \yii\base\Object
if (is_object($direction)) { if (is_object($direction)) {
$orders[] = (string)$direction; $orders[] = (string)$direction;
} else { } else {
$orders[] = $this->db->quoteColumnName($name) . ($direction === Query::SORT_DESC ? ' DESC' : ''); $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
} }
} }

206
framework/yii/db/QueryInterface.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\db;
/**
* The QueryInterface defines the minimum set of methods to be implemented by a database query.
*
* The default implementation of this interface is provided by [[QueryTrait]].
*
* It has support for getting [[one]] instance or [[all]].
* Allows pagination via [[limit]] and [[offset]].
* Sorting is supported via [[orderBy]] and items can be limited to match some conditions using [[where]].
*
* By calling [[createCommand()]], we can get a [[Command]] instance which can be further
* used to perform/execute the DB query against a database.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
interface QueryInterface
{
/**
* Executes the query and returns all results as an array.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return array the query results. If the query results in nothing, an empty array will be returned.
*/
public function all($db = null);
/**
* Executes the query and returns a single row of result.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
* results in nothing.
*/
public function one($db = null);
/**
* Returns the number of records.
* @param string $q the COUNT expression. Defaults to '*'.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return integer number of records
*/
public function count($q = '*', $db = null);
/**
* Returns a value indicating whether the query result contains any row of data.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return boolean whether the query result contains any row of data.
*/
public function exists($db = null);
/**
* Sets the [[indexBy]] property.
* @param string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row data. The signature of the callable should be:
*
* ~~~
* function ($row)
* {
* // return the index value corresponding to $row
* }
* ~~~
*
* @return static the query object itself
*/
public function indexBy($column);
/**
* Sets the WHERE part of the query.
*
* The method requires a $condition parameter.
*
* The $condition parameter should be an array in one of the following two formats:
*
* - hash format: `['column1' => value1, 'column2' => value2, ...]`
* - operator format: `[operator, operand1, operand2, ...]`
*
* A condition in hash format represents the following SQL expression in general:
* `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
* an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
* in the generated expression. Below are some examples:
*
* - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`.
* - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`.
* - `['status' => null] generates `status IS NULL`.
*
* A condition in operator format generates the SQL expression according to the specified operator, which
* can be one of the followings:
*
* - `and`: the operands should be concatenated together using `AND`. For example,
* `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array,
* it will be converted into a string using the rules described here. For example,
* `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`.
* The method will NOT do any quoting or escaping.
*
* - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
*
* - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
* starting and ending values of the range that the column is in.
* For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`.
*
* - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
* in the generated condition.
*
* - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
* the range of the values that the column or DB expression should be in. For example,
* `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`.
* The method will properly quote the column name and escape values in the range.
*
* - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
*
* - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
* the values that the column or DB expression should be like.
* For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`.
* When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
* using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate
* `name LIKE '%test%' AND name LIKE '%sample%'`.
* The method will properly quote the column name and escape values in the range.
*
* - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* predicates when operand 2 is an array.
*
* - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
* in the generated condition.
*
* - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
* the `NOT LIKE` predicates.
*
* @param array $condition the conditions that should be put in the WHERE part.
* @return static the query object itself
* @see andWhere()
* @see orWhere()
*/
public function where($condition);
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return static the query object itself
* @see where()
* @see orWhere()
*/
public function andWhere($condition);
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return static the query object itself
* @see where()
* @see andWhere()
*/
public function orWhere($condition);
/**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return static the query object itself
* @see addOrderBy()
*/
public function orderBy($columns);
/**
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return static the query object itself
* @see orderBy()
*/
public function addOrderBy($columns);
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit. Use null or negative value to disable limit.
* @return static the query object itself
*/
public function limit($limit);
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset. Use null or negative value to disable offset.
* @return static the query object itself
*/
public function offset($offset);
}

208
framework/yii/db/QueryTrait.php

@ -0,0 +1,208 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* The BaseQuery trait represents the minimum method set of a database Query.
*
* It has support for getting [[one]] instance or [[all]].
* Allows pagination via [[limit]] and [[offset]].
* Sorting is supported via [[orderBy]] and items can be limited to match some conditions unsing [[where]].
*
* By calling [[createCommand()]], we can get a [[Command]] instance which can be further
* used to perform/execute the DB query against a database.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
trait QueryTrait
{
/**
* @var string|array query condition. This refers to the WHERE clause in a SQL statement.
* For example, `age > 31 AND team = 1`.
* @see where()
*/
public $where;
/**
* @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
*/
public $limit;
/**
* @var integer zero-based offset from where the records are to be returned. If not set or
* less than 0, it means starting from the beginning.
*/
public $offset;
/**
* @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
* The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
* can be either [SORT_ASC](http://php.net/manual/en/array.constants.php#constant.sort-asc)
* or [SORT_DESC](http://php.net/manual/en/array.constants.php#constant.sort-desc).
* The array may also contain [[Expression]] objects. If that is the case, the expressions
* will be converted into strings without any change.
*/
public $orderBy;
/**
* @var string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row data. For more details, see [[indexBy()]]. This property is only used by [[all()]].
*/
public $indexBy;
/**
* Sets the [[indexBy]] property.
* @param string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row data. The signature of the callable should be:
*
* ~~~
* function ($row)
* {
* // return the index value corresponding to $row
* }
* ~~~
*
* @return static the query object itself
*/
public function indexBy($column)
{
$this->indexBy = $column;
return $this;
}
/**
* Sets the WHERE part of the query.
*
* See [[QueryInterface::where()]] for detailed documentation.
*
* @param array $condition the conditions that should be put in the WHERE part.
* @return static the query object itself
* @see andWhere()
* @see orWhere()
*/
public function where($condition)
{
$this->where = $condition;
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return static the query object itself
* @see where()
* @see orWhere()
*/
public function andWhere($condition)
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = ['and', $this->where, $condition];
}
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return static the query object itself
* @see where()
* @see andWhere()
*/
public function orWhere($condition)
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = ['or', $this->where, $condition];
}
return $this;
}
/**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return static the query object itself
* @see addOrderBy()
*/
public function orderBy($columns)
{
$this->orderBy = $this->normalizeOrderBy($columns);
return $this;
}
/**
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return static the query object itself
* @see orderBy()
*/
public function addOrderBy($columns)
{
$columns = $this->normalizeOrderBy($columns);
if ($this->orderBy === null) {
$this->orderBy = $columns;
} else {
$this->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
protected function normalizeOrderBy($columns)
{
if (is_array($columns)) {
return $columns;
} else {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
$result = [];
foreach ($columns as $column) {
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
} else {
$result[$column] = SORT_ASC;
}
}
return $result;
}
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit. Use null or negative value to disable limit.
* @return static the query object itself
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset. Use null or negative value to disable offset.
* @return static the query object itself
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
}

12
framework/yii/db/Schema.php

@ -92,14 +92,16 @@ abstract class Schema extends Object
$realName = $this->getRawTableName($name); $realName = $this->getRawTableName($name);
if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) { if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
/** @var $cache Cache */ /** @var Cache $cache */
$cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache; $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
if ($cache instanceof Cache) { if ($cache instanceof Cache) {
$key = $this->getCacheKey($name); $key = $this->getCacheKey($name);
if ($refresh || ($table = $cache->get($key)) === false) { if ($refresh || ($table = $cache->get($key)) === false) {
$table = $this->loadTableSchema($realName); $table = $this->loadTableSchema($realName);
if ($table !== null) { if ($table !== null) {
$cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency($this->getCacheGroup())); $cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency([
'group' => $this->getCacheGroup(),
]));
} }
} }
return $this->_tables[$name] = $table; return $this->_tables[$name] = $table;
@ -213,7 +215,7 @@ abstract class Schema extends Object
*/ */
public function refresh() public function refresh()
{ {
/** @var $cache Cache */ /** @var Cache $cache */
$cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache; $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
if ($this->db->enableSchemaCache && $cache instanceof Cache) { if ($this->db->enableSchemaCache && $cache instanceof Cache) {
GroupDependency::invalidate($cache, $this->getCacheGroup()); GroupDependency::invalidate($cache, $this->getCacheGroup());
@ -289,7 +291,7 @@ abstract class Schema extends Object
* then this method will do nothing. * then this method will do nothing.
* @param string $name table name * @param string $name table name
* @return string the properly quoted table name * @return string the properly quoted table name
* @see quoteSimpleTableName * @see quoteSimpleTableName()
*/ */
public function quoteTableName($name) public function quoteTableName($name)
{ {
@ -314,7 +316,7 @@ abstract class Schema extends Object
* then this method will do nothing. * then this method will do nothing.
* @param string $name column name * @param string $name column name
* @return string the properly quoted column name * @return string the properly quoted column name
* @see quoteSimpleColumnName * @see quoteSimpleColumnName()
*/ */
public function quoteColumnName($name) public function quoteColumnName($name)
{ {

2
framework/yii/grid/ActionColumn.php

@ -12,6 +12,8 @@ use Closure;
use yii\helpers\Html; use yii\helpers\Html;
/** /**
* ActionColumn is a column for the [[GridView]] widget that displays buttons for viewing and manipulating the items.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

4
framework/yii/grid/DataColumn.php

@ -15,6 +15,10 @@ use yii\helpers\Html;
use yii\helpers\Inflector; use yii\helpers\Inflector;
/** /**
* DataColumn is the default column type for the [[GridView]] widget.
*
* It is used to show data columns and allows sorting them.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

15
framework/yii/grid/GridView.php

@ -16,6 +16,10 @@ use yii\helpers\Json;
use yii\widgets\BaseListView; use yii\widgets\BaseListView;
/** /**
* The GridView widget is used to display data in a grid.
*
* It provides features like sorting, paging and also filtering the data.
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
@ -89,10 +93,9 @@ class GridView extends BaseListView
*/ */
public $showFooter = false; public $showFooter = false;
/** /**
* @var string|boolean the HTML content to be displayed when [[dataProvider]] does not have any data. * @var boolean whether to show the grid view if [[dataProvider]] returns no data.
* If false, the grid view will still be displayed (without body content though).
*/ */
public $empty = false; public $showOnEmpty = true;
/** /**
* @var array|Formatter the formatter used to format model attribute values into displayable texts. * @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]] * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
@ -342,8 +345,14 @@ class GridView extends BaseListView
} }
} }
} }
if (empty($rows)) {
$colspan = count($this->columns);
return "<tbody>\n<tr><td colspan=\"$colspan\">" . $this->renderEmpty() . "</td></tr>\n</tbody>";
} else {
return "<tbody>\n" . implode("\n", $rows) . "\n</tbody>"; return "<tbody>\n" . implode("\n", $rows) . "\n</tbody>";
} }
}
/** /**
* Renders a table row with the given data model and key. * Renders a table row with the given data model and key.

1
framework/yii/grid/GridViewAsset.php

@ -10,6 +10,7 @@ namespace yii\grid;
use yii\web\AssetBundle; use yii\web\AssetBundle;
/** /**
* This asset bundle provides the javascript files for the [[GridView]] widget.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0

26
framework/yii/helpers/BaseHtml.php

@ -86,7 +86,7 @@ class BaseHtml
* @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false,
* HTML entities in `$content` will not be further encoded. * HTML entities in `$content` will not be further encoded.
* @return string the encoded content * @return string the encoded content
* @see decode * @see decode()
* @see http://www.php.net/manual/en/function.htmlspecialchars.php * @see http://www.php.net/manual/en/function.htmlspecialchars.php
*/ */
public static function encode($content, $doubleEncode = true) public static function encode($content, $doubleEncode = true)
@ -99,7 +99,7 @@ class BaseHtml
* This is the opposite of [[encode()]]. * This is the opposite of [[encode()]].
* @param string $content the content to be decoded * @param string $content the content to be decoded
* @return string the decoded content * @return string the decoded content
* @see encode * @see encode()
* @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
*/ */
public static function decode($content) public static function decode($content)
@ -116,8 +116,8 @@ class BaseHtml
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered. * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated HTML tag * @return string the generated HTML tag
* @see beginTag * @see beginTag()
* @see endTag * @see endTag()
*/ */
public static function tag($name, $content = '', $options = []) public static function tag($name, $content = '', $options = [])
{ {
@ -132,8 +132,8 @@ class BaseHtml
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered. * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated start tag * @return string the generated start tag
* @see endTag * @see endTag()
* @see tag * @see tag()
*/ */
public static function beginTag($name, $options = []) public static function beginTag($name, $options = [])
{ {
@ -144,8 +144,8 @@ class BaseHtml
* Generates an end tag. * Generates an end tag.
* @param string $name the tag name * @param string $name the tag name
* @return string the generated end tag * @return string the generated end tag
* @see beginTag * @see beginTag()
* @see tag * @see tag()
*/ */
public static function endTag($name) public static function endTag($name)
{ {
@ -187,7 +187,7 @@ class BaseHtml
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered. * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated link tag * @return string the generated link tag
* @see url * @see url()
*/ */
public static function cssFile($url, $options = []) public static function cssFile($url, $options = [])
{ {
@ -203,7 +203,7 @@ class BaseHtml
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered. * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated script tag * @return string the generated script tag
* @see url * @see url()
*/ */
public static function jsFile($url, $options = []) public static function jsFile($url, $options = [])
{ {
@ -222,7 +222,7 @@ class BaseHtml
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered. * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated form start tag. * @return string the generated form start tag.
* @see endForm * @see endForm()
*/ */
public static function beginForm($action = '', $method = 'post', $options = []) public static function beginForm($action = '', $method = 'post', $options = [])
{ {
@ -271,7 +271,7 @@ class BaseHtml
/** /**
* Generates a form end tag. * Generates a form end tag.
* @return string the generated tag * @return string the generated tag
* @see beginForm * @see beginForm()
*/ */
public static function endForm() public static function endForm()
{ {
@ -290,7 +290,7 @@ class BaseHtml
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered. * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated hyperlink * @return string the generated hyperlink
* @see url * @see url()
*/ */
public static function a($text, $url = null, $options = []) public static function a($text, $url = null, $options = [])
{ {

2
framework/yii/helpers/BaseInflector.php

@ -328,7 +328,7 @@ class BaseInflector
* Converts a word like "send_email" to "SendEmail". It * Converts a word like "send_email" to "SendEmail". It
* will remove non alphanumeric character from the word, so * will remove non alphanumeric character from the word, so
* "who's online" will be converted to "WhoSOnline" * "who's online" will be converted to "WhoSOnline"
* @see variablize * @see variablize()
* @param string $word the word to CamelCase * @param string $word the word to CamelCase
* @return string * @return string
*/ */

8
framework/yii/helpers/BaseSecurity.php

@ -175,7 +175,7 @@ class BaseSecurity
/** /**
* Returns a secret key associated with the specified name. * Returns a secret key associated with the specified name.
* If the secret key does not exist, a random key will be generated * If the secret key does not exist, a random key will be generated
* and saved in the file "keys.php" under the application's runtime directory * and saved in the file "keys.data" under the application's runtime directory
* so that the same secret key can be returned in future requests. * so that the same secret key can be returned in future requests.
* @param string $name the name that is associated with the secret key * @param string $name the name that is associated with the secret key
* @param integer $length the length of the key that should be generated if not exists * @param integer $length the length of the key that should be generated if not exists
@ -184,16 +184,16 @@ class BaseSecurity
public static function getSecretKey($name, $length = 32) public static function getSecretKey($name, $length = 32)
{ {
static $keys; static $keys;
$keyFile = Yii::$app->getRuntimePath() . '/keys.php'; $keyFile = Yii::$app->getRuntimePath() . '/keys.json';
if ($keys === null) { if ($keys === null) {
$keys = []; $keys = [];
if (is_file($keyFile)) { if (is_file($keyFile)) {
$keys = require($keyFile); $keys = json_decode(file_get_contents($keyFile), true);
} }
} }
if (!isset($keys[$name])) { if (!isset($keys[$name])) {
$keys[$name] = static::generateRandomKey($length); $keys[$name] = static::generateRandomKey($length);
file_put_contents($keyFile, "<?php\nreturn " . var_export($keys, true) . ";\n"); file_put_contents($keyFile, json_encode($keys));
} }
return $keys[$name]; return $keys[$name];
} }

9
framework/yii/i18n/Formatter.php

@ -19,6 +19,15 @@ use yii\base\InvalidConfigException;
* Formatter requires the PHP "intl" extension to be installed. Formatter supports localized * Formatter requires the PHP "intl" extension to be installed. Formatter supports localized
* formatting of date, time and numbers, based on the current [[locale]]. * formatting of date, time and numbers, based on the current [[locale]].
* *
* This Formatter can replace the `formatter` application component that is configured by default.
* To do so, add the following to your application config under `components`:
*
* ```php
* 'formatter' => [
* 'class' => 'yii\i18n\Formatter',
* ]
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */

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

Loading…
Cancel
Save