Commit 8bb3e07c by Klimov Paul

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

parents 60312771 d1e0a545
...@@ -11,6 +11,9 @@ return [ ...@@ -11,6 +11,9 @@ return [
'mail' => [ 'mail' => [
'class' => 'yii\swiftmailer\Mailer', 'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mail', 'viewPath' => '@common/mail',
// send all mails to a file by default. You have to set
// 'useFileTransport' to false and configure a transport
// for the mailer to send real emails.
'useFileTransport' => true, 'useFileTransport' => true,
], ],
], ],
......
...@@ -19,6 +19,9 @@ $config = [ ...@@ -19,6 +19,9 @@ $config = [
], ],
'mail' => [ 'mail' => [
'class' => 'yii\swiftmailer\Mailer', 'class' => 'yii\swiftmailer\Mailer',
// send all mails to a file by default. You have to set
// 'useFileTransport' to false and configure a transport
// for the mailer to send real emails.
'useFileTransport' => true, 'useFileTransport' => true,
], ],
'log' => [ 'log' => [
......
...@@ -35,8 +35,8 @@ Application Structure ...@@ -35,8 +35,8 @@ Application Structure
* [Applications](structure-applications.md) * [Applications](structure-applications.md)
* [Application Components](structure-application-components.md) * [Application Components](structure-application-components.md)
* [Controllers](structure-controllers.md) * [Controllers](structure-controllers.md)
* [Views](structure-views.md)
* [Models](structure-models.md) * [Models](structure-models.md)
* [Views](structure-views.md)
* **TBD** [Filters](structure-filters.md) * **TBD** [Filters](structure-filters.md)
* **TBD** [Widgets](structure-widgets.md) * **TBD** [Widgets](structure-widgets.md)
* **TBD** [Modules](structure-modules.md) * **TBD** [Modules](structure-modules.md)
...@@ -88,9 +88,9 @@ Getting Data from Users ...@@ -88,9 +88,9 @@ Getting Data from Users
----------------------- -----------------------
* [Creating Forms](input-forms.md) * [Creating Forms](input-forms.md)
* [Input Validation](input-validation.md) * [Validating Input](input-validation.md)
* **TBD** [Uploading Files](input-file-uploading.md) * **TBD** [Uploading Files](input-file-upload.md)
* **TBD** [Inputs for Multiple Models](input-multiple-models.md) * **TBD** [Getting Data for Multiple Models](input-multiple-models.md)
Displaying Data Displaying Data
......
...@@ -8,8 +8,56 @@ pass them to [models](structure-models.md), inject model results into [views](st ...@@ -8,8 +8,56 @@ pass them to [models](structure-models.md), inject model results into [views](st
and finally generate outgoing responses. and finally generate outgoing responses.
Controllers are composed by *actions* which are the most basic units that end users can address and request for Controllers are composed by *actions* which are the most basic units that end users can address and request for
execution. A controller can have one or multiple actions. For example, you can have a `post` controller which execution. A controller can have one or multiple actions.
contains a `view` action. End users can request this `view` action which may display a requested post.
The following example shows a `post` controller with two actions: `view` and `create`:
```php
namespace app\controllers;
use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model !== null) {
return $this->render('view', [
'model' => $model,
]);
} else {
throw new NotFoundHttpException;
}
}
public function actionCreate()
{
$model = new Post;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
}
```
In the `view` action (defined by the `actionView()` method), the code first loads the [model](structure-models.md)
according to the requested model ID; If the model is loaded successfully, it will display it using
a [view](structure-views.md) named `view`. Otherwise, it will throw an exception.
In the `create` action (defined by the `actionCreate()` method), the code is similar. It first tries to populate
the [model](structure-models.md) using the request data and save the model. If both succeed it will redirect
the browser to the `view` action with the ID of the newly created model. Otherwise it will display
the `create` view through which users can provide the needed input.
## Routes <a name="routes"></a> ## Routes <a name="routes"></a>
...@@ -386,64 +434,13 @@ to fulfill the request: ...@@ -386,64 +434,13 @@ to fulfill the request:
## Best Practices <a name="best-practices"></a> ## Best Practices <a name="best-practices"></a>
In a well-designed application, controllers are often very thin with each action containing only a few lines of code. In a well-designed application, controllers are often very thin with each action containing only a few lines of code.
The main role of these code is to invoke appropriate [models](structure-models.md) with the request data If your controller is rather complicated, it usually indicates that you should refactor it and move some code
and use [views](structure-views.md) to present the models. to other classes.
The following code is a typical example showing how the `view` and `create` actions should be implemented
in a controller.
```php
namespace app\controllers;
use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model !== null) {
return $this->render('view', [
'model' => $model,
]);
} else {
throw new NotFoundHttpException;
}
}
public function actionCreate()
{
$model = new Post;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
}
```
In the `view` action, the code first loads the model according to the requested model ID; If the model
is loaded successfully, it will display it using the view named `view`. Otherwise, it will throw an exception.
In the `create` action, the code is similar. It first tries to populate the model using the request data
and save the model. If both succeed it will redirect the browser to the `view` action with the ID of
the newly created model. Otherwise it will display the `create` view through which users can provide the needed input.
In summary, a controller In summary, controllers
* may access the [request](runtime-requests.md) data; * may access the [request](runtime-requests.md) data;
* may send commands to [models](structure-models.md) and [views](structure-views.md); * may call methods of [models](structure-models.md) and other service components with request data;
* should return the [response](runtime-responses.md) data; * may use [views](structure-views.md) to compose responses;
* should NOT process the request data; * should NOT process the request data - this should be done in [models](structure-models.md);
* should NOT build the response data. * should avoid embedding HTML or other presentational code - this is better done in [views](structure-views.md).
If your controller is rather complicated, it usually indicates that you should refactor it and move some code
to other classes.
...@@ -4,11 +4,12 @@ Yii Framework 2 elasticsearch extension Change Log ...@@ -4,11 +4,12 @@ Yii Framework 2 elasticsearch extension Change Log
2.0.0-rc under development 2.0.0-rc under development
-------------------------- --------------------------
- Bug #3587: Fixed an issue with storing empty records (cebe)
- Enh #3520: Added `unlinkAll()`-method to active record to remove all records of a model relation (NmDimas, samdark, cebe)
- Enh #3527: Added `highlight` property to Query and ActiveRecord. (Borales)
- Chg: asArray in ActiveQuery is now equal to using the normal Query. This means, that the output structure has changed and `with` is supported anymore. (cebe) - Chg: asArray in ActiveQuery is now equal to using the normal Query. This means, that the output structure has changed and `with` is supported anymore. (cebe)
- Chg: Deletion of a record is now also considered successful if the record did not exist. (cebe) - Chg: Deletion of a record is now also considered successful if the record did not exist. (cebe)
- Chg: Requirement changes: Yii now requires elasticsearch version 1.0 or higher (cebe) - Chg: Requirement changes: Yii now requires elasticsearch version 1.0 or higher (cebe)
- Enh #3520: Added `unlinkAll()`-method to active record to remove all records of a model relation (NmDimas, samdark, cebe)
- Enh #3527: Added `highlight` property to Query and ActiveRecord. (Borales)
2.0.0-beta April 13, 2014 2.0.0-beta April 13, 2014
...@@ -27,7 +28,9 @@ Yii Framework 2 elasticsearch extension Change Log ...@@ -27,7 +28,9 @@ Yii Framework 2 elasticsearch extension Change Log
All relational queries are now directly served by `ActiveQuery` allowing to use All relational queries are now directly served by `ActiveQuery` allowing to use
custom scopes in relations (cebe) custom scopes in relations (cebe)
2.0.0-alpha, December 1, 2013 2.0.0-alpha, December 1, 2013
----------------------------- -----------------------------
- Initial release. - Initial release.
...@@ -80,7 +80,11 @@ class Command extends Component ...@@ -80,7 +80,11 @@ class Command extends Component
*/ */
public function insert($index, $type, $data, $id = null, $options = []) public function insert($index, $type, $data, $id = null, $options = [])
{ {
if (empty($data)) {
$body = '{}';
} else {
$body = is_array($data) ? Json::encode($data) : $data; $body = is_array($data) ? Json::encode($data) : $data;
}
if ($id !== null) { if ($id !== null) {
return $this->db->put([$index, $type, $id], $options, $body); return $this->db->put([$index, $type, $id], $options, $body);
......
...@@ -6,6 +6,7 @@ Yii Framework 2 gii extension Change Log ...@@ -6,6 +6,7 @@ Yii Framework 2 gii extension Change Log
- Bug #1263: Fixed the issue that Gii and Debug modules might be affected by incompatible asset manager configuration (qiangxue) - Bug #1263: Fixed the issue that Gii and Debug modules might be affected by incompatible asset manager configuration (qiangxue)
- Bug #3265: Fixed incorrect controller class name validation (suralc) - Bug #3265: Fixed incorrect controller class name validation (suralc)
- Bug #3693: Fixed broken Gii preview when a file is unchanged (cebe)
- Enh #2018: Search model is not required anymore in CRUD generator (johonunu) - Enh #2018: Search model is not required anymore in CRUD generator (johonunu)
- Enh #3088: The gii module will manage their own URL rules now (qiangxue) - Enh #3088: The gii module will manage their own URL rules now (qiangxue)
- Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2) - Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2)
......
...@@ -45,7 +45,14 @@ yii.gii = (function ($) { ...@@ -45,7 +45,14 @@ yii.gii = (function ($) {
$modal.find('.modal-title').text($link.data('title')); $modal.find('.modal-title').text($link.data('title'));
$modal.find('.modal-body').html('Loading ...'); $modal.find('.modal-body').html('Loading ...');
$modal.modal('show'); $modal.modal('show');
var checked = $('a.' + $modal.data('action') + '[href="' + $link.attr('href') + '"]').closest('tr').find('input').get(0).checked; var checkbox = $('a.' + $modal.data('action') + '[href="' + $link.attr('href') + '"]').closest('tr').find('input').get(0);
var checked = false;
if (checkbox) {
checked = checkbox.checked;
$modal.find('.modal-checkbox').removeClass('disabled');
} else {
$modal.find('.modal-checkbox').addClass('disabled');
}
$modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked); $modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked);
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
......
...@@ -10,7 +10,7 @@ namespace yii\base; ...@@ -10,7 +10,7 @@ namespace yii\base;
use yii\validators\Validator; use yii\validators\Validator;
/** /**
* DynamicModel is a model class primarily used to support ad-hoc data validation. * DynamicModel is a model class primarily used to support ad hoc data validation.
* *
* The typical usage of DynamicModel is as follows, * The typical usage of DynamicModel is as follows,
* *
......
...@@ -12,7 +12,7 @@ use yii\base\InvalidConfigException; ...@@ -12,7 +12,7 @@ use yii\base\InvalidConfigException;
use yii\helpers\Html; use yii\helpers\Html;
/** /**
* CompareValidator compares the specified attribute value with another value and validates if they are equal. * CompareValidator compares the specified attribute value with another value.
* *
* The value being compared with can be another attribute value * The value being compared with can be another attribute value
* (specified via [[compareAttribute]]) or a constant (specified via * (specified via [[compareAttribute]]) or a constant (specified via
...@@ -47,14 +47,14 @@ class CompareValidator extends Validator ...@@ -47,14 +47,14 @@ class CompareValidator extends Validator
/** /**
* @var string the operator for comparison. The following operators are supported: * @var string the operator for comparison. The following operators are supported:
* *
* - '==': validates to see if the two values are equal. The comparison is done is non-strict mode. * - `==`: check if two values are equal. The comparison is done is non-strict mode.
* - '===': validates to see if the two values are equal. The comparison is done is strict mode. * - `===`: check if two values are equal. The comparison is done is strict mode.
* - '!=': validates to see if the two values are NOT equal. The comparison is done is non-strict mode. * - `!=`: check if two values are NOT equal. The comparison is done is non-strict mode.
* - '!==': validates to see if the two values are NOT equal. The comparison is done is strict mode. * - `!==`: check if two values are NOT equal. The comparison is done is strict mode.
* - `>`: validates to see if the value being validated is greater than the value being compared with. * - `>`: check if value being validated is greater than the value being compared with.
* - `>=`: validates to see if the value being validated is greater than or equal to the value being compared with. * - `>=`: check if value being validated is greater than or equal to the value being compared with.
* - `<`: validates to see if the value being validated is less than the value being compared with. * - `<`: check if value being validated is less than the value being compared with.
* - `<=`: validates to see if the value being validated is less than or equal to the value being compared with. * - `<=`: check if value being validated is less than or equal to the value being compared with.
*/ */
public $operator = '=='; public $operator = '==';
/** /**
......
...@@ -32,6 +32,11 @@ class Order extends ActiveRecord ...@@ -32,6 +32,11 @@ class Order extends ActiveRecord
return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
} }
public function getOrderItemsWithNullFK()
{
return $this->hasMany(OrderItemWithNullFK::className(), ['order_id' => 'id']);
}
public function getItems() public function getItems()
{ {
return $this->hasMany(Item::className(), ['id' => 'item_id']) return $this->hasMany(Item::className(), ['id' => 'item_id'])
...@@ -72,7 +77,7 @@ class Order extends ActiveRecord ...@@ -72,7 +77,7 @@ class Order extends ActiveRecord
public function getBooksWithNullFK() public function getBooksWithNullFK()
{ {
return $this->hasMany(Item::className(), ['id' => 'item_id']) return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItemsWithNullFk') ->via('orderItemsWithNullFK')
->where(['category_id' => 1]); ->where(['category_id' => 1]);
} }
......
...@@ -186,6 +186,16 @@ class ActiveRecordTest extends ElasticSearchTestCase ...@@ -186,6 +186,16 @@ class ActiveRecordTest extends ElasticSearchTestCase
$db->createCommand()->flushIndex('yiitest'); $db->createCommand()->flushIndex('yiitest');
} }
public function testSaveNoChanges()
{
// this should not fail with exception
$customer = new Customer();
// insert
$customer->save(false);
// update
$customer->save(false);
}
public function testFindAsArray() public function testFindAsArray()
{ {
// asArray // asArray
......
...@@ -773,23 +773,23 @@ trait ActiveRecordTestTrait ...@@ -773,23 +773,23 @@ trait ActiveRecordTestTrait
/** @var Order $order */ /** @var Order $order */
$order = $orderClass::findOne(1); $order = $orderClass::findOne(1);
$this->assertEquals(2, count($order->books)); $this->assertEquals(2, count($order->books));
$this->assertEquals(6, $orderItemClass::find()->count()); $orderItemCount = $orderItemClass::find()->count();
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
$order->unlinkAll('books', true); $order->unlinkAll('books', true);
$this->afterSave(); $this->afterSave();
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
$this->assertEquals(4, $orderItemClass::find()->count()); $this->assertEquals($orderItemCount - 2, $orderItemClass::find()->count());
$this->assertEquals(0, count($order->books)); $this->assertEquals(0, count($order->books));
// via model without delete // via model without delete
$this->assertEquals(2, count($order->booksWithNullFK)); $this->assertEquals(2, count($order->booksWithNullFK));
$this->assertEquals(6, $orderItemsWithNullFKClass::find()->count()); $orderItemCount = $orderItemsWithNullFKClass::find()->count();
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
$order->unlinkAll('booksWithNullFK',false); $order->unlinkAll('booksWithNullFK',false);
$this->afterSave(); $this->afterSave();
$this->assertEquals(0, count($order->booksWithNullFK)); $this->assertEquals(0, count($order->booksWithNullFK));
$this->assertEquals(2,$orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); $this->assertEquals(2, $orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count());
$this->assertEquals(6, $orderItemsWithNullFKClass::find()->count()); $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count());
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
// via table is covered in \yiiunit\framework\db\ActiveRecordTest::testUnlinkAllViaTable() // via table is covered in \yiiunit\framework\db\ActiveRecordTest::testUnlinkAllViaTable()
......
...@@ -543,30 +543,30 @@ class ActiveRecordTest extends DatabaseTestCase ...@@ -543,30 +543,30 @@ class ActiveRecordTest extends DatabaseTestCase
/** @var \yii\db\ActiveRecordInterface $orderItemClass */ /** @var \yii\db\ActiveRecordInterface $orderItemClass */
$orderItemClass = $this->getOrderItemClass(); $orderItemClass = $this->getOrderItemClass();
/** @var \yii\db\ActiveRecordInterface $itemClass */ /** @var \yii\db\ActiveRecordInterface $itemClass */
$itemClass = $this->getOrderItemClass(); $itemClass = $this->getItemClass();
/** @var \yii\db\ActiveRecordInterface $orderItemsWithNullFKClass */ /** @var \yii\db\ActiveRecordInterface $orderItemsWithNullFKClass */
$orderItemsWithNullFKClass = $this->getOrderItemWithNullFKmClass(); $orderItemsWithNullFKClass = $this->getOrderItemWithNullFKmClass();
// via table with delete // via table with delete
/** @var Order $order */ /** @var Order $order */
$order = $orderClass::findOne(1); $order = $orderClass::findOne(1);
$this->assertEquals(2, count($order->books)); $this->assertEquals(2, count($order->booksViaTable));
$this->assertEquals(6, $orderItemClass::find()->count()); $orderItemCount = $orderItemClass::find()->count();
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
$order->unlinkAll('booksViaTable', true); $order->unlinkAll('booksViaTable', true);
$this->afterSave(); $this->afterSave();
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
$this->assertEquals(4, $orderItemClass::find()->count()); $this->assertEquals($orderItemCount - 2, $orderItemClass::find()->count());
$this->assertEquals(0, count($order->books)); $this->assertEquals(0, count($order->booksViaTable));
// via table without delete // via table without delete
$this->assertEquals(2, count($order->booksWithNullFK)); $this->assertEquals(2, count($order->booksWithNullFKViaTable));
$this->assertEquals(6, $orderItemsWithNullFKClass::find()->count()); $orderItemCount = $orderItemsWithNullFKClass::find()->count();
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
$order->unlinkAll('booksWithNullFKViaTable',false); $order->unlinkAll('booksWithNullFKViaTable',false);
$this->assertEquals(0, count($order->booksWithNullFK)); $this->assertEquals(0, count($order->booksWithNullFKViaTable));
$this->assertEquals(2,$orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count()); $this->assertEquals(2,$orderItemsWithNullFKClass::find()->where(['AND', ['item_id' => [1, 2]], ['order_id' => null]])->count());
$this->assertEquals(6, $orderItemsWithNullFKClass::find()->count()); $this->assertEquals($orderItemCount, $orderItemsWithNullFKClass::find()->count());
$this->assertEquals(5, $itemClass::find()->count()); $this->assertEquals(5, $itemClass::find()->count());
} }
} }
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