Commit 87e09805 by Tobias Munk

Merge branch 'master' into feature/gii-extension-generator

parents 097ee6f8 bbc0c568
......@@ -54,11 +54,11 @@ class SiteController extends Controller
public function actionLogin()
{
if (!\Yii::$app->user->isGuest) {
$this->goHome();
return $this->goHome();
}
$model = new LoginForm();
if ($model->load($_POST) && $model->login()) {
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
} else {
return $this->render('login', [
......
......@@ -3,7 +3,7 @@ use yii\helpers\Html;
/**
* @var yii\web\View $this
* @var common\models\User $user;
* @var common\models\User $user
*/
$resetLink = Yii::$app->urlManager->createAbsoluteUrl('site/reset-password', ['token' => $user->password_reset_token]);
......
<?php
namespace common\models;
use yii\base\Model;
use Yii;
use yii\base\Model;
/**
* Login form
......@@ -44,12 +44,13 @@ class LoginForm extends Model
/**
* Logs in a user using the provided username and password.
*
* @return boolean whether the user is logged in successfully
*/
public function login()
{
if ($this->validate()) {
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
} else {
return false;
}
......
......@@ -27,6 +27,12 @@ class User extends ActiveRecord implements IdentityInterface
const ROLE_USER = 10;
/**
* Creates a new user
*
* @param array $attributes the attributes given by field => value
* @return static|null the newly created model, or null on failure
*/
public static function create($attributes)
{
/** @var User $user */
......@@ -69,18 +75,18 @@ class User extends ActiveRecord implements IdentityInterface
* Finds user by username
*
* @param string $username
* @return self
* @return static|null
*/
public static function findByUsername($username)
{
return static::find(['username' => $username, 'status' => static::STATUS_ACTIVE]);
return static::find(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}
/**
* Finds user by password reset token
*
* @param string $token password reset token
* @return self
* @return static|null
*/
public static function findByPasswordResetToken($token)
{
......@@ -92,9 +98,9 @@ class User extends ActiveRecord implements IdentityInterface
return null;
}
return User::find([
return static::find([
'password_reset_token' => $token,
'status' => User::STATUS_ACTIVE,
'status' => self::STATUS_ACTIVE,
]);
}
......
......@@ -65,7 +65,7 @@ class SiteController extends Controller
public function actionLogin()
{
if (!\Yii::$app->user->isGuest) {
$this->goHome();
return $this->goHome();
}
$model = new LoginForm();
......@@ -86,8 +86,8 @@ class SiteController extends Controller
public function actionContact()
{
$model = new ContactForm;
if ($model->load($_POST) && $model->contact(Yii::$app->params['adminEmail'])) {
$model = new ContactForm();
if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
Yii::$app->session->setFlash('success', 'Thank you for contacting us. We will respond to you as soon as possible.');
return $this->refresh();
} else {
......@@ -144,7 +144,7 @@ class SiteController extends Controller
throw new BadRequestHttpException($e->getMessage());
}
if ($model->load($_POST) && $model->resetPassword()) {
if ($model->load(Yii::$app->request->post()) && $model->resetPassword()) {
Yii::$app->getSession()->setFlash('success', 'New password was saved.');
return $this->goHome();
}
......
......@@ -17,7 +17,7 @@ class ContactForm extends Model
public $verifyCode;
/**
* @return array the validation rules.
* @inheritdoc
*/
public function rules()
{
......@@ -32,7 +32,7 @@ class ContactForm extends Model
}
/**
* @return array customized attribute labels
* @inheritdoc
*/
public function attributeLabels()
{
......@@ -43,6 +43,7 @@ class ContactForm extends Model
/**
* Sends an email to the specified email address using the information collected by this model.
*
* @param string $email the target email address
* @return boolean whether the model passes validation
*/
......
......@@ -25,8 +25,9 @@ class PasswordResetRequestForm extends Model
}
/**
* Sends an email with a link, for resetting the password.
*
* @return boolean sends an email
* @return boolean whether the email was send
*/
public function sendEmail()
{
......@@ -52,4 +53,3 @@ class PasswordResetRequestForm extends Model
return false;
}
}
\ No newline at end of file
......@@ -19,7 +19,7 @@ class ResetPasswordForm extends Model
private $_user;
/**
* Creates a form model given a token
* Creates a form model given a token.
*
* @param string $token
* @param array $config name-value pairs that will be used to initialize the object properties
......@@ -38,7 +38,7 @@ class ResetPasswordForm extends Model
}
/**
* @return array the validation rules.
* @inheritdoc
*/
public function rules()
{
......@@ -50,6 +50,7 @@ class ResetPasswordForm extends Model
/**
* Resets password.
*
* @return boolean if password was reset.
*/
public function resetPassword()
......@@ -60,4 +61,3 @@ class ResetPasswordForm extends Model
return $user->save();
}
}
\ No newline at end of file
......@@ -36,7 +36,8 @@ class SignupForm extends Model
/**
* Signs user up.
* @return User saved model
*
* @return User|null the saved model or null if saving fails
*/
public function signup()
{
......@@ -46,4 +47,3 @@ class SignupForm extends Model
return null;
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@ Yii 2 Basic Application Template
================================
Yii 2 Basic Application Template is a skeleton Yii 2 application best for
rapidly developing small Websites containing mainly informational pages.
rapidly creating small projects.
The template contains the basic features including user login/logout and a contact page.
It includes all commonly used configurations that would allow you to focus on adding new
......
......@@ -55,11 +55,11 @@ class SiteController extends Controller
public function actionLogin()
{
if (!\Yii::$app->user->isGuest) {
$this->goHome();
return $this->goHome();
}
$model = new LoginForm();
if ($model->load($_POST) && $model->login()) {
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
} else {
return $this->render('login', [
......@@ -76,8 +76,8 @@ class SiteController extends Controller
public function actionContact()
{
$model = new ContactForm;
if ($model->load($_POST) && $model->contact(Yii::$app->params['adminEmail'])) {
$model = new ContactForm();
if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
Yii::$app->session->setFlash('contactFormSubmitted');
return $this->refresh();
} else {
......
......@@ -24,11 +24,20 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface
],
];
/**
* @inheritdoc
*/
public static function findIdentity($id)
{
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
}
/**
* Finds user by username
*
* @param string $username
* @return static|null
*/
public static function findByUsername($username)
{
foreach (self::$users as $user) {
......@@ -39,21 +48,36 @@ class User extends \yii\base\Object implements \yii\web\IdentityInterface
return null;
}
/**
* @inheritdoc
*/
public function getId()
{
return $this->id;
}
/**
* @inheritdoc
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* @inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* @param string $password password to validate
* @return bool if password provided is valid for current user
*/
public function validatePassword($password)
{
return $this->password === $password;
......
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'test');
// fcgi doesn't have STDIN and STDOUT defined by default
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
defined('STDOUT') or define('STDOUT', fopen('php://stdout', 'w'));
require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
......@@ -17,6 +17,6 @@ class ContactPage extends BasePage
$inputType = $field === 'body' ? 'textarea' : 'input';
$this->guy->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value);
}
$this->guy->click('Submit', '#contact-form');
$this->guy->click('contact-button');
}
}
......@@ -14,8 +14,8 @@ class LoginPage extends BasePage
*/
public function login($username, $password)
{
$this->guy->fillField('input[name="LoginForm[username]"]',$username);
$this->guy->fillField('input[name="LoginForm[password]"]',$password);
$this->guy->click('Login','#login-form');
$this->guy->fillField('input[name="LoginForm[username]"]', $username);
$this->guy->fillField('input[name="LoginForm[password]"]', $password);
$this->guy->click('login-button');
}
}
<?php
return yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../config/console.php'),
require(__DIR__ . '/../_config.php'),
[
'components' => [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=yii2_basic_acceptance',
],
],
]
);
\ No newline at end of file
#!/usr/bin/env php
<?php
/**
* Yii console bootstrap file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
require_once __DIR__ . '/../_console_bootstrap.php';
$config = require(__DIR__ . '/_console.php');
$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);
@echo off
rem -------------------------------------------------------------
rem Yii command line bootstrap script for Windows.
rem
rem @author Qiang Xue <qiang.xue@gmail.com>
rem @link http://www.yiiframework.com/
rem @copyright Copyright &copy; 2012 Yii Software LLC
rem @license http://www.yiiframework.com/license/
rem -------------------------------------------------------------
@setlocal
set YII_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
"%PHP_COMMAND%" "%YII_PATH%yii" %*
@endlocal
<?php
return yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../config/console.php'),
require(__DIR__ . '/../_config.php'),
[
'components' => [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=yii2_basic_functional',
],
],
]
);
\ No newline at end of file
#!/usr/bin/env php
<?php
/**
* Yii console bootstrap file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
require_once __DIR__ . '/../_console_bootstrap.php';
$config = require(__DIR__ . '/_console.php');
$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);
@echo off
rem -------------------------------------------------------------
rem Yii command line bootstrap script for Windows.
rem
rem @author Qiang Xue <qiang.xue@gmail.com>
rem @link http://www.yiiframework.com/
rem @copyright Copyright &copy; 2012 Yii Software LLC
rem @license http://www.yiiframework.com/license/
rem -------------------------------------------------------------
@setlocal
set YII_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
"%PHP_COMMAND%" "%YII_PATH%yii" %*
@endlocal
<?php
return yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/../../config/console.php'),
require(__DIR__ . '/../_config.php'),
[
'components' => [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=yii2_basic_unit',
],
],
]
);
\ No newline at end of file
......@@ -11,6 +11,12 @@ class LoginFormTest extends TestCase
use \Codeception\Specify;
protected function tearDown()
{
Yii::$app->user->logout();
parent::tearDown();
}
public function testLoginNoUser()
{
$model = $this->mockUser(null);
......
#!/usr/bin/env php
<?php
/**
* Yii console bootstrap file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
require_once __DIR__ . '/../_console_bootstrap.php';
$config = require(__DIR__ . '/_console.php');
$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);
@echo off
rem -------------------------------------------------------------
rem Yii command line bootstrap script for Windows.
rem
rem @author Qiang Xue <qiang.xue@gmail.com>
rem @link http://www.yiiframework.com/
rem @copyright Copyright &copy; 2012 Yii Software LLC
rem @license http://www.yiiframework.com/license/
rem -------------------------------------------------------------
@setlocal
set YII_PATH=%~dp0
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
"%PHP_COMMAND%" "%YII_PATH%yii" %*
@endlocal
......@@ -48,7 +48,7 @@ $this->params['breadcrumbs'][] = $this->title;
'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
]) ?>
<div class="form-group">
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary', 'name' => 'contact-button']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
......
......@@ -34,7 +34,7 @@ $this->params['breadcrumbs'][] = $this->title;
<div class="form-group">
<div class="col-lg-offset-1 col-lg-11">
<?= Html::submitButton('Login', ['class' => 'btn btn-primary']) ?>
<?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?>
</div>
</div>
......
......@@ -113,6 +113,25 @@ $customers = Customer::find()->indexBy('id')->all();
// $customers array is indexed by customer IDs
```
Batch query is also supported when working with Active Record. For example,
```php
// fetch 10 customers at a time
foreach (Customer::find()->batch(10) as $customers) {
// $customers is an array of 10 or fewer Customer objects
}
// fetch 10 customers at a time and iterate them one by one
foreach (Customer::find()->each(10) as $customer) {
// $customer is a Customer object
}
// batch query with eager loading
foreach (Customer::find()->with('orders')->each() as $customer) {
}
```
As explained in [Query Builder](query-builder.md), batch query is very useful when you are fetching
a large amount of data from database. It will keep your memory usage under a limit.
Accessing Column Data
---------------------
......
Authentication
==============
Authentication is the act of verifying who a user is, and is the basis of the login process. Typically, authentication uses an identifier--a username or email address--and password, submitted through a form. The application then compares this information against that previously stored.
Authentication is the act of verifying who a user is, and is the basis of the login process. Typically, authentication uses the combination of an identifier--a username or email address--and a password. The user submits these values through a form, and the application then compares the submitted information against that previously stored (e.g., upon registration).
In Yii all this is done semi-automatically, leaving the developer to merely implement [[\yii\web\IdentityInterface]]. Typically, implementation is accomplished using the `User` model. You can find a full featured example in the
[advanced application template](installation.md). Below only the interface methods are listed:
In Yii, this entire process is performed semi-automatically, leaving the developer to merely implement [[\yii\web\IdentityInterface]], the most important class in the authentication system. Typically, implementation of `IdentityInterface` is accomplished using the `User` model.
You can find a full featured example of authentication in the
[advanced application template](installation.md). Below, only the interface methods are listed:
```php
class User extends ActiveRecord implements IdentityInterface
......@@ -49,8 +51,8 @@ class User extends ActiveRecord implements IdentityInterface
}
```
Two of the outlined methods are simple: `findIdentity` is provided with an ID and returns a model instance represented by that ID. The `getId` method returns the ID itself.
Two of the other methods--`getAuthKey` and `validateAuthKey`--are used to provide extra security to the "remember me" cookie. `getAuthKey` should return a string that is unique for each user. A good idea is to save this value when the user is created by using Yii's `Security::generateRandomKey()`:
Two of the outlined methods are simple: `findIdentity` is provided with an ID value and returns a model instance associated with that ID. The `getId` method returns the ID itself.
Two of the other methods--`getAuthKey` and `validateAuthKey`--are used to provide extra security to the "remember me" cookie. The `getAuthKey` method should return a string that is unique for each user. You can create reliably create a unique string using `Security::generateRandomKey()`. It's a good idea to also save this as part of the user's record:
```php
public function beforeSave($insert)
......@@ -65,4 +67,4 @@ public function beforeSave($insert)
}
```
The `validateAuthKey` method just compares the `$authKey` variable, passed as parameter (itself retrieved from a cookie), with the value fetched from database.
The `validateAuthKey` method just needs to compare the `$authKey` variable, passed as parameter (itself retrieved from a cookie), with the value fetched from database.
......@@ -210,7 +210,7 @@ $command->execute();
Transactions
------------
If the underlying DBMS supports transactions, you can perform transactional SQL queries like the following:
You can perform transactional SQL queries like the following:
```php
$transaction = $connection->beginTransaction();
......@@ -220,10 +220,34 @@ try {
// ... executing other SQL statements ...
$transaction->commit();
} catch(Exception $e) {
$transaction->rollback();
$transaction->rollBack();
}
```
You can also nest multiple transactions, if needed:
```php
// outer transaction
$transaction1 = $connection->beginTransaction();
try {
$connection->createCommand($sql1)->execute();
// inner transaction
$transaction2 = $connection->beginTransaction();
try {
$connection->createCommand($sql2)->execute();
$transaction2->commit();
} catch (Exception $e) {
$transaction2->rollBack();
}
$transaction1->commit();
} catch (Exception $e) {
$transaction1->rollBack();
}
```
Working with database schema
----------------------------
......
Error Handling
==============
Error handling in Yii is different than handling errors in plain PHP. First of all, Yii will convert all non-fatal errors to *exceptions*. By doing so, you can gracefully handle them using `try`-`catch`. Second, even fatal errors in Yii are rendered in a nice way. This means that in debugging mode, you can trace the causes of fatal errors in order to more quickly identify the cause of the problem.
Error handling in Yii is different than handling errors in plain PHP. First of all, Yii will convert all non-fatal errors
to *exceptions*. By doing so, you can gracefully handle them using `try`-`catch`. Second, even fatal errors in Yii are
rendered in a nice way. This means that in debugging mode, you can trace the causes of fatal errors in order to more quickly
identify the cause of the problem.
Rendering errors in a dedicated controller action
-------------------------------------------------
The default Yii error page is great when developing a site, and is acceptable for production sites if `YII_DEBUG` is turned off in your bootstrap index.php file. But but you may want to customize the default error page to make it more suitable for your project.
The default Yii error page is great when developing a site, and is acceptable for production sites if `YII_DEBUG`
is turned off in your bootstrap index.php file. But but you may want to customize the default error page to make it
more suitable for your project.
The easiest way to create a custom error page it is to use a dedicated controller action for error rendering. First, you'll need to configure the `errorHandler` component in the application's configuration:
The easiest way to create a custom error page it is to use a dedicated controller action for error rendering. First,
you'll need to configure the `errorHandler` component in the application's configuration:
```php
return [
......@@ -20,7 +26,8 @@ return [
],
```
With that configuration in place, whenever an error occurs, Yii will execute the "error" action of the "Site" controller. That action should look for an exception and, if present, render the proper view file, passing along the exception:
With that configuration in place, whenever an error occurs, Yii will execute the "error" action of the "Site" controller.
That action should look for an exception and, if present, render the proper view file, passing along the exception:
```php
public function actionError()
......@@ -31,22 +38,23 @@ public function actionError()
}
```
Next, you would create the `views/site/error.php` file, which would make use of the exception. The exception object has the following properties:
Next, you would create the `views/site/error.php` file, which would make use of the exception. The exception object has
the following properties:
* `code`: the HTTP status code (e.g. 403, 500)
* `type`: the error type (e.g. CHttpException, PHP Error)
* `message`: the error message
* `file`: the name of the PHP script file where the error occurs
* `line`: the line number of the code where the error occurs
* `trace`: the call stack of the error
* `source`: the context source code where the error occurs
[[Need to confirm the above for Yii 2.]]
- `statusCode`: the HTTP status code (e.g. 403, 500). Available for HTTP exceptions only.
- `code`: the code of the exception.
- `type`: the error type (e.g. HttpException, PHP Error).
- `message`: the error message.
- `file`: the name of the PHP script file where the error occurs.
- `line`: the line number of the code where the error occurs.
- `trace`: the call stack of the error.
- `source`: the context source code where the error occurs.
Rendering errors without a dedicated controller action
------------------------------------------------------
Instead of creating a dedicated action within the Site controller, you could just indicate to Yii what class should be used to handle errors:
Instead of creating a dedicated action within the Site controller, you could just indicate to Yii what class should
be used to handle errors:
```php
public function actions()
......@@ -59,7 +67,8 @@ public function actions()
}
```
After associating the class with the error as in the above, define the `views/site/error.php` file, which will automatically be used. The view will be passed three variables:
After associating the class with the error as in the above, define the `views/site/error.php` file, which will
automatically be used. The view will be passed three variables:
- `$name`: the error name
- `$message`: the error message
......
......@@ -39,7 +39,8 @@ $component->on($eventName, function($event) {
});
```
As shown in the anonymous function example, the event handling function must be defined so that it takes one argument. This will be an [[Event]] object.
As shown in the anonymous function example, the event handling function must be defined so that it takes one argument.
This will be an [[\yii\base\Event]] object.
Removing Event Handlers
......
......@@ -127,3 +127,65 @@ div.required label:after {
color: red;
}
```
Handling multiple models with a single form
-------------------------------------------
Sometimes you need to handle multiple models of the same kind in a single form. For example, multiple settings where
each setting is stored as name-value and is represented by `Setting` model. The
following shows how to implement it with Yii.
Let's start with controller action:
```php
namespace app\controllers;
use Yii;
use yii\base\Model;
use yii\web\Controller;
use app\models\Setting;
class SettingsController extends Controller
{
// ...
public function actionUpdate()
{
$settings = Setting::find()->indexBy('id')->all();
if (Model::loadMultiple($settings, Yii::$app->request->post()) && Model::validateMultiple($settings)) {
foreach ($settings as $setting) {
$setting->save(false);
}
return $this->redirect('index');
}
return $this->render('update', ['settings' => $settings]);
}
}
```
In the code above we're using `indexBy` when retrieving models from database to make array indexed by model ids. These
will be later used to identify form fields. `loadMultiple` fills multiple modelds with the form data coming from POST
and `validateMultiple` validates all models at once. In order to skip validation when saving we're passing `false` as
a parameter to `save`.
Now the form that's in `update` view:
```php
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
$form = ActiveForm::begin();
foreach ($settings as $index => $setting) {
echo Html::encode($setting->name) . ': ' . $form->field($setting, "[$index]value");
}
ActiveForm::end();
```
Here for each setting we are rendering name and an input with a value. It is important to add a proper index
to input name since that is how `loadMultiple` determines which model to fill with which values.
The Gii code generation tool
============================
Yii2 includes a handy tool that allows rapid prototyping by generating commonly used code snippets
Yii includes a handy tool, named Gii, that provides rapid prototyping by generating commonly used code snippets
as well as complete CRUD controllers.
Installing and configuring
--------------------------
Gii comes as an offical extension and the preferred way to install this extension is through
Gii is an offical Yii extension. The preferred way to install this extension is through
[composer](http://getcomposer.org/download/).
Either run
You can either run this command:
```
php composer.phar require --prefer-dist yiisoft/yii2-gii "*"
```
or add
Or you can add this code to the require section of your `composer.json` file:
```
"yiisoft/yii2-gii": "*"
```
to the require section of your `composer.json` file.
Once the extension is installed, simply add these lines to your application configuration file:
Once the Gii extension has been installed, you enable it by adding these lines to your application configuration file:
```php
'modules' => [
......@@ -41,8 +39,7 @@ You can then access Gii through the following URL:
http://localhost/path/to/index.php?r=gii
```
> Note: if you are accessing gii from another IP than localhost, access will be denied by default.
You have to add allowed IPs to the configuration in this case:
> Note: if you are accessing gii from an IP address other than localhost, access will be denied by default. To circumvent that default, add the allowed IP addressess to the configuration:
>
```php
'gii' => [
......
Helper Classes
==============
Yii provides many helper classes to help simplify some common coding tasks, such as string/array manipulations,
HTML code generation. These helper classes are organized under the `yii\helpers` namespace and
are all static classes (meaning they contain only static properties and methods and should not be instantiated).
You use a helper class by directly calling its static method, like the following,
```php
use yii\helpers\ArrayHelper;
$c = ArrayHelper::merge($a, $b);
```
Extending Helper Classes
------------------------
Static classes are typically hard to customize because when you use them, you already hardcode the class names
in your code, and as a result your customized versions will not get used unless you do some global replacement in your code.
To solve this problem, Yii breaks each helper into two classes: one is base class (e.g. `BaseArrayHelper`)
and the other the concrete class (e.g. `ArrayHelper`). When you use a helper, you should only use the concrete version.
If you want to customize a helper, e.g., `ArrayHelper`, do the following steps:
1. Name your class the same as the concrete class provided by Yii, including the namespace part, e.g.,
`yii\helpers\ArrayHelper`;
2. Extend your class from the base class, e.g., `class ArrayHelper extends \yii\helpers\BaseArrayHelper`;
3. In your class, override any method or property as your want, or add new methods or properties;
4. In your application that you plan to use your own version of the helper class, include the following
line of code in the bootstrap script:
```php
Yii::$classMap['yii\helpers\ArrayHelper'] = 'path/to/ArrayHelper.php';
```
The Step 4 above will instruct Yii class autoloader to load your version of the helper instead of the one
included in the Yii distribution.
> Tip: You can also use `Yii::$classMap` to replace ANY core Yii class, not necessarily helper classes,
> with your own customized version.
......@@ -41,12 +41,13 @@ Database
- [Basics](database-basics.md) - Connecting to a database, basic queries, transactions and schema manipulation
- [Query Builder](query-builder.md) - Querying the database using a simple abstraction layer
- [ActiveRecord](active-record.md) - The active record ORM, retrieving and manipulatings records and defining relations
- [Database Migration](console-migrate.md) - Versioning your database with database migrations
- [ActiveRecord](active-record.md) - The active record ORM, retrieving and manipulating records and defining relations
- [Database Migration](console-migrate.md) - Versioning your database with database migration
Developers Toolbox
------------------
- [Helper Classes](helpers.md)
- [Automatic Code Generation](gii.md)
- [Debug toolbar and debugger](module-debug.md)
- [Error Handling](error.md)
......
......@@ -66,7 +66,7 @@ In the config above we are defining two log targets: [[\yii\log\FileTarget|file]
In both cases we are filtering messages handles by these targets by severity. In case of file target we're
additionally filter by category. `yii\*` means all categories starting with `yii\`.
Each log target can have a name and can be referenced via the [[targets]] property as follows:
Each log target can have a name and can be referenced via the [[yii\log\Logger::targets|targets]] property as follows:
```php
Yii::$app->log->targets['file']->enabled = false;
......
......@@ -226,7 +226,7 @@ When `validate()` is called, the actual validation rules executed are determined
### Creating your own validators (Inline validators)
If none of the built in validators fit your needs, you can create your own validator by creating a method in you model class.
This method will be wrapped by an [[InlineValidator|yii\validation\InlineValidator]] an be called upon validation.
This method will be wrapped by an [[InlineValidator|yii\validators\InlineValidator]] an be called upon validation.
You will do the validation of the attribute and [[add errors|yii\base\Model::addError()]] to the model when validation fails.
The method has the following signature `public function myValidator($attribute, $params)` while you are free to choose the name.
......@@ -251,8 +251,8 @@ public function rules()
}
```
You may also set other properties of the [[InlineValidator|yii\validation\InlineValidator]] in the rules definition,
for example the [[skipOnEmpty|yii\validation\InlineValidator::skipOnEmpty]] property:
You may also set other properties of the [[InlineValidator|yii\validators\InlineValidator]] in the rules definition,
for example the [[skipOnEmpty|yii\validators\InlineValidator::skipOnEmpty]] property:
```php
[['birthdate'], 'validateAge', 'params' => ['min' => '12'], 'skipOnEmpty' => false],
......
......@@ -9,22 +9,22 @@ Installing and configuring
Add these lines to your config file:
```
```php
'preload' => ['debug'],
'modules' => [
'debug' => ['yii\debug\Module']
]
```
**Watch out: by default the debug module only works when browsing the website from the localhost. If you want to use it
on a remote (staging) server, add the parameter allowedIPs to the config to whitelist your IP, e.g. :**
> Note: by default the debug module only works when browsing the website from the localhost. If you want to use it
> on a remote (staging) server, add the parameter allowedIPs to the config to whitelist your IP, e.g. :**
```
```php
'preload' => ['debug'],
'modules' => [
'debug' => [
'class'=>'yii\debug\Module',
'allowedIPs'=>['1.2.3.4', '127.0.0.1', '::1']
'class' => 'yii\debug\Module',
'allowedIPs' => ['1.2.3.4', '127.0.0.1', '::1']
]
]
```
......
......@@ -13,30 +13,50 @@ $rows = (new \yii\db\Query)
->select('id, name')
->from('tbl_user')
->limit(10)
->createCommand()
->queryAll();
->all();
// which is equivalent to the following code:
$query = new \yii\db\Query;
$query->select('id, name')
$query = (new \yii\db\Query)
->select('id, name')
->from('tbl_user')
->limit(10);
// Create a command.
// Create a command. You can get the actual SQL using $command->sql
$command = $query->createCommand();
// You can get the actual SQL using $command->sql
// Execute the command:
$rows = $command->queryAll();
```
Query Methods
-------------
As you can see, [[yii\db\Query]] is the main player that you need to deal with. Behind the scene,
`Query` is actually only responsible for representing various query information. The actual query
building logic is done by [[yii\db\QueryBuilder]] when you call the `createCommand()` method,
and the query execution is done by [[yii\db\Command]].
For convenience, [[yii\db\Query]] provides a set of commonly used query methods that will build
the query, execute it, and return the result. For example,
- [[yii\db\Query::all()|all()]]: builds the query, executes it and returns all results as an array.
- [[yii\db\Query::one()|one()]]: returns the first row of the result.
- [[yii\db\Query::column()|column()]]: returns the first column of the result.
- [[yii\db\Query::scalar()|scalar()]]: returns the first column in the first row of the result.
- [[yii\db\Query::exists()|exists()]]: returns a value indicating whether the query results in anything.
- [[yii\db\Query::count()|count()]]: returns the result of a `COUNT` query. Other similar methods
include `sum()`, `average()`, `max()`, `min()`, which support the so-called aggregational data query.
Building Query
--------------
In the following, we will explain how to build various clauses in a SQL statement. For simplicity,
we use `$query` to represent a [[yii\db\Query]] object.
`SELECT`
--------
### `SELECT`
In order to form a basic `SELECT` query, you need to specify what columns to select and from what table:
......@@ -68,8 +88,7 @@ To select distinct rows, you may call `distinct()`, like the following:
$query->select('user_id')->distinct()->from('tbl_post');
```
`FROM`
------
### `FROM`
To specify which table(s) to select data from, call `from()`:
......@@ -102,44 +121,7 @@ $query->select('*')->from(['u' => $subQuery]);
```
`JOIN`
-----
The `JOIN` clauses are generated in the Query Builder by using the applicable join method:
- `innerJoin()`
- `leftJoin()`
- `rightJoin()`
This left join selects data from two related tables in one query:
```php
$query->select(['tbl_user.name AS author', 'tbl_post.title as title'])
->from('tbl_user')
->leftJoin('tbl_post', 'tbl_post.user_id = tbl_user.id');
```
In the code, the `leftJoin()` method's first parameter
specifies the table to join to. The second parameter defines the join condition.
If your database application supports other join types, you can use those via the generic `join` method:
```php
$query->join('FULL OUTER JOIN', 'tbl_post', 'tbl_post.user_id = tbl_user.id');
```
The first argument is the join type to perform. The second is the table to join to, and the third is the condition.
Like `FROM`, you may also join with sub-queries. To do so, specify the sub-query as an array
which must contain one element. The array value must be a `Query` object representing the sub-query,
while the array key is the alias for the sub-query. For example,
```php
$query->leftJoin(['u' => $subQuery], 'u.id=author_id');
```
`WHERE`
-------
### `WHERE`
Usually data is selected based upon certain criteria. Query Builder has some useful methods to specify these, the most powerful of which being `where`. It can be used in multiple ways.
......@@ -250,8 +232,7 @@ In case `$search` isn't empty the following SQL will be generated:
WHERE (`status` = 10) AND (`title` LIKE '%yii%')
```
`ORDER BY`
-----
### `ORDER BY`
For ordering results `orderBy` and `addOrderBy` could be used:
......@@ -266,8 +247,7 @@ Here we are ordering by `id` ascending and then by `name` descending.
```
Group and Having
----------------
### `GROUP BY` and `HAVING`
In order to add `GROUP BY` to generated SQL you can use the following:
......@@ -288,8 +268,7 @@ for these are similar to the ones for `where` methods group:
$query->having(['status' => $status]);
```
Limit and offset
----------------
### `LIMIT` and `OFFSET`
To limit result to 10 rows `limit` can be used:
......@@ -303,8 +282,43 @@ To skip 100 fist rows use:
$query->offset(100);
```
Union
-----
### `JOIN`
The `JOIN` clauses are generated in the Query Builder by using the applicable join method:
- `innerJoin()`
- `leftJoin()`
- `rightJoin()`
This left join selects data from two related tables in one query:
```php
$query->select(['tbl_user.name AS author', 'tbl_post.title as title'])
->from('tbl_user')
->leftJoin('tbl_post', 'tbl_post.user_id = tbl_user.id');
```
In the code, the `leftJoin()` method's first parameter
specifies the table to join to. The second parameter defines the join condition.
If your database application supports other join types, you can use those via the generic `join` method:
```php
$query->join('FULL OUTER JOIN', 'tbl_post', 'tbl_post.user_id = tbl_user.id');
```
The first argument is the join type to perform. The second is the table to join to, and the third is the condition.
Like `FROM`, you may also join with sub-queries. To do so, specify the sub-query as an array
which must contain one element. The array value must be a `Query` object representing the sub-query,
while the array key is the alias for the sub-query. For example,
```php
$query->leftJoin(['u' => $subQuery], 'u.id=author_id');
```
### `UNION`
`UNION` in SQL adds results of one query to results of another query. Columns returned by both queries should match.
In Yii in order to build it you can first form two query objects and then use `union` method:
......@@ -319,3 +333,57 @@ $anotherQuery->select('id, 'user' as type, name')->from('tbl_user')->limit(10);
$query->union($anotherQuery);
```
Batch Query
-----------
When working with large amount of data, methods such as [[yii\db\Query::all()]] are not suitable
because they require loading all data into the memory. To keep the memory requirement low, Yii
provides the so-called batch query support. A batch query makes uses of data cursor and fetches
data in batches.
Batch query can be used like the following:
```php
use yii\db\Query;
$query = (new Query)
->from('tbl_user')
->orderBy('id');
foreach ($query->batch() as $users) {
// $users is an array of 100 or fewer rows from the user table
}
// or if you want to iterate the row one by one
foreach ($query->each() as $user) {
// $user represents one row of data from the user table
}
```
The method [[yii\db\Query::batch()]] and [[yii\db\Query::each()]] return an [[yii\db\BatchQueryResult]] object
which implements the `Iterator` interface and thus can be used in the `foreach` construct.
During the first iteration, a SQL query is made to the database. Data are since then fetched in batches
in the iterations. By default, the batch size is 100, meaning 100 rows of data are being fetched in each batch.
You can change the batch size by passing the first parameter to the `batch()` or `each()` method.
Compared to the [[yii\db\Query::all()]], the batch query only loads 100 rows of data at a time into the memory.
If you process the data and then discard it right away, the batch query can help keep the memory usage under a limit.
If you specify the query result to be indexed by some column via [[yii\db\Query::indexBy()]], the batch query
will still keep the proper index. For example,
```php
use yii\db\Query;
$query = (new Query)
->from('tbl_user')
->indexBy('username');
foreach ($query->batch() as $users) {
// $users is indexed by the "username" column
}
foreach ($query->each() as $username => $user) {
}
```
......@@ -10,7 +10,7 @@ Standard Yii validators
Standard Yii validators could be specified using aliases instead of referring to class names. Here's the list of all
validators bundled with Yii with their most useful properties:
### `boolean`: [[yii\validation\BooleanValidator|BooleanValidator]]
### `boolean`: [[yii\validators\BooleanValidator|BooleanValidator]]
Checks if the attribute value is a boolean value.
......@@ -26,15 +26,15 @@ with [[yii\captcha\CaptchaAction]].
- `caseSensitive` whether the comparison is case sensitive. _(false)_
- `captchaAction` the route of the controller action that renders the CAPTCHA image. _('site/captcha')_
### `compare`: [[yii\validation\CompareValidator|CompareValidator]]
### `compare`: [[yii\validators\CompareValidator|CompareValidator]]
Compares the specified attribute value with another value and validates if they are equal.
- `compareAttribute` the name of the attribute to be compared with. _(currentAttribute_repeat)_
- `compareAttribute` the name of the attribute to be compared with. _(currentAttribute&#95;repeat)_
- `compareValue` the constant value to be compared with.
- `operator` the operator for comparison. _('==')_
### `date`: [[yii\validation\DateValidator|DateValidator]]
### `date`: [[yii\validators\DateValidator|DateValidator]]
Verifies if the attribute represents a date, time or datetime in a proper format.
......@@ -42,20 +42,20 @@ Verifies if the attribute represents a date, time or datetime in a proper format
[PHP date_create_from_format](http://www.php.net/manual/en/datetime.createfromformat.php). _('Y-m-d')_
- `timestampAttribute` the name of the attribute to receive the parsing result.
### `default`: [[yii\validation\DefaultValueValidator|DefaultValueValidator]]
### `default`: [[yii\validators\DefaultValueValidator|DefaultValueValidator]]
Sets the attribute to be the specified default value.
- `value` the default value to be set to the specified attributes.
### `double`: [[yii\validation\NumberValidator|NumberValidator]]
### `double`: [[yii\validators\NumberValidator|NumberValidator]]
Validates that the attribute value is a number.
- `max` limit of the number. _(null)_
- `min` lower limit of the number. _(null)_
### `email`: [[yii\validation\EmailValidator|EmailValidator]]
### `email`: [[yii\validators\EmailValidator|EmailValidator]]
Validates that the attribute value is a valid email address.
......@@ -64,7 +64,7 @@ Validates that the attribute value is a valid email address.
- `checkPort` whether to check port 25 for the email address. _(false)_
- `enableIDN` whether validation process should take into account IDN (internationalized domain names). _(false)_
### `exist`: [[yii\validation\ExistValidator|ExistValidator]]
### `exist`: [[yii\validators\ExistValidator|ExistValidator]]
Validates that the attribute value exists in a table.
......@@ -73,7 +73,7 @@ Validates that the attribute value exists in a table.
- `targetAttribute` the ActiveRecord attribute name that should be used to look for the attribute value being validated.
_(name of the attribute being validated)_
### `file`: [[yii\validation\FileValidator|FileValidator]]
### `file`: [[yii\validators\FileValidator|FileValidator]]
Verifies if an attribute is receiving a valid uploaded file.
......@@ -82,7 +82,7 @@ Verifies if an attribute is receiving a valid uploaded file.
- `maxSize` the maximum number of bytes required for the uploaded file.
- `maxFiles` the maximum file count the given attribute can hold. _(1)_
### `filter`: [[yii\validation\FilterValidator|FilterValidator]]
### `filter`: [[yii\validators\FilterValidator|FilterValidator]]
Converts the attribute value according to a filter.
......@@ -103,7 +103,7 @@ Or an anonymous function:
}],
```
### `in`: [[yii\validation\RangeValidator|RangeValidator]]
### `in`: [[yii\validators\RangeValidator|RangeValidator]]
Validates that the attribute value is among a list of values.
......@@ -111,7 +111,7 @@ Validates that the attribute value is among a list of values.
- `strict` whether the comparison is strict (both type and value must be the same). _(false)_
- `not` whether to invert the validation logic. _(false)_
### `inline`: [[yii\validation\InlineValidator|InlineValidator]]
### `inline`: [[yii\validators\InlineValidator|InlineValidator]]
Uses a custom function to validate the attribute. You need to define a public method in your
model class which will evaluate the validity of the attribute. For example, if an attribute
......@@ -126,40 +126,40 @@ public function myValidationMethod($attribute) {
}
```
### `integer`: [[yii\validation\NumberValidator|NumberValidator]]
### `integer`: [[yii\validators\NumberValidator|NumberValidator]]
Validates that the attribute value is an integer number.
- `max` limit of the number. _(null)_
- `min` lower limit of the number. _(null)_
### `match`: [[yii\validation\RegularExpressionValidator|RegularExpressionValidator]]
### `match`: [[yii\validators\RegularExpressionValidator|RegularExpressionValidator]]
Validates that the attribute value matches the specified pattern defined by regular expression.
- `pattern` the regular expression to be matched with.
- `not` whether to invert the validation logic. _(false)_
### `number`: [[yii\validation\NumberValidator|NumberValidator]]
### `number`: [[yii\validators\NumberValidator|NumberValidator]]
Validates that the attribute value is a number.
- `max` limit of the number. _(null)_
- `min` lower limit of the number. _(null)_
### `required`: [[yii\validation\RequiredValidator|RequiredValidator]]
### `required`: [[yii\validators\RequiredValidator|RequiredValidator]]
Validates that the specified attribute does not have null or empty value.
- `requiredValue` the desired value that the attribute must have. _(any)_
- `strict` whether the comparison between the attribute value and
[[yii\validation\RequiredValidator::requiredValue|requiredValue]] is strict. _(false)_
[[yii\validators\RequiredValidator::requiredValue|requiredValue]] is strict. _(false)_
### `safe`: [[yii\validation\SafeValidator|SafeValidator]]
### `safe`: [[yii\validators\SafeValidator|SafeValidator]]
Serves as a dummy validator whose main purpose is to mark the attributes to be safe for massive assignment.
### `string`: [[yii\validation\StringValidator|StringValidator]]
### `string`: [[yii\validators\StringValidator|StringValidator]]
Validates that the attribute value is of certain length.
......@@ -168,7 +168,7 @@ Validates that the attribute value is of certain length.
- `min` minimum length. If not set, it means no minimum length limit.
- `encoding` the encoding of the string value to be validated. _([[\yii\base\Application::charset]])_
### `unique`: [[yii\validation\UniqueValidator|UniqueValidator]]
### `unique`: [[yii\validators\UniqueValidator|UniqueValidator]]
Validates that the attribute value is unique in the corresponding database table.
......@@ -177,7 +177,7 @@ Validates that the attribute value is unique in the corresponding database table
- `targetAttribute` the ActiveRecord attribute name that should be used to look for the attribute value being validated.
_(name of the attribute being validated)_
### `url`: [[yii\validation\UrlValidator|UrlValidator]]
### `url`: [[yii\validators\UrlValidator|UrlValidator]]
Validates that the attribute value is a valid http or https URL.
......
......@@ -77,7 +77,12 @@ class Foo
### 4.2. Properties
- When declaring public class members specify `public` keyword explicitly.
- Variables should be declared at the top of the class before any method declarations.
- Public and protected variables should be declared at the top of the class before any method declarations.
Private variables should also be declared at the top of the class but may be added right before the methods
that are dealing with them in cases where they are only related to a small subset of the class methods.
- The order of property declaration in a class should be ascending from public over protected to private.
- For better readability there should be no blank lines between property declarations and two blank lines
between property and method declaration sections.
- Private variables should be named like `$_varName`.
- Public class members and standalone variables should be named using `$camelCase`
with first letter lowercase.
......@@ -310,7 +315,7 @@ $mul = array_reduce($numbers, function($r, $x) use($n) {
Documentation
-------------
- Refer ot [phpDoc](http://phpdoc.org/) for documentation syntax.
- Refer to [phpDoc](http://phpdoc.org/) for documentation syntax.
- Code without documentation is not allowed.
- All class files must contain a "file-level" docblock at the top of each file
and a "class-level" docblock immediately above each class.
......
......@@ -21,7 +21,7 @@
"require": {
"yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*",
"phpdocumentor/reflection": "dev-master | >1.0.2",
"phpdocumentor/reflection": ">=1.0.3",
"erusev/parsedown": "0.9.*"
},
"autoload": {
......
......@@ -8,7 +8,9 @@ Yii Framework 2 bootstrap extension Change Log
- Enh #1474: Added option to make NavBar 100% width (cebe)
- Enh #1552: It is now possible to use multiple bootstrap NavBar in a single page (Alex-Code)
- Enh #1553: Only add navbar-default class to NavBar when no other class is specified (cebe)
- Enh #1562: Added `yii\bootstrap\Tabs::linkOptions` (kartik-v)
- Enh #1601: Added support for tagName and encodeLabel parameters in ButtonDropdown (omnilight)
- Enh #1881: Improved `yii\bootstrap\NavBar` with `containerOptions`, `innerContainerOptions` and `renderInnerContainer` (creocoder)
- Chg #1459: Update Collapse to use bootstrap 3 classes (tonydspaniard)
- Chg #1820: Update Progress to use bootstrap 3 markup (samdark)
......
......@@ -8,6 +8,7 @@
namespace yii\bootstrap;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
......@@ -33,11 +34,24 @@ use yii\helpers\Html;
*
* @see http://getbootstrap.com/components/#navbar
* @author Antonio Ramirez <amigo.cobos@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class NavBar extends Widget
{
/**
* @var array the HTML attributes for the widget container tag. The following special options are recognized:
*
* - tag: string, defaults to "nav", the name of the container tag
*/
public $options = [];
/**
* @var array the HTML attributes for the container tag. The following special options are recognized:
*
* - tag: string, defaults to "div", the name of the container tag
*/
public $containerOptions = [];
/**
* @var string the text of the brand. Note that this is not HTML-encoded.
* @see http://getbootstrap.com/components/#navbar
*/
......@@ -56,10 +70,14 @@ class NavBar extends Widget
*/
public $screenReaderToggleText = 'Toggle navigation';
/**
* @var bool whether the navbar content should be included in a `container` div which adds left and right padding.
* Set this to false for a 100% width navbar.
* @var bool whether the navbar content should be included in an inner div container which by default
* adds left and right padding. Set this to false for a 100% width navbar.
*/
public $renderInnerContainer = true;
/**
* @var array the HTML attributes of the inner container.
*/
public $padded = true;
public $innerContainerOptions = [];
/**
* Initializes the widget.
......@@ -69,27 +87,37 @@ class NavBar extends Widget
parent::init();
$this->clientOptions = false;
Html::addCssClass($this->options, 'navbar');
if ($this->options['class'] == 'navbar') {
if ($this->options['class'] === 'navbar') {
Html::addCssClass($this->options, 'navbar-default');
}
Html::addCssClass($this->brandOptions, 'navbar-brand');
if (empty($this->options['role'])) {
$this->options['role'] = 'navigation';
}
echo Html::beginTag('nav', $this->options);
if ($this->padded) {
echo Html::beginTag('div', ['class' => 'container']);
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'nav');
echo Html::beginTag($tag, $options);
if ($this->renderInnerContainer) {
if (!isset($this->innerContainerOptions['class'])) {
Html::addCssClass($this->innerContainerOptions, 'container');
}
echo Html::beginTag('div', $this->innerContainerOptions);
}
echo Html::beginTag('div', ['class' => 'navbar-header']);
if (!isset($this->containerOptions['id'])) {
$this->containerOptions['id'] = "{$this->options['id']}-collapse";
}
echo $this->renderToggleButton();
if ($this->brandLabel !== null) {
Html::addCssClass($this->brandOptions, 'navbar-brand');
echo Html::a($this->brandLabel, $this->brandUrl === null ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions);
}
echo Html::endTag('div');
echo Html::beginTag('div', ['class' => "collapse navbar-collapse navbar-{$this->options['id']}-collapse"]);
Html::addCssClass($this->containerOptions, 'collapse');
Html::addCssClass($this->containerOptions, 'navbar-collapse');
$options = $this->containerOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div');
echo Html::beginTag($tag, $options);
}
/**
......@@ -97,12 +125,13 @@ class NavBar extends Widget
*/
public function run()
{
echo Html::endTag('div');
if ($this->padded) {
$tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div');
echo Html::endTag($tag);
if ($this->renderInnerContainer) {
echo Html::endTag('div');
}
echo Html::endTag('nav');
$tag = ArrayHelper::remove($this->options, 'tag', 'nav');
echo Html::endTag($tag, $this->options);
BootstrapPluginAsset::register($this->getView());
}
......@@ -113,11 +142,11 @@ class NavBar extends Widget
protected function renderToggleButton()
{
$bar = Html::tag('span', '', ['class' => 'icon-bar']);
$screenReader = '<span class="sr-only">'.$this->screenReaderToggleText.'</span>';
$screenReader = "<span class=\"sr-only\">{$this->screenReaderToggleText}</span>";
return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [
'class' => 'navbar-toggle',
'data-toggle' => 'collapse',
'data-target' => ".navbar-{$this->options['id']}-collapse",
'data-target' => "#{$this->containerOptions['id']}",
]);
}
}
......@@ -59,6 +59,7 @@ class Tabs extends Widget
*
* - label: string, required, the tab header label.
* - headerOptions: array, optional, the HTML attributes of the tab header.
* - linkOptions: array, optional, the HTML attributes of the tab header link tags.
* - content: array, required if `items` is not set. The content (HTML) of the tab pane.
* - options: array, optional, the HTML attributes of the tab pane container.
* - active: boolean, optional, whether the item tab header and pane should be visible or not.
......@@ -82,6 +83,11 @@ class Tabs extends Widget
*/
public $headerOptions = [];
/**
* @var array list of HTML attributes for the tab header link tags. This will be overwritten
* by the "linkOptions" set in individual [[items]].
*/
public $linkOptions = [];
/**
* @var boolean whether the labels for header items should be HTML-encoded.
*/
public $encodeLabels = true;
......@@ -124,6 +130,7 @@ class Tabs extends Widget
}
$label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
$headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
$linkOptions = array_merge($this->linkOptions, ArrayHelper::getValue($item, 'linkOptions', []));
if (isset($item['items'])) {
$label .= ' <b class="caret"></b>';
......@@ -133,7 +140,9 @@ class Tabs extends Widget
Html::addCssClass($headerOptions, 'active');
}
$header = Html::a($label, "#", ['class' => 'dropdown-toggle', 'data-toggle' => 'dropdown']) . "\n"
Html::addCssClass($linkOptions, 'dropdown-toggle');
$linkOptions['data-toggle'] = 'dropdown';
$header = Html::a($label, "#", $linkOptions) . "\n"
. Dropdown::widget(['items' => $item['items'], 'clientOptions' => false, 'view' => $this->getView()]);
} elseif (isset($item['content'])) {
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
......@@ -144,7 +153,8 @@ class Tabs extends Widget
Html::addCssClass($options, 'active');
Html::addCssClass($headerOptions, 'active');
}
$header = Html::a($label, '#' . $options['id'], ['data-toggle' => 'tab']);
$linkOptions['data-toggle'] = 'tab';
$header = Html::a($label, '#' . $options['id'], $linkOptions);
$panes[] = Html::tag('div', $item['content'], $options);
} else {
throw new InvalidConfigException("Either the 'content' or 'items' option must be set.");
......@@ -154,7 +164,7 @@ class Tabs extends Widget
}
return Html::tag('ul', implode("\n", $headers), $this->options) . "\n"
. Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']);
. Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']);
}
/**
......
......@@ -267,9 +267,9 @@ Then run command `php codecept.phar run --debug unit/SomeDebugTest` and you will
[authTimeout] =>
[autoRenewCookie] => 1
[idVar] => __id
[authTimeoutVar] => __expire
[returnUrlVar] => __returnUrl
[idParam] => __id
[authTimeoutParam] => __expire
[returnUrlParam] => __returnUrl
[_access:yii\web\User:private] => Array
(
)
......
......@@ -47,19 +47,7 @@ class LogTarget extends Target
mkdir($path);
}
$request = Yii::$app->getRequest();
$response = Yii::$app->getResponse();
$summary = [
'tag' => $this->tag,
'url' => $request->getAbsoluteUrl(),
'ajax' => $request->getIsAjax(),
'method' => $request->getMethod(),
'ip' => $request->getUserIP(),
'time' => time(),
'statusCode' => $response->statusCode,
'sqlCount' => $this->getSqlTotalCount(),
];
$summary = $this->collectSummary();
$dataFile = "$path/{$this->tag}.data";
$data = [];
foreach ($this->module->panels as $id => $panel) {
......@@ -140,6 +128,32 @@ class LogTarget extends Target
}
/**
* Collects summary data of current request.
* @return array
*/
protected function collectSummary()
{
$request = Yii::$app->getRequest();
$response = Yii::$app->getResponse();
$summary = [
'tag' => $this->tag,
'url' => $request->getAbsoluteUrl(),
'ajax' => $request->getIsAjax(),
'method' => $request->getMethod(),
'ip' => $request->getUserIP(),
'time' => time(),
'statusCode' => $response->statusCode,
'sqlCount' => $this->getSqlTotalCount(),
];
if (isset($this->module->panels['mail'])) {
$summary['mailCount'] = count($this->module->panels['mail']->getMessages());
}
return $summary;
}
/**
* Returns total sql count executed in current request. If database panel is not configured
* returns 0.
* @return integer
......@@ -149,9 +163,10 @@ class LogTarget extends Target
if (!isset($this->module->panels['db'])) {
return 0;
}
$profileLogs = $this->module->panels['db']->save();
$profileLogs = $this->module->panels['db']->getProfileLogs();
# / 2 because messages are in couple (begin/end)
return count($profileLogs['messages']) / 2;
return count($profileLogs) / 2;
}
}
......@@ -35,6 +35,7 @@ abstract class Base extends Component implements MatcherInterface
*/
public function hasValue()
{
return !empty($this->baseValue) || $this->baseValue === 0;
return !empty($this->baseValue) || ($this->baseValue === '0');
}
}
......@@ -50,12 +50,16 @@ class Debug extends Base
public $statusCode;
/**
*
* @var integer sql count attribute input search value
*/
public $sqlCount;
/**
* @var integer total mail count attribute input search value
*/
public $mailCount;
/**
* @var array critical codes, used to determine grid row options.
*/
public $criticalCodes = [400, 404, 500];
......@@ -66,7 +70,7 @@ class Debug extends Base
public function rules()
{
return [
[['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount'], 'safe'],
[['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount', 'mailCount'], 'safe'],
];
}
......@@ -82,7 +86,8 @@ class Debug extends Base
'ajax' => 'Ajax',
'url' => 'url',
'statusCode' => 'Status code',
'sqlCount' => 'Total queries',
'sqlCount' => 'Query Count',
'mailCount' => 'Mail Count',
];
}
......@@ -97,7 +102,7 @@ class Debug extends Base
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'sort' => [
'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount'],
'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount', 'mailCount'],
],
'pagination' => [
'pageSize' => 50,
......@@ -116,6 +121,7 @@ class Debug extends Base
$this->addCondition($filter, 'url', true);
$this->addCondition($filter, 'statusCode');
$this->addCondition($filter, 'sqlCount');
$this->addCondition($filter, 'mailCount');
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
......
......@@ -94,9 +94,18 @@ class DbPanel extends Panel
*/
public function save()
{
return ['messages' => $this->getProfileLogs()];
}
/**
* Returns all profile logs of the current request for this panel. It includes categories such as:
* 'yii\db\Command::query', 'yii\db\Command::execute'.
* @return array
*/
public function getProfileLogs()
{
$target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
return ['messages' => $messages];
return $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
}
/**
......@@ -164,4 +173,5 @@ class DbPanel extends Panel
{
return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
}
}
......@@ -77,6 +77,16 @@ class MailPanel extends Panel
public function save()
{
return $this->getMessages();
}
/**
* Returns info about messages of current request. Each element is array holding
* message info, such as: time, reply, bc, cc, from, to and other.
* @return array messages
*/
public function getMessages()
{
return $this->_messages;
}
......
......@@ -68,7 +68,7 @@ if (isset($this->context->module->panels['db']) && isset($this->context->module-
'ip',
[
'attribute' => 'sqlCount',
'label' => 'Total queries',
'label' => 'Query Count',
'value' => function ($data) {
$dbPanel = $this->context->module->panels['db'];
......@@ -86,6 +86,10 @@ if (isset($this->context->module->panels['db']) && isset($this->context->module-
'format' => 'html',
],
[
'attribute' => 'mailCount',
'visible' => isset($this->context->module->panels['mail']),
],
[
'attribute' => 'method',
'filter' => ['get' => 'GET', 'post' => 'POST', 'delete' => 'DELETE', 'put' => 'PUT', 'head' => 'HEAD']
],
......
......@@ -89,7 +89,7 @@ class <?= $controllerClass ?> extends <?= StringHelper::basename($generator->bas
{
$model = new <?= $modelClass ?>;
if ($model->load($_POST) && $model->save()) {
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', <?= $urlParams ?>]);
} else {
return $this->render('create', [
......@@ -108,7 +108,7 @@ class <?= $controllerClass ?> extends <?= StringHelper::basename($generator->bas
{
$model = $this->findModel(<?= $actionParams ?>);
if ($model->load($_POST) && $model->save()) {
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', <?= $urlParams ?>]);
} else {
return $this->render('update', [
......
......@@ -16,7 +16,7 @@ public function action<?= Inflector::id2camel(trim(basename($generator->viewName
{
$model = new <?= $generator->modelClass ?><?= empty($generator->scenarioName) ? "" : "(['scenario' => '{$generator->scenarioName}'])" ?>;
if ($model->load($_POST)) {
if ($model->load(Yii::$app->request->post())) {
if ($model->validate()) {
// form inputs are valid, do something here
return;
......
......@@ -372,12 +372,12 @@ abstract class ActiveRecord extends BaseActiveRecord
try {
$result = $this->insertInternal($attributes);
if ($result === false) {
$transaction->rollback();
$transaction->rollBack();
} else {
$transaction->commit();
}
} catch (\Exception $e) {
$transaction->rollback();
$transaction->rollBack();
throw $e;
}
} else {
......@@ -473,12 +473,12 @@ abstract class ActiveRecord extends BaseActiveRecord
try {
$result = $this->updateInternal($attributes);
if ($result === false) {
$transaction->rollback();
$transaction->rollBack();
} else {
$transaction->commit();
}
} catch (\Exception $e) {
$transaction->rollback();
$transaction->rollBack();
throw $e;
}
} else {
......@@ -589,14 +589,14 @@ abstract class ActiveRecord extends BaseActiveRecord
}
if ($transaction !== null) {
if ($result === false) {
$transaction->rollback();
$transaction->rollBack();
} else {
$transaction->commit();
}
}
} catch (\Exception $e) {
if ($transaction !== null) {
$transaction->rollback();
$transaction->rollBack();
}
throw $e;
}
......
......@@ -72,8 +72,7 @@ class QueryBuilder extends Object
if ($from === null && $query instanceof ActiveQuery) {
/** @var ActiveRecord $modelClass */
$modelClass = $query->modelClass;
$tableName = $modelClass::indexName();
$from = [$tableName];
$from = [$modelClass::indexName()];
}
$clauses = [
......
......@@ -67,6 +67,7 @@ Yii Framework 2 Change Log
- Enh #1476: Add yii\web\Session::handler property (nineinchnick)
- Enh #1499: Added `ActionColumn::controller` property to support customizing the controller for handling GridView actions (qiangxue)
- Enh #1523: Query conditions now allow to use the NOT operator (cebe)
- Enh #1562: Added `yii\bootstrap\Tabs::linkOptions` (kartik-v)
- Enh #1572: Added `yii\web\Controller::createAbsoluteUrl()` (samdark)
- Enh #1579: throw exception when the given AR relation name does not match in a case sensitive manner (qiangxue)
- Enh #1581: Added `ActiveQuery::joinWith()` and `ActiveQuery::innerJoinWith()` to support joining with relations (qiangxue)
......@@ -78,12 +79,14 @@ Yii Framework 2 Change Log
- Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue)
- Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo)
- Enh #1645: Added `Connection::$pdoClass` property (Ragazzo)
- Enh #1645: Added support for nested DB transactions (qiangxue)
- Enh #1681: Added support for automatically adjusting the "for" attribute of label generated by `ActiveField::label()` (qiangxue)
- Enh #1706: Added support for registering a single JS/CSS file with dependency (qiangxue)
- Enh #1773: keyPrefix property of Cache is not restricted to alnum characters anymore, however it is still recommended (cebe)
- Enh #1809: Added support for building "EXISTS" and "NOT EXISTS" query conditions (abdrasulov)
- Enh #1839: Added support for getting file extension and basename from uploaded file (anfrantic)
- Enh #1852: ActiveRecord::tableName() now returns table name using DbConnection::tablePrefix (creocoder)
- Enh #1881: Improved `yii\bootstrap\NavBar` with `containerOptions`, `innerContainerOptions` and `renderInnerContainer` (creocoder)
- Enh #1894: The path aliases `@webroot` and `@web` are now available right after the application is initialized (qiangxue)
- Enh #1921: Grid view ActionColumn now allow to name buttons like `{controller/action}` (creocoder)
- Enh #1973: `yii message/extract` is now able to generate `.po` files (SergeiKutanov, samdark)
......@@ -109,6 +112,9 @@ Yii Framework 2 Change Log
- Enh #2240: Improved `yii\web\AssetManager::publish()`, `yii\web\AssetManager::getPublishedPath()` and `yii\web\AssetManager::getPublishedUrl()` to support aliases (vova07)
- Enh #2325: Adding support for the `X-HTTP-Method-Override` header in `yii\web\Request::getMethod()` (pawzar)
- Enh #2364: Take into account current error reporting level in error handler (gureedo)
- Enh #2387: Added support for fetching data from database in batches (nineinchnick, qiangxue)
- Enh #2417: Added possibility to set `dataType` for `$.ajax` call in yii.activeForm.js (Borales)
- Enh #2436: Label of the attribute, which looks like `relatedModel.attribute`, will be received from the related model if it available (djagya)
- Enh: Added support for using arrays as option values for console commands (qiangxue)
- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
......@@ -127,6 +133,7 @@ Yii Framework 2 Change Log
- Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue)
- Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue)
- Enh: Added support for building SQLs with sub-queries (qiangxue)
- Chg #1186: Changed `Sort` to use comma to separate multiple sort fields and use negative sign to indicate descending sort (qiangxue)
- Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue)
- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue)
- Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue)
......@@ -169,7 +176,19 @@ Yii Framework 2 Change Log
- Chg: Renamed `ActiveRecordInterface::createActiveRelation()` to `createRelation()` (qiangxue)
- Chg: The scripts in asset bundles are now registered in `View` at the end of `endBody()`. It was done in `endPage()` previously (qiangxue)
- Chg: Renamed `csrf-var` to `csrf-param` for CSRF header name (Dilip)
- Cgh: The directory holding email templates is renamed from `mails` to `mail` (qiangxue)
- Chg: The directory holding email templates is renamed from `mails` to `mail` (qiangxue)
- Chg: Renamed properties `fooVar` to `fooParam` for various classes (qiangxue)
- Renamed `ActiveForm::ajaxVar` to `ajaxParam`
- Renamed `Pagination::pageVar` to `pageParam`
- Renamed `Sort::sortVar` to `sortParam`
- Renamed `yii\web\Request::csrfVar` to `csrfParam`
- Renamed `yii\web\Request::methodVar` to `methodParam`
- Renamed `UrlManager::routeVar` to `routeParam`
- Renamed `yii\web\Session::flashVar` to `flashParam`
- Renamed `yii\web\User::idVar` to `idParam`
- Renamed `yii\web\User::authTimeoutVar` to `authTimeoutParam`
- Renamed `yii\web\User::returnUrlVar` to `returnUrlParam`
- New #66: [Auth client library](https://github.com/yiisoft/yii2-authclient) OpenId, OAuth1, OAuth2 clients (klimov-paul)
- New #706: Added `yii\widgets\Pjax` and enhanced `GridView` to work with `Pjax` to support AJAX-update (qiangxue)
- New #1393: [Codeception testing framework integration](https://github.com/yiisoft/yii2-codeception) (Ragazzo)
......
......@@ -45,7 +45,9 @@
// function ($form, attribute, messages)
afterValidate: undefined,
// the GET parameter name indicating an AJAX-based validation
ajaxVar: 'ajax'
ajaxParam: 'ajax',
// the type of data that you're expecting back from the server
ajaxDataType: 'json'
};
var attributeDefaults = {
......@@ -293,7 +295,7 @@
// If the validation is triggered by form submission, ajax validation
// should be done only when all inputs pass client validation
var $button = data.submitObject,
extData = '&' + data.settings.ajaxVar + '=' + $form.prop('id');
extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id');
if ($button && $button.length && $button.prop('name')) {
extData += '&' + $button.prop('name') + '=' + $button.prop('value');
}
......@@ -301,7 +303,7 @@
url: data.settings.validationUrl,
type: $form.prop('method'),
data: $form.serialize() + extData,
dataType: 'json',
dataType: data.settings.ajaxDataType,
success: function (msgs) {
if (msgs !== null && typeof msgs === 'object') {
$.each(data.attributes, function () {
......
......@@ -58,9 +58,9 @@ yii = (function ($) {
changeableSelector: 'select, input, textarea',
/**
* @return string|undefined the CSRF variable name. Undefined is returned if CSRF validation is not enabled.
* @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled.
*/
getCsrfVar: function () {
getCsrfParam: function () {
return $('meta[name=csrf-param]').prop('content');
},
......@@ -130,9 +130,9 @@ yii = (function ($) {
if (!method.match(/(get|post)/i)) {
$form.append('<input name="_method" value="' + method + '" type="hidden">');
}
var csrfVar = pub.getCsrfVar();
if (csrfVar) {
$form.append('<input name="' + csrfVar + '" value="' + pub.getCsrfToken() + '" type="hidden">');
var csrfParam = pub.getCsrfParam();
if (csrfParam) {
$form.append('<input name="' + csrfParam + '" value="' + pub.getCsrfToken() + '" type="hidden">');
}
$form.hide().appendTo('body');
}
......@@ -199,7 +199,7 @@ yii = (function ($) {
function initCsrfHandler() {
// automatically send CSRF token for all AJAX requests
$.ajaxPrefilter(function (options, originalOptions, xhr) {
if (!options.crossDomain && pub.getCsrfVar()) {
if (!options.crossDomain && pub.getCsrfParam()) {
xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken());
}
});
......@@ -236,6 +236,9 @@ yii = (function ($) {
return this.src.charAt(0) === '/' ? hostInfo + this.src : this.src;
}).toArray();
$.ajaxPrefilter('script', function (options, originalOptions, xhr) {
if(options.dataType == 'jsonp') {
return;
}
var url = options.url.charAt(0) === '/' ? hostInfo + options.url : options.url;
if ($.inArray(url, loadedScripts) === -1) {
loadedScripts.push(url);
......
......@@ -187,7 +187,7 @@ abstract class BaseDataProvider extends Component implements DataProviderInterfa
if (is_array($value)) {
$config = ['class' => Pagination::className()];
if ($this->id !== null) {
$config['pageVar'] = $this->id . '-page';
$config['pageParam'] = $this->id . '-page';
}
$this->_pagination = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Pagination || $value === false) {
......@@ -225,7 +225,7 @@ abstract class BaseDataProvider extends Component implements DataProviderInterfa
if (is_array($value)) {
$config = ['class' => Sort::className()];
if ($this->id !== null) {
$config['sortVar'] = $this->id . '-sort';
$config['sortParam'] = $this->id . '-sort';
}
$this->_sort = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Sort || $value === false) {
......
......@@ -71,12 +71,12 @@ class Pagination extends Object
* @var string name of the parameter storing the current page index. Defaults to 'page'.
* @see params
*/
public $pageVar = 'page';
public $pageParam = 'page';
/**
* @var boolean whether to always have the page parameter in the URL created by [[createUrl()]].
* If false and [[page]] is 0, the page parameter will not be put in the URL.
*/
public $forcePageVar = true;
public $forcePageParam = true;
/**
* @var string the route of the controller action for displaying the paged contents.
* If not set, it means using the currently requested route.
......@@ -88,7 +88,7 @@ class Pagination extends Object
*
* In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
*
* The array element indexed by [[pageVar]] is considered to be the current page number.
* The array element indexed by [[pageParam]] is considered to be the current page number.
* If the element does not exist, the current page number is considered 0.
*/
public $params;
......@@ -102,7 +102,7 @@ class Pagination extends Object
* When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1).
* Because [[pageCount]] relies on the correct value of [[totalCount]] which may not be available
* in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page
* number validation. By doing so, [[page]] will return the value indexed by [[pageVar]] in [[params]].
* number validation. By doing so, [[page]] will return the value indexed by [[pageParam]] in [[params]].
*/
public $validatePage = true;
/**
......@@ -143,8 +143,8 @@ class Pagination extends Object
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
if (isset($params[$this->pageVar]) && is_scalar($params[$this->pageVar])) {
$this->_page = (int)$params[$this->pageVar] - 1;
if (isset($params[$this->pageParam]) && is_scalar($params[$this->pageParam])) {
$this->_page = (int)$params[$this->pageParam] - 1;
if ($this->validatePage) {
$pageCount = $this->getPageCount();
if ($this->_page >= $pageCount) {
......@@ -177,7 +177,7 @@ class Pagination extends Object
* @param boolean $absolute whether to create an absolute URL. Defaults to `false`.
* @return string the created URL
* @see params
* @see forcePageVar
* @see forcePageParam
*/
public function createUrl($page, $absolute = false)
{
......@@ -185,10 +185,10 @@ class Pagination extends Object
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
if ($page > 0 || $page >= 0 && $this->forcePageVar) {
$params[$this->pageVar] = $page + 1;
if ($page > 0 || $page >= 0 && $this->forcePageParam) {
$params[$this->pageParam] = $page + 1;
} else {
unset($params[$this->pageVar]);
unset($params[$this->pageParam]);
}
$route = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
$urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
......
......@@ -98,7 +98,7 @@ class Sort extends Object
* ]
* ~~~
*
* In the above, two attributes are declared: "age" and "user". The "age" attribute is
* In the above, two attributes are declared: "age" and "name". The "age" attribute is
* a simple attribute which is equivalent to the following:
*
* ~~~
......@@ -110,10 +110,10 @@ class Sort extends Object
* ]
* ~~~
*
* The "user" attribute is a composite attribute:
* The "name" attribute is a composite attribute:
*
* - The "user" key represents the attribute name which will appear in the URLs leading
* to sort actions. Attribute names cannot contain characters listed in [[separators]].
* - The "name" key represents the attribute name which will appear in the URLs leading
* to sort actions.
* - The "asc" and "desc" elements specify how to sort by the attribute in ascending
* and descending orders, respectively. Their values represent the actual columns and
* the directions by which the data should be sorted by.
......@@ -124,7 +124,7 @@ class Sort extends Object
* Note that it will not be HTML-encoded.
*
* Note that if the Sort object is already created, you can only use the full format
* to configure every attribute. Each attribute must include these elements: asc and desc.
* to configure every attribute. Each attribute must include these elements: `asc` and `desc`.
*/
public $attributes = [];
/**
......@@ -132,12 +132,7 @@ class Sort extends Object
* in which direction. Defaults to 'sort'.
* @see params
*/
public $sortVar = 'sort';
/**
* @var string the tag appeared in the [[sortVar]] parameter that indicates the attribute should be sorted
* in descending order. Defaults to 'desc'.
*/
public $descTag = 'desc';
public $sortParam = 'sort';
/**
* @var array the order that should be used when the current request does not specify any order.
* The array keys are attribute names and the array values are the corresponding sort directions. For example,
......@@ -158,22 +153,19 @@ class Sort extends Object
*/
public $route;
/**
* @var array separators used in the generated URL. This must be an array consisting of
* two elements. The first element specifies the character separating different
* attributes, while the second element specifies the character separating attribute name
* and the corresponding sort direction. Defaults to `['.', '-']`.
* @var string the character used to separate different attributes that need to be sorted by.
*/
public $separators = ['.', '-'];
public $separator = ',';
/**
* @var array parameters (name => value) that should be used to obtain the current sort directions
* and to create new sort URLs. If not set, $_GET will be used instead.
*
* In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
*
* The array element indexed by [[sortVar]] is considered to be the current sort directions.
* The array element indexed by [[sortParam]] is considered to be the current sort directions.
* If the element does not exist, the [[defaultOrder|default order]] will be used.
*
* @see sortVar
* @see sortParam
* @see defaultOrder
*/
public $params;
......@@ -247,14 +239,13 @@ class Sort extends Object
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
if (isset($params[$this->sortVar]) && is_scalar($params[$this->sortVar])) {
$attributes = explode($this->separators[0], $params[$this->sortVar]);
if (isset($params[$this->sortParam]) && is_scalar($params[$this->sortParam])) {
$attributes = explode($this->separator, $params[$this->sortParam]);
foreach ($attributes as $attribute) {
$descending = false;
if (($pos = strrpos($attribute, $this->separators[1])) !== false) {
if ($descending = (substr($attribute, $pos + 1) === $this->descTag)) {
$attribute = substr($attribute, 0, $pos);
}
if (strncmp($attribute, '-', 1) === 0) {
$descending = true;
$attribute = substr($attribute, 1);
}
if (isset($this->attributes[$attribute])) {
......@@ -310,7 +301,7 @@ class Sort extends Object
}
$url = $this->createUrl($attribute);
$options['data-sort'] = $this->createSortVar($attribute);
$options['data-sort'] = $this->createSortParam($attribute);
if (isset($options['label'])) {
$label = $options['label'];
......@@ -343,7 +334,7 @@ class Sort extends Object
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
$params[$this->sortVar] = $this->createSortVar($attribute);
$params[$this->sortParam] = $this->createSortParam($attribute);
$route = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
$urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
if ($absolute) {
......@@ -361,7 +352,7 @@ class Sort extends Object
* @return string the value of the sort variable
* @throws InvalidConfigException if the specified attribute is not defined in [[attributes]]
*/
public function createSortVar($attribute)
public function createSortParam($attribute)
{
if (!isset($this->attributes[$attribute])) {
throw new InvalidConfigException("Unknown attribute: $attribute");
......@@ -383,9 +374,9 @@ class Sort extends Object
$sorts = [];
foreach ($directions as $attribute => $direction) {
$sorts[] = $direction === SORT_DESC ? $attribute . $this->separators[1] . $this->descTag : $attribute;
$sorts[] = $direction === SORT_DESC ? '-' . $attribute : $attribute;
}
return implode($this->separators[0], $sorts);
return implode($this->separator, $sorts);
}
/**
......
......@@ -64,25 +64,31 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*/
public function all($db = null)
{
$command = $this->createCommand($db);
$rows = $command->queryAll();
if (!empty($rows)) {
$models = $this->createModels($rows);
if (!empty($this->join) && $this->indexBy === null) {
$models = $this->removeDuplicatedModels($models);
}
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
} else {
return parent::all($db);
}
/**
* @inheritdoc
*/
public function prepareResult($rows)
{
if (empty($rows)) {
return [];
}
$models = $this->createModels($rows);
if (!empty($this->join) && $this->indexBy === null) {
$models = $this->removeDuplicatedModels($models);
}
if (!empty($this->with)) {
$this->findWith($this->with, $models);
}
if (!$this->asArray) {
foreach($models as $model) {
$model->afterFind();
}
}
return $models;
}
/**
......
......@@ -337,12 +337,12 @@ class ActiveRecord extends BaseActiveRecord
try {
$result = $this->insertInternal($attributes);
if ($result === false) {
$transaction->rollback();
$transaction->rollBack();
} else {
$transaction->commit();
}
} catch (\Exception $e) {
$transaction->rollback();
$transaction->rollBack();
throw $e;
}
} else {
......@@ -449,12 +449,12 @@ class ActiveRecord extends BaseActiveRecord
try {
$result = $this->updateInternal($attributes);
if ($result === false) {
$transaction->rollback();
$transaction->rollBack();
} else {
$transaction->commit();
}
} catch (\Exception $e) {
$transaction->rollback();
$transaction->rollBack();
throw $e;
}
} else {
......@@ -505,14 +505,14 @@ class ActiveRecord extends BaseActiveRecord
}
if ($transaction !== null) {
if ($result === false) {
$transaction->rollback();
$transaction->rollBack();
} else {
$transaction->commit();
}
}
} catch (\Exception $e) {
if ($transaction !== null) {
$transaction->rollback();
$transaction->rollBack();
}
throw $e;
}
......
......@@ -238,7 +238,8 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\".");
}
}
return $this->_related[$name] = $value->multiple ? $value->all() : $value->one();
$this->populateRelation($name, $value->multiple ? $value->all() : $value->one());
return $this->_related[$name];
}
return $value;
}
......@@ -1123,7 +1124,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
/** @var $viaClass ActiveRecord */
/** @var $record ActiveRecord */
$record = new $viaClass();
foreach($columns as $column => $value) {
foreach ($columns as $column => $value) {
$record->$column = $value;
}
$record->insert(false);
......@@ -1285,4 +1286,44 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
return false;
}
}
/**
* Returns the text label for the specified attribute.
* If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
* @param string $attribute the attribute name
* @return string the attribute label
* @see generateAttributeLabel()
* @see attributeLabels()
*/
public function getAttributeLabel($attribute)
{
$labels = $this->attributeLabels();
if (isset($labels[$attribute])) {
return ($labels[$attribute]);
} elseif (strpos($attribute, '.')) {
$attributeParts = explode('.', $attribute);
$neededAttribute = array_pop($attributeParts);
$relatedModel = $this;
foreach ($attributeParts as $relationName) {
if (isset($this->_related[$relationName]) && $this->_related[$relationName] instanceof self) {
$relatedModel = $this->_related[$relationName];
} else {
try {
$relation = $relatedModel->getRelation($relationName);
} catch (InvalidParamException $e) {
return $this->generateAttributeLabel($attribute);
}
$relatedModel = new $relation->modelClass;
}
}
$labels = $relatedModel->attributeLabels();
if (isset($labels[$neededAttribute])) {
return $labels[$neededAttribute];
}
}
return $this->generateAttributeLabel($attribute);
}
}
<?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\Object;
/**
* BatchQueryResult represents a batch query from which you can retrieve data in batches.
*
* You usually do not instantiate BatchQueryResult directly. Instead, you obtain it by
* calling [[Query::batch()]] or [[Query::each()]]. Because BatchQueryResult implements the `Iterator` interface,
* you can iterate it to obtain a batch of data in each iteration. For example,
*
* ```php
* $query = (new Query)->from('tbl_user');
* foreach ($query->batch() as $i => $users) {
* // $users represents the rows in the $i-th batch
* }
* foreach ($query->each() as $user) {
* }
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class BatchQueryResult extends Object implements \Iterator
{
/**
* @var Connection the DB connection to be used when performing batch query.
* If null, the "db" application component will be used.
*/
public $db;
/**
* @var Query the query object associated with this batch query.
* Do not modify this property directly unless after [[reset()]] is called explicitly.
*/
public $query;
/**
* @var integer the number of rows to be returned in each batch.
*/
public $batchSize = 100;
/**
* @var boolean whether to return a single row during each iteration.
* If false, a whole batch of rows will be returned in each iteration.
*/
public $each = false;
/**
* @var DataReader the data reader associated with this batch query.
*/
private $_dataReader;
/**
* @var array the data retrieved in the current batch
*/
private $_batch;
/**
* @var mixed the value for the current iteration
*/
private $_value;
/**
* @var string|integer the key for the current iteration
*/
private $_key;
/**
* Destructor.
*/
public function __destruct()
{
// make sure cursor is closed
$this->reset();
}
/**
* Resets the batch query.
* This method will clean up the existing batch query so that a new batch query can be performed.
*/
public function reset()
{
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}
/**
* Resets the iterator to the initial state.
* This method is required by the interface Iterator.
*/
public function rewind()
{
$this->reset();
$this->next();
}
/**
* Moves the internal pointer to the next dataset.
* This method is required by the interface Iterator.
*/
public function next()
{
if ($this->_batch === null || !$this->each || $this->each && next($this->_batch) === false) {
$this->_batch = $this->fetchData();
}
if ($this->each) {
$this->_value = current($this->_batch);
if ($this->query->indexBy !== null) {
$this->_key = key($this->_batch);
} elseif (key($this->_batch) !== null) {
$this->_key++;
} else {
$this->_key = null;
}
} else {
$this->_value = $this->_batch;
$this->_key = $this->_key === null ? 0 : $this->_key + 1;
}
}
/**
* Fetches the next batch of data.
* @return array the data fetched
*/
protected function fetchData()
{
if ($this->_dataReader === null) {
$this->_dataReader = $this->query->createCommand($this->db)->query();
}
$rows = [];
$count = 0;
while ($count++ < $this->batchSize && ($row = $this->_dataReader->read())) {
$rows[] = $row;
}
return $this->query->prepareResult($rows);
}
/**
* Returns the index of the current dataset.
* This method is required by the interface Iterator.
* @return integer the index of the current row.
*/
public function key()
{
return $this->_key;
}
/**
* Returns the current dataset.
* This method is required by the interface Iterator.
* @return mixed the current dataset.
*/
public function current()
{
return $this->_value;
}
/**
* Returns whether there is a valid dataset at the current position.
* This method is required by the interface Iterator.
* @return boolean whether there is a valid dataset at the current position.
*/
public function valid()
{
return !empty($this->_batch);
}
}
......@@ -700,7 +700,7 @@ class Command extends \yii\base\Component
* Creates a SQL command for creating a new index.
* @param string $name the name of the index. The name will be properly quoted by the method.
* @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
* @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
* @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
* by commas. The column names will be properly quoted by the method.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
* @return Command the command object itself
......
......@@ -68,7 +68,7 @@ use yii\caching\Cache;
* // ... executing other SQL statements ...
* $transaction->commit();
* } catch(Exception $e) {
* $transaction->rollback();
* $transaction->rollBack();
* }
* ~~~
*
......@@ -249,6 +249,11 @@ class Connection extends Component
*/
public $pdoClass;
/**
* @var boolean whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint).
* Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect.
*/
public $enableSavepoint = true;
/**
* @var Transaction the currently active transaction
*/
private $_transaction;
......@@ -396,7 +401,7 @@ class Connection extends Component
*/
public function getTransaction()
{
return $this->_transaction && $this->_transaction->isActive ? $this->_transaction : null;
return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
}
/**
......@@ -406,9 +411,12 @@ class Connection extends Component
public function beginTransaction()
{
$this->open();
$this->_transaction = new Transaction(['db' => $this]);
$this->_transaction->begin();
return $this->_transaction;
if (($transaction = $this->getTransaction()) === null) {
$transaction = $this->_transaction = new Transaction(['db' => $this]);
}
$transaction->begin();
return $transaction;
}
/**
......
......@@ -64,14 +64,14 @@ class Migration extends \yii\base\Component
$transaction = $this->db->beginTransaction();
try {
if ($this->safeUp() === false) {
$transaction->rollback();
$transaction->rollBack();
return false;
}
$transaction->commit();
} catch (\Exception $e) {
echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
$transaction->rollback();
$transaction->rollBack();
return false;
}
return null;
......@@ -89,14 +89,14 @@ class Migration extends \yii\base\Component
$transaction = $this->db->beginTransaction();
try {
if ($this->safeDown() === false) {
$transaction->rollback();
$transaction->rollBack();
return false;
}
$transaction->commit();
} catch (\Exception $e) {
echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
$transaction->rollback();
$transaction->rollBack();
return false;
}
return null;
......@@ -388,15 +388,15 @@ class Migration extends \yii\base\Component
* Builds and executes a SQL statement for creating a new index.
* @param string $name the name of the index. The name will be properly quoted by the method.
* @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
* @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them
* @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
* by commas or use an array. The column names will be properly quoted by the method.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
*/
public function createIndex($name, $table, $column, $unique = false)
public function createIndex($name, $table, $columns, $unique = false)
{
echo " > create" . ($unique ? ' unique' : '') . " index $name on $table (" . implode(',', (array)$column) . ") ...";
echo " > create" . ($unique ? ' unique' : '') . " index $name on $table (" . implode(',', (array)$columns) . ") ...";
$time = microtime(true);
$this->db->createCommand()->createIndex($name, $table, $column, $unique)->execute();
$this->db->createCommand()->createIndex($name, $table, $columns, $unique)->execute();
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
......
......@@ -124,6 +124,65 @@ class Query extends Component implements QueryInterface
}
/**
* Starts a batch query.
*
* A batch query supports fetching data in batches, which can keep the memory usage under a limit.
* This method will return a [[BatchQueryResult]] object which implements the `Iterator` interface
* and can be traversed to retrieve the data in batches.
*
* For example,
*
* ```php
* $query = (new Query)->from('tbl_user');
* foreach ($query->batch() as $rows) {
* // $rows is an array of 10 or fewer rows from tbl_user
* }
* ```
*
* @param integer $batchSize the number of records to be fetched in each batch.
* @param Connection $db the database connection. If not set, the "db" application component will be used.
* @return BatchQueryResult the batch query result. It implements the `Iterator` interface
* and can be traversed to retrieve the data in batches.
*/
public function batch($batchSize = 100, $db = null)
{
return Yii::createObject([
'class' => BatchQueryResult::className(),
'query' => $this,
'batchSize' => $batchSize,
'db' => $db,
'each' => false,
]);
}
/**
* Starts a batch query and retrieves data row by row.
* This method is similar to [[batch()]] except that in each iteration of the result,
* only one row of data is returned. For example,
*
* ```php
* $query = (new Query)->from('tbl_user');
* foreach ($query->each() as $row) {
* }
* ```
*
* @param integer $batchSize the number of records to be fetched in each batch.
* @param Connection $db the database connection. If not set, the "db" application component will be used.
* @return BatchQueryResult the batch query result. It implements the `Iterator` interface
* and can be traversed to retrieve the data in batches.
*/
public function each($batchSize = 100, $db = null)
{
return Yii::createObject([
'class' => BatchQueryResult::className(),
'query' => $this,
'batchSize' => $batchSize,
'db' => $db,
'each' => true,
]);
}
/**
* Executes the query and returns all results as an array.
* @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.
......@@ -132,6 +191,18 @@ class Query extends Component implements QueryInterface
public function all($db = null)
{
$rows = $this->createCommand($db)->queryAll();
return $this->prepareResult($rows);
}
/**
* Converts the raw query results into the format as specified by this query.
* This method is internally used to convert the data fetched from database
* into the format as required by this query.
* @param array $rows the raw query result from database
* @return array the converted query result
*/
public function prepareResult($rows)
{
if ($this->indexBy === null) {
return $rows;
}
......
......@@ -290,6 +290,41 @@ abstract class Schema extends Object
}
/**
* @return boolean whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
*/
public function supportsSavepoint()
{
return $this->db->enableSavepoint;
}
/**
* Creates a new savepoint.
* @param string $name the savepoint name
*/
public function createSavepoint($name)
{
$this->db->createCommand("SAVEPOINT $name")->execute();
}
/**
* Releases an existing savepoint.
* @param string $name the savepoint name
*/
public function releaseSavepoint($name)
{
$this->db->createCommand("RELEASE SAVEPOINT $name")->execute();
}
/**
* Rolls back to a previously created savepoint.
* @param string $name the savepoint name
*/
public function rollBackSavepoint($name)
{
$this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute();
}
/**
* Quotes a string value for use in a query.
* Note that if the parameter is not a string, it will be returned without change.
* @param string $str string to be quoted
......
......@@ -7,6 +7,7 @@
namespace yii\db;
use Yii;
use yii\base\InvalidConfigException;
/**
......@@ -25,12 +26,12 @@ use yii\base\InvalidConfigException;
* //.... other SQL executions
* $transaction->commit();
* } catch(Exception $e) {
* $transaction->rollback();
* $transaction->rollBack();
* }
* ~~~
*
* @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]]
* or [[rollback()]]. This property is read-only.
* or [[rollBack()]]. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
......@@ -42,19 +43,18 @@ class Transaction extends \yii\base\Object
*/
public $db;
/**
* @var boolean whether this transaction is active. Only an active transaction
* can [[commit()]] or [[rollback()]]. This property is set true when the transaction is started.
* @var integer the nesting level of the transaction. 0 means the outermost level.
*/
private $_active = false;
private $_level = 0;
/**
* Returns a value indicating whether this transaction is active.
* @return boolean whether this transaction is active. Only an active transaction
* can [[commit()]] or [[rollback()]].
* can [[commit()]] or [[rollBack()]].
*/
public function getIsActive()
{
return $this->_active;
return $this->_level > 0 && $this->db && $this->db->isActive;
}
/**
......@@ -63,44 +63,79 @@ class Transaction extends \yii\base\Object
*/
public function begin()
{
if (!$this->_active) {
if ($this->db === null) {
throw new InvalidConfigException('Transaction::db must be set.');
}
\Yii::trace('Starting transaction', __METHOD__);
$this->db->open();
if ($this->db === null) {
throw new InvalidConfigException('Transaction::db must be set.');
}
$this->db->open();
if ($this->_level == 0) {
Yii::trace('Begin transaction', __METHOD__);
$this->db->pdo->beginTransaction();
$this->_active = true;
$this->_level = 1;
return;
}
$schema = $this->db->getSchema();
if ($schema->supportsSavepoint()) {
Yii::trace('Set savepoint ' . $this->_level, __METHOD__);
$schema->createSavepoint('LEVEL' . $this->_level);
} else {
Yii::info('Transaction not started: nested transaction not supported', __METHOD__);
}
$this->_level++;
}
/**
* Commits a transaction.
* @throws Exception if the transaction or the [[db|DB connection]] is not active.
* @throws Exception if the transaction is not active
*/
public function commit()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Committing transaction', __METHOD__);
if (!$this->getIsActive()) {
throw new Exception('Failed to commit transaction: transaction was inactive.');
}
$this->_level--;
if ($this->_level == 0) {
Yii::trace('Commit transaction', __METHOD__);
$this->db->pdo->commit();
$this->_active = false;
return;
}
$schema = $this->db->getSchema();
if ($schema->supportsSavepoint()) {
Yii::trace('Release savepoint ' . $this->_level, __METHOD__);
$schema->releaseSavepoint('LEVEL' . $this->_level);
} else {
throw new Exception('Failed to commit transaction: transaction was inactive.');
Yii::info('Transaction not committed: nested transaction not supported', __METHOD__);
}
}
/**
* Rolls back a transaction.
* @throws Exception if the transaction or the [[db|DB connection]] is not active.
* @throws Exception if the transaction is not active
*/
public function rollback()
public function rollBack()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Rolling back transaction', __METHOD__);
if (!$this->getIsActive()) {
throw new Exception('Failed to roll back transaction: transaction was inactive.');
}
$this->_level--;
if ($this->_level == 0) {
Yii::trace('Roll back transaction', __METHOD__);
$this->db->pdo->rollBack();
$this->_active = false;
return;
}
$schema = $this->db->getSchema();
if ($schema->supportsSavepoint()) {
Yii::trace('Roll back to savepoint ' . $this->_level, __METHOD__);
$schema->rollBackSavepoint('LEVEL' . $this->_level);
} else {
throw new Exception('Failed to roll back transaction: transaction was inactive.');
Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__);
// throw an exception to fail the outer transaction
throw new Exception('Roll back failed: nested transaction not supported.');
}
}
}
......@@ -64,6 +64,15 @@ class Schema extends \yii\db\Schema
'enum' => self::TYPE_STRING,
];
/**
* @inheritdoc
*/
public function releaseSavepoint($name)
{
// does nothing as cubrid does not support this
}
/**
* Quotes a table name for use in a query.
* A simple table name has no schema prefix.
......
......@@ -51,7 +51,7 @@ class PDO extends \PDO
/**
* Rollbacks a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
* natively support transactions.
* @return boolean the result of a transaction rollback.
* @return boolean the result of a transaction roll back.
*/
public function rollBack()
{
......
......@@ -74,6 +74,30 @@ class Schema extends \yii\db\Schema
];
/**
* @inheritdoc
*/
public function createSavepoint($name)
{
$this->db->createCommand("SAVE TRANSACTION $name")->execute();
}
/**
* @inheritdoc
*/
public function releaseSavepoint($name)
{
// does nothing as MSSQL does not support this
}
/**
* @inheritdoc
*/
public function rollBackSavepoint($name)
{
$this->db->createCommand("ROLLBACK TRANSACTION $name")->execute();
}
/**
* Quotes a table name for use in a query.
* A simple table name has no schema prefix.
* @param string $name table name.
......
......@@ -34,6 +34,14 @@ class Schema extends \yii\db\Schema
/**
* @inheritdoc
*/
public function releaseSavepoint($name)
{
// does nothing as Oracle does not support this
}
/**
* @inheritdoc
*/
public function quoteSimpleTableName($name)
{
return '"' . $name . '"';
......
......@@ -123,7 +123,7 @@ class ActionColumn extends Column
if ($this->urlCreator instanceof Closure) {
return call_user_func($this->urlCreator, $action, $model, $key, $index);
} else {
$params = is_array($key) ? $key : ['id' => $key];
$params = is_array($key) ? $key : ['id' => (string)$key];
$route = $this->controller ? $this->controller . '/' . $action : $action;
return Yii::$app->controller->createUrl($route, $params);
}
......
......@@ -379,7 +379,7 @@ class GridView extends BaseListView
} else {
$options = $this->rowOptions;
}
$options['data-key'] = is_array($key) ? json_encode($key) : $key;
$options['data-key'] = is_array($key) ? json_encode($key) : (string)$key;
return Html::tag('tr', implode('', $cells), $options);
}
......
......@@ -223,7 +223,7 @@ class BaseHtml
* @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive).
* Since most browsers only support "post" and "get", if other methods are given, they will
* be simulated using "post", and a hidden input will be added which contains the actual method type.
* See [[\yii\web\Request::methodVar]] for more details.
* See [[\yii\web\Request::methodParam]] for more details.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* 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.
......@@ -240,11 +240,11 @@ class BaseHtml
if ($request instanceof Request) {
if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) {
// simulate PUT, DELETE, etc. via POST
$hiddenInputs[] = static::hiddenInput($request->methodVar, $method);
$hiddenInputs[] = static::hiddenInput($request->methodParam, $method);
$method = 'post';
}
if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) {
$hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken());
$hiddenInputs[] = static::hiddenInput($request->csrfParam, $request->getCsrfToken());
}
}
......
......@@ -335,14 +335,19 @@ class BaseSecurity
throw new InvalidParamException('Cost must be between 4 and 31.');
}
// Get 20 * 8bits of pseudo-random entropy from mt_rand().
$rand = '';
for ($i = 0; $i < 20; ++$i) {
$rand .= chr(mt_rand(0, 255));
// Get 20 * 8bits of random entropy
if (function_exists('openssl_random_pseudo_bytes')) {
// https://github.com/yiisoft/yii2/pull/2422
$rand = openssl_random_pseudo_bytes(20);
} else {
$rand = '';
for ($i = 0; $i < 20; ++$i) {
$rand .= chr(mt_rand(0, 255));
}
}
// Add the microtime for a little more entropy.
$rand .= microtime();
$rand .= microtime(true);
// Mix the bits cryptographically into a 20-byte binary string.
$rand = sha1($rand, true);
// Form the prefix that specifies Blowfish algorithm and cost parameter.
......
......@@ -57,7 +57,6 @@ use yii\helpers\StringHelper;
* @property string $pathInfo Part of the request URL that is after the entry script and before the question
* mark. Note, the returned path info is already URL-decoded.
* @property integer $port Port number for insecure requests.
* @property array $postParams The request POST parameter values.
* @property array $queryParams The request GET parameter values.
* @property string $queryString Part of the request URL that is after the question mark. This property is
* read-only.
......@@ -95,10 +94,10 @@ class Request extends \yii\base\Request
* from the same application. If not, a 400 HTTP exception will be raised.
*
* Note, this feature requires that the user client accepts cookie. Also, to use this feature,
* forms submitted via POST method must contain a hidden input whose name is specified by [[csrfVar]].
* forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
* You may use [[\yii\web\Html::beginForm()]] to generate his hidden input.
*
* In JavaScript, you may get the values of [[csrfVar]] and [[csrfToken]] via `yii.getCsrfParam()` and
* In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
* `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
*
* @see Controller::enableCsrfValidation
......@@ -109,7 +108,7 @@ class Request extends \yii\base\Request
* @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
* This property is used only when [[enableCsrfValidation]] is true.
*/
public $csrfVar = '_csrf';
public $csrfParam = '_csrf';
/**
* @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
* @see Cookie
......@@ -125,7 +124,7 @@ class Request extends \yii\base\Request
* @see getMethod()
* @see getBodyParams()
*/
public $methodVar = '_method';
public $methodParam = '_method';
/**
* @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
* The array keys are the request `Content-Types`, and the array values are the
......@@ -147,13 +146,16 @@ class Request extends \yii\base\Request
*/
public $parsers = [];
/**
* @var CookieCollection Collection of request cookies.
*/
private $_cookies;
/**
* @var array the headers in this collection (indexed by the header names)
*/
private $_headers;
/**
* Resolves the current request into a route and the associated parameters.
* @return array the first element is the route, and the second is the associated parameters.
......@@ -208,8 +210,8 @@ class Request extends \yii\base\Request
*/
public function getMethod()
{
if (isset($_POST[$this->methodVar])) {
return strtoupper($_POST[$this->methodVar]);
if (isset($_POST[$this->methodParam])) {
return strtoupper($_POST[$this->methodParam]);
} elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
} else {
......@@ -331,9 +333,9 @@ class Request extends \yii\base\Request
{
if ($this->_bodyParams === null) {
$contentType = $this->getContentType();
if (isset($_POST[$this->methodVar])) {
if (isset($_POST[$this->methodParam])) {
$this->_bodyParams = $_POST;
unset($this->_bodyParams[$this->methodVar]);
unset($this->_bodyParams[$this->methodParam]);
} elseif (isset($this->parsers[$contentType])) {
$parser = Yii::createObject($this->parsers[$contentType]);
if (!($parser instanceof RequestParserInterface)) {
......@@ -1103,7 +1105,7 @@ class Request extends \yii\base\Request
public function getRawCsrfToken()
{
if ($this->_csrfCookie === null) {
$this->_csrfCookie = $this->getCookies()->get($this->csrfVar);
$this->_csrfCookie = $this->getCookies()->get($this->csrfParam);
if ($this->_csrfCookie === null) {
$this->_csrfCookie = $this->createCsrfCookie();
Yii::$app->getResponse()->getCookies()->add($this->_csrfCookie);
......@@ -1175,7 +1177,7 @@ class Request extends \yii\base\Request
protected function createCsrfCookie()
{
$options = $this->csrfCookie;
$options['name'] = $this->csrfVar;
$options['name'] = $this->csrfParam;
$options['value'] = Security::generateRandomKey();
return new Cookie($options);
}
......@@ -1194,8 +1196,8 @@ class Request extends \yii\base\Request
if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
return true;
}
$trueToken = $this->getCookies()->getValue($this->csrfVar);
$token = $this->getBodyParam($this->csrfVar);
$trueToken = $this->getCookies()->getValue($this->csrfParam);
$token = $this->getBodyParam($this->csrfParam);
return $this->validateCsrfTokenInternal($token, $trueToken)
|| $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
}
......
......@@ -81,7 +81,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
/**
* @var string the name of the session variable that stores the flash message data.
*/
public $flashVar = '__flash';
public $flashParam = '__flash';
/**
* @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
*/
......@@ -569,7 +569,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
protected function updateFlashCounters()
{
$counters = $this->get($this->flashVar, []);
$counters = $this->get($this->flashParam, []);
if (is_array($counters)) {
foreach ($counters as $key => $count) {
if ($count) {
......@@ -578,10 +578,10 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
$counters[$key]++;
}
}
$_SESSION[$this->flashVar] = $counters;
$_SESSION[$this->flashParam] = $counters;
} else {
// fix the unexpected problem that flashVar doesn't return an array
unset($_SESSION[$this->flashVar]);
// fix the unexpected problem that flashParam doesn't return an array
unset($_SESSION[$this->flashParam]);
}
}
......@@ -596,7 +596,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function getFlash($key, $defaultValue = null, $delete = false)
{
$counters = $this->get($this->flashVar, []);
$counters = $this->get($this->flashParam, []);
if (isset($counters[$key])) {
$value = $this->get($key, $defaultValue);
if ($delete) {
......@@ -614,7 +614,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function getAllFlashes()
{
$counters = $this->get($this->flashVar, []);
$counters = $this->get($this->flashParam, []);
$flashes = [];
foreach (array_keys($counters) as $key) {
if (isset($_SESSION[$key])) {
......@@ -634,10 +634,10 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function setFlash($key, $value = true)
{
$counters = $this->get($this->flashVar, []);
$counters = $this->get($this->flashParam, []);
$counters[$key] = 0;
$_SESSION[$key] = $value;
$_SESSION[$this->flashVar] = $counters;
$_SESSION[$this->flashParam] = $counters;
}
/**
......@@ -650,10 +650,10 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function removeFlash($key)
{
$counters = $this->get($this->flashVar, []);
$counters = $this->get($this->flashParam, []);
$value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
unset($counters[$key], $_SESSION[$key]);
$_SESSION[$this->flashVar] = $counters;
$_SESSION[$this->flashParam] = $counters;
return $value;
}
......@@ -665,11 +665,11 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
*/
public function removeAllFlashes()
{
$counters = $this->get($this->flashVar, []);
$counters = $this->get($this->flashParam, []);
foreach (array_keys($counters) as $key) {
unset($_SESSION[$key]);
}
unset($_SESSION[$this->flashVar]);
unset($_SESSION[$this->flashParam]);
}
/**
......
......@@ -105,9 +105,9 @@ class UrlManager extends Component
*/
public $showScriptName = true;
/**
* @var string the GET variable name for route. This property is used only if [[enablePrettyUrl]] is false.
* @var string the GET parameter name for route. This property is used only if [[enablePrettyUrl]] is false.
*/
public $routeVar = 'r';
public $routeParam = 'r';
/**
* @var Cache|string the cache object or the application component ID of the cache object.
* Compiled URL rules will be cached through this cache object, if it is available.
......@@ -217,7 +217,7 @@ class UrlManager extends Component
return [$pathInfo, []];
} else {
Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
$route = $request->getQueryParam($this->routeVar, '');
$route = $request->getQueryParam($this->routeParam, '');
if (is_array($route)) {
$route = '';
}
......@@ -235,7 +235,7 @@ class UrlManager extends Component
public function createUrl($route, $params = [])
{
$anchor = isset($params['#']) ? '#' . $params['#'] : '';
unset($params['#'], $params[$this->routeVar]);
unset($params['#'], $params[$this->routeParam]);
$route = trim($route, '/');
$baseUrl = $this->getBaseUrl();
......@@ -264,7 +264,7 @@ class UrlManager extends Component
}
return "$baseUrl/{$route}{$anchor}";
} else {
$url = "$baseUrl?{$this->routeVar}=$route";
$url = "$baseUrl?{$this->routeParam}=$route";
if (!empty($params)) {
$url .= '&' . http_build_query($params);
}
......
......@@ -97,16 +97,16 @@ class User extends Component
/**
* @var string the session variable name used to store the value of [[id]].
*/
public $idVar = '__id';
public $idParam = '__id';
/**
* @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
* This is used when [[authTimeout]] is set.
*/
public $authTimeoutVar = '__expire';
public $authTimeoutParam = '__expire';
/**
* @var string the session variable name used to store the value of [[returnUrl]].
*/
public $returnUrlVar = '__returnUrl';
public $returnUrlParam = '__returnUrl';
private $_access = [];
......@@ -270,7 +270,7 @@ class User extends Component
*/
public function getId()
{
return Yii::$app->getSession()->get($this->idVar);
return Yii::$app->getSession()->get($this->idParam);
}
/**
......@@ -285,7 +285,7 @@ class User extends Component
*/
public function getReturnUrl($defaultUrl = null)
{
$url = Yii::$app->getSession()->get($this->returnUrlVar, $defaultUrl);
$url = Yii::$app->getSession()->get($this->returnUrlParam, $defaultUrl);
if (is_array($url)) {
if (isset($url[0])) {
$route = array_shift($url);
......@@ -309,7 +309,7 @@ class User extends Component
*/
public function setReturnUrl($url)
{
Yii::$app->getSession()->set($this->returnUrlVar, $url);
Yii::$app->getSession()->set($this->returnUrlParam, $url);
}
/**
......@@ -467,12 +467,12 @@ class User extends Component
$session->regenerateID(true);
}
$this->setIdentity($identity);
$session->remove($this->idVar);
$session->remove($this->authTimeoutVar);
$session->remove($this->idParam);
$session->remove($this->authTimeoutParam);
if ($identity instanceof IdentityInterface) {
$session->set($this->idVar, $identity->getId());
$session->set($this->idParam, $identity->getId());
if ($this->authTimeout !== null) {
$session->set($this->authTimeoutVar, time() + $this->authTimeout);
$session->set($this->authTimeoutParam, time() + $this->authTimeout);
}
if ($duration > 0 && $this->enableAutoLogin) {
$this->sendIdentityCookie($identity, $duration);
......@@ -491,11 +491,11 @@ class User extends Component
protected function renewAuthStatus()
{
if ($this->authTimeout !== null && !$this->getIsGuest()) {
$expire = Yii::$app->getSession()->get($this->authTimeoutVar);
$expire = Yii::$app->getSession()->get($this->authTimeoutParam);
if ($expire !== null && $expire < time()) {
$this->logout(false);
} else {
Yii::$app->getSession()->set($this->authTimeoutVar, time() + $this->authTimeout);
Yii::$app->getSession()->set($this->authTimeoutParam, time() + $this->authTimeout);
}
}
}
......
......@@ -457,7 +457,7 @@ class View extends \yii\base\View
$request = Yii::$app->getRequest();
if ($request instanceof \yii\web\Request && $request->enableCsrfValidation && !$request->getIsAjax()) {
$lines[] = Html::tag('meta', '', ['name' => 'csrf-param', 'content' => $request->csrfVar]);
$lines[] = Html::tag('meta', '', ['name' => 'csrf-param', 'content' => $request->csrfParam]);
$lines[] = Html::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]);
}
......
......@@ -102,7 +102,11 @@ class ActiveForm extends Widget
/**
* @var string the name of the GET parameter indicating the validation request is an AJAX request.
*/
public $ajaxVar = 'ajax';
public $ajaxParam = 'ajax';
/**
* @var string the type of data that you're expecting back from the server.
*/
public $ajaxDataType = 'json';
/**
* @var string|JsExpression a JS callback that will be called when the form is being submitted.
* The signature of the callback should be:
......@@ -186,7 +190,8 @@ class ActiveForm extends Widget
'errorCssClass' => $this->errorCssClass,
'successCssClass' => $this->successCssClass,
'validatingCssClass' => $this->validatingCssClass,
'ajaxVar' => $this->ajaxVar,
'ajaxParam' => $this->ajaxParam,
'ajaxDataType' => $this->ajaxDataType,
];
if ($this->validationUrl !== null) {
$options['validationUrl'] = Html::url($this->validationUrl);
......@@ -237,7 +242,7 @@ class ActiveForm extends Widget
}
if (!empty($lines)) {
$content = "<ul><li>" . implode("</li>\n<li>", $lines) . "</li><ul>";
$content = "<ul><li>" . implode("</li>\n<li>", $lines) . "</li></ul>";
return Html::tag('div', $header . $content . $footer, $options);
} else {
$content = "<ul></ul>";
......
......@@ -98,7 +98,7 @@ class ListView extends BaseListView
$options = $this->itemOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div');
if ($tag !== false) {
$options['data-key'] = is_array($key) ? json_encode($key) : $key;
$options['data-key'] = is_array($key) ? json_encode($key) : (string)$key;
return Html::tag($tag, $content, $options);
} else {
return $content;
......
......@@ -36,7 +36,7 @@ class SortTest extends TestCase
],
],
'params' => [
'sort' => 'age.name-desc'
'sort' => 'age,-name'
],
'enableMultiSort' => true,
]);
......@@ -64,7 +64,7 @@ class SortTest extends TestCase
],
],
'params' => [
'sort' => 'age.name-desc'
'sort' => 'age,-name'
],
'enableMultiSort' => true,
]);
......@@ -91,7 +91,7 @@ class SortTest extends TestCase
],
],
'params' => [
'sort' => 'age.name-desc'
'sort' => 'age,-name'
],
'enableMultiSort' => true,
]);
......@@ -101,7 +101,7 @@ class SortTest extends TestCase
$this->assertNull($sort->getAttributeOrder('xyz'));
}
public function testCreateSortVar()
public function testCreateSortParam()
{
$sort = new Sort([
'attributes' => [
......@@ -112,14 +112,14 @@ class SortTest extends TestCase
],
],
'params' => [
'sort' => 'age.name-desc'
'sort' => 'age,-name'
],
'enableMultiSort' => true,
'route' => 'site/index',
]);
$this->assertEquals('age-desc.name-desc', $sort->createSortVar('age'));
$this->assertEquals('name.age', $sort->createSortVar('name'));
$this->assertEquals('-age,-name', $sort->createSortParam('age'));
$this->assertEquals('name,age', $sort->createSortParam('name'));
}
public function testCreateUrl()
......@@ -138,15 +138,15 @@ class SortTest extends TestCase
],
],
'params' => [
'sort' => 'age.name-desc'
'sort' => 'age,-name'
],
'enableMultiSort' => true,
'urlManager' => $manager,
'route' => 'site/index',
]);
$this->assertEquals('/index.php?r=site/index&sort=age-desc.name-desc', $sort->createUrl('age'));
$this->assertEquals('/index.php?r=site/index&sort=name.age', $sort->createUrl('name'));
$this->assertEquals('/index.php?r=site/index&sort=-age%2C-name', $sort->createUrl('age'));
$this->assertEquals('/index.php?r=site/index&sort=name%2Cage', $sort->createUrl('name'));
}
public function testLink()
......@@ -166,13 +166,13 @@ class SortTest extends TestCase
],
],
'params' => [
'sort' => 'age.name-desc'
'sort' => 'age,-name'
],
'enableMultiSort' => true,
'urlManager' => $manager,
'route' => 'site/index',
]);
$this->assertEquals('<a class="asc" href="/index.php?r=site/index&amp;sort=age-desc.name-desc" data-sort="age-desc.name-desc">Age</a>', $sort->link('age'));
$this->assertEquals('<a class="asc" href="/index.php?r=site/index&amp;sort=-age%2C-name" data-sort="-age,-name">Age</a>', $sort->link('age'));
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\db;
use Yii;
use yiiunit\data\ar\ActiveRecord;
use yii\db\Query;
use yii\db\BatchQueryResult;
use yiiunit\data\ar\Customer;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class BatchQueryResultTest extends DatabaseTestCase
{
public function setUp()
{
parent::setUp();
ActiveRecord::$db = $this->getConnection();
}
public function testQuery()
{
$db = $this->getConnection();
// initialize property test
$query = new Query();
$query->from('tbl_customer')->orderBy('id');
$result = $query->batch(2, $db);
$this->assertTrue($result instanceof BatchQueryResult);
$this->assertEquals(2, $result->batchSize);
$this->assertTrue($result->query === $query);
// normal query
$query = new Query();
$query->from('tbl_customer')->orderBy('id');
$allRows = [];
$batch = $query->batch(2, $db);
foreach ($batch as $rows) {
$allRows = array_merge($allRows, $rows);
}
$this->assertEquals(3, count($allRows));
$this->assertEquals('user1', $allRows[0]['name']);
$this->assertEquals('user2', $allRows[1]['name']);
$this->assertEquals('user3', $allRows[2]['name']);
// rewind
$allRows = [];
foreach ($batch as $rows) {
$allRows = array_merge($allRows, $rows);
}
$this->assertEquals(3, count($allRows));
// reset
$batch->reset();
// empty query
$query = new Query();
$query->from('tbl_customer')->where(['id' => 100]);
$allRows = [];
$batch = $query->batch(2, $db);
foreach ($batch as $rows) {
$allRows = array_merge($allRows, $rows);
}
$this->assertEquals(0, count($allRows));
// query with index
$query = new Query();
$query->from('tbl_customer')->indexBy('name');
$allRows = [];
foreach ($query->batch(2, $db) as $rows) {
$allRows = array_merge($allRows, $rows);
}
$this->assertEquals(3, count($allRows));
$this->assertEquals('address1', $allRows['user1']['address']);
$this->assertEquals('address2', $allRows['user2']['address']);
$this->assertEquals('address3', $allRows['user3']['address']);
// each
$query = new Query();
$query->from('tbl_customer')->orderBy('id');
$allRows = [];
foreach ($query->each(100, $db) as $rows) {
$allRows[] = $rows;
}
$this->assertEquals(3, count($allRows));
$this->assertEquals('user1', $allRows[0]['name']);
$this->assertEquals('user2', $allRows[1]['name']);
$this->assertEquals('user3', $allRows[2]['name']);
// each with key
$query = new Query();
$query->from('tbl_customer')->orderBy('id')->indexBy('name');
$allRows = [];
foreach ($query->each(100, $db) as $key => $row) {
$allRows[$key] = $row;
}
$this->assertEquals(3, count($allRows));
$this->assertEquals('address1', $allRows['user1']['address']);
$this->assertEquals('address2', $allRows['user2']['address']);
$this->assertEquals('address3', $allRows['user3']['address']);
}
public function testActiveQuery()
{
$db = $this->getConnection();
$query = Customer::find()->orderBy('id');
$customers = [];
foreach ($query->batch(2, $db) as $models) {
$customers = array_merge($customers, $models);
}
$this->assertEquals(3, count($customers));
$this->assertEquals('user1', $customers[0]->name);
$this->assertEquals('user2', $customers[1]->name);
$this->assertEquals('user3', $customers[2]->name);
// batch with eager loading
$query = Customer::find()->with('orders')->orderBy('id');
$customers = [];
foreach ($query->batch(2, $db) as $models) {
$customers = array_merge($customers, $models);
foreach ($models as $model) {
$this->assertTrue($model->isRelationPopulated('orders'));
}
}
$this->assertEquals(3, count($customers));
$this->assertEquals(1, count($customers[0]->orders));
$this->assertEquals(2, count($customers[1]->orders));
$this->assertEquals(0, count($customers[2]->orders));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment