95 changed files with 4820 additions and 1691 deletions
@ -0,0 +1,23 @@ |
|||||||
|
# Autodetect text files |
||||||
|
* text=auto |
||||||
|
|
||||||
|
# ...Unless the name matches the following overriding patterns |
||||||
|
|
||||||
|
# Definitively text files |
||||||
|
*.php text |
||||||
|
*.css text |
||||||
|
*.js text |
||||||
|
*.txt text |
||||||
|
*.md text |
||||||
|
*.xml text |
||||||
|
*.json text |
||||||
|
*.bat text |
||||||
|
*.sql text |
||||||
|
*.xml text |
||||||
|
*.yml text |
||||||
|
|
||||||
|
# Ensure those won't be messed up with |
||||||
|
*.png binary |
||||||
|
*.jpg binary |
||||||
|
*.gif binary |
||||||
|
*.ttf binary |
@ -0,0 +1,5 @@ |
|||||||
|
<?php |
||||||
|
$I = new TestGuy($scenario); |
||||||
|
$I->wantTo('ensure that about works'); |
||||||
|
$I->amOnPage('?r=site/about'); |
||||||
|
$I->see('About', 'h1'); |
@ -0,0 +1,36 @@ |
|||||||
|
<?php |
||||||
|
$I = new TestGuy($scenario); |
||||||
|
$I->wantTo('ensure that contact works'); |
||||||
|
$I->amOnPage('?r=site/contact'); |
||||||
|
$I->see('Contact', 'h1'); |
||||||
|
|
||||||
|
$I->submitForm('#contact-form', array()); |
||||||
|
$I->see('Contact', 'h1'); |
||||||
|
$I->see('Name cannot be blank'); |
||||||
|
$I->see('Email cannot be blank'); |
||||||
|
$I->see('Subject cannot be blank'); |
||||||
|
$I->see('Body cannot be blank'); |
||||||
|
$I->see('The verification code is incorrect'); |
||||||
|
|
||||||
|
$I->submitForm('#contact-form', array( |
||||||
|
'ContactForm[name]' => 'tester', |
||||||
|
'ContactForm[email]' => 'tester.email', |
||||||
|
'ContactForm[subject]' => 'test subject', |
||||||
|
'ContactForm[body]' => 'test content', |
||||||
|
'ContactForm[verifyCode]' => 'testme', |
||||||
|
)); |
||||||
|
$I->dontSee('Name cannot be blank', '.help-inline'); |
||||||
|
$I->see('Email is not a valid email address.'); |
||||||
|
$I->dontSee('Subject cannot be blank', '.help-inline'); |
||||||
|
$I->dontSee('Body cannot be blank', '.help-inline'); |
||||||
|
$I->dontSee('The verification code is incorrect', '.help-inline'); |
||||||
|
|
||||||
|
$I->submitForm('#contact-form', array( |
||||||
|
'ContactForm[name]' => 'tester', |
||||||
|
'ContactForm[email]' => 'tester@example.com', |
||||||
|
'ContactForm[subject]' => 'test subject', |
||||||
|
'ContactForm[body]' => 'test content', |
||||||
|
'ContactForm[verifyCode]' => 'testme', |
||||||
|
)); |
||||||
|
$I->dontSeeElement('#contact-form'); |
||||||
|
$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); |
@ -0,0 +1,8 @@ |
|||||||
|
<?php |
||||||
|
$I = new TestGuy($scenario); |
||||||
|
$I->wantTo('ensure that home page works'); |
||||||
|
$I->amOnPage(''); |
||||||
|
$I->see('My Company'); |
||||||
|
$I->seeLink('About'); |
||||||
|
$I->click('About'); |
||||||
|
$I->see('This is the About page.'); |
@ -0,0 +1,23 @@ |
|||||||
|
<?php |
||||||
|
$I = new TestGuy($scenario); |
||||||
|
$I->wantTo('ensure that login works'); |
||||||
|
$I->amOnPage('?r=site/login'); |
||||||
|
$I->see('Login', 'h1'); |
||||||
|
|
||||||
|
$I->submitForm('#login-form', array()); |
||||||
|
$I->dontSee('Logout (admin)'); |
||||||
|
$I->see('Username cannot be blank'); |
||||||
|
$I->see('Password cannot be blank'); |
||||||
|
|
||||||
|
$I->submitForm('#login-form', array( |
||||||
|
'LoginForm[username]' => 'admin', |
||||||
|
'LoginForm[password]' => 'wrong', |
||||||
|
)); |
||||||
|
$I->dontSee('Logout (admin)'); |
||||||
|
$I->see('Incorrect username or password'); |
||||||
|
|
||||||
|
$I->submitForm('#login-form', array( |
||||||
|
'LoginForm[username]' => 'admin', |
||||||
|
'LoginForm[password]' => 'admin', |
||||||
|
)); |
||||||
|
$I->see('Logout (admin)'); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,602 @@ |
|||||||
|
Active Record |
||||||
|
============= |
||||||
|
|
||||||
|
ActiveRecord implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). |
||||||
|
The idea is that an ActiveRecord object is associated with a row in a database table so object properties are mapped |
||||||
|
to colums of the corresponding database row. For example, a `Customer` object is associated with a row in the |
||||||
|
`tbl_customer` table. |
||||||
|
|
||||||
|
Instead of writing raw SQL statements to access the data in the table, you can call intuitive methods available in the |
||||||
|
corresponding ActiveRecord class to achieve the same goals. For example, calling [[save()]] would insert or update a row |
||||||
|
in the underlying table: |
||||||
|
|
||||||
|
```php |
||||||
|
$customer = new Customer(); |
||||||
|
$customer->name = 'Qiang'; |
||||||
|
$customer->save(); |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
Declaring ActiveRecord Classes |
||||||
|
------------------------------ |
||||||
|
|
||||||
|
To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and |
||||||
|
implement `tableName` method like the following: |
||||||
|
|
||||||
|
```php |
||||||
|
class Customer extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @return string the name of the table associated with this ActiveRecord class. |
||||||
|
*/ |
||||||
|
public static function tableName() |
||||||
|
{ |
||||||
|
return 'tbl_customer'; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Connecting to Database |
||||||
|
---------------------- |
||||||
|
|
||||||
|
ActiveRecord relies on a [[Connection|DB connection]]. By default, it assumes that |
||||||
|
there is an application component named `db` that gives the needed [[Connection]] |
||||||
|
instance which serves as the DB connection. Usually this component is configured |
||||||
|
via application configuration like the following: |
||||||
|
|
||||||
|
```php |
||||||
|
return array( |
||||||
|
'components' => array( |
||||||
|
'db' => array( |
||||||
|
'class' => 'yii\db\Connection', |
||||||
|
'dsn' => 'mysql:host=localhost;dbname=testdb', |
||||||
|
'username' => 'demo', |
||||||
|
'password' => 'demo', |
||||||
|
// turn on schema caching to improve performance |
||||||
|
// 'schemaCacheDuration' => 3600, |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
Check [Database basics](database-basics.md) section in order to learn more on how to configure and use database |
||||||
|
connections. |
||||||
|
|
||||||
|
Getting Data from Database |
||||||
|
-------------------------- |
||||||
|
|
||||||
|
There are two ActiveRecord methods for getting data: |
||||||
|
|
||||||
|
- [[find()]] |
||||||
|
- [[findBySql()]] |
||||||
|
|
||||||
|
They both return an [[ActiveQuery]] instance. Coupled with the various customization and query methods |
||||||
|
provided by [[ActiveQuery]], ActiveRecord supports very flexible and powerful data retrieval approaches. |
||||||
|
|
||||||
|
The followings are some examples, |
||||||
|
|
||||||
|
```php |
||||||
|
// to retrieve all *active* customers and order them by their ID: |
||||||
|
$customers = Customer::find() |
||||||
|
->where(array('status' => $active)) |
||||||
|
->orderBy('id') |
||||||
|
->all(); |
||||||
|
|
||||||
|
// to return a single customer whose ID is 1: |
||||||
|
$customer = Customer::find() |
||||||
|
->where(array('id' => 1)) |
||||||
|
->one(); |
||||||
|
|
||||||
|
// or use the following shortcut approach: |
||||||
|
$customer = Customer::find(1); |
||||||
|
|
||||||
|
// to retrieve customers using a raw SQL statement: |
||||||
|
$sql = 'SELECT * FROM tbl_customer'; |
||||||
|
$customers = Customer::findBySql($sql)->all(); |
||||||
|
|
||||||
|
// to return the number of *active* customers: |
||||||
|
$count = Customer::find() |
||||||
|
->where(array('status' => $active)) |
||||||
|
->count(); |
||||||
|
|
||||||
|
// to return customers in terms of arrays rather than `Customer` objects: |
||||||
|
$customers = Customer::find()->asArray()->all(); |
||||||
|
// each $customers element is an array of name-value pairs |
||||||
|
|
||||||
|
// to index the result by customer IDs: |
||||||
|
$customers = Customer::find()->indexBy('id')->all(); |
||||||
|
// $customers array is indexed by customer IDs |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
Accessing Column Data |
||||||
|
--------------------- |
||||||
|
|
||||||
|
ActiveRecord maps each column of the corresponding database table row to an *attribute* in the ActiveRecord |
||||||
|
object. An attribute is like a regular object property whose name is the same as the corresponding column |
||||||
|
name and is case sensitive. |
||||||
|
|
||||||
|
To read the value of a column, we can use the following expression: |
||||||
|
|
||||||
|
```php |
||||||
|
// "id" is the name of a column in the table associated with $customer ActiveRecord object |
||||||
|
$id = $customer->id; |
||||||
|
// or alternatively, |
||||||
|
$id = $customer->getAttribute('id'); |
||||||
|
``` |
||||||
|
|
||||||
|
We can get all column values through the [[attributes]] property: |
||||||
|
|
||||||
|
```php |
||||||
|
$values = $customer->attributes; |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
Persisting Data to Database |
||||||
|
--------------------------- |
||||||
|
|
||||||
|
ActiveRecord provides the following methods to insert, update and delete data: |
||||||
|
|
||||||
|
- [[save()]] |
||||||
|
- [[insert()]] |
||||||
|
- [[update()]] |
||||||
|
- [[delete()]] |
||||||
|
- [[updateCounters()]] |
||||||
|
- [[updateAll()]] |
||||||
|
- [[updateAllCounters()]] |
||||||
|
- [[deleteAll()]] |
||||||
|
|
||||||
|
Note that [[updateAll()]], [[updateAllCounters()]] and [[deleteAll()]] apply to the whole database |
||||||
|
table, while the rest of the methods only apply to the row associated with the ActiveRecord object. |
||||||
|
|
||||||
|
The followings are some examples: |
||||||
|
|
||||||
|
```php |
||||||
|
// to insert a new customer record |
||||||
|
$customer = new Customer; |
||||||
|
$customer->name = 'James'; |
||||||
|
$customer->email = 'james@example.com'; |
||||||
|
$customer->save(); // equivalent to $customer->insert(); |
||||||
|
|
||||||
|
// to update an existing customer record |
||||||
|
$customer = Customer::find($id); |
||||||
|
$customer->email = 'james@example.com'; |
||||||
|
$customer->save(); // equivalent to $customer->update(); |
||||||
|
// Note that model attributes will be validated first and |
||||||
|
// model will not be saved unless it's valid. |
||||||
|
|
||||||
|
// to delete an existing customer record |
||||||
|
$customer = Customer::find($id); |
||||||
|
$customer->delete(); |
||||||
|
|
||||||
|
// to increment the age of all customers by 1 |
||||||
|
Customer::updateAllCounters(array('age' => 1)); |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
Getting Relational Data |
||||||
|
----------------------- |
||||||
|
|
||||||
|
Using ActiveRecord you can expose relationships as properties. For example, with an appropriate declaration, |
||||||
|
`$customer->orders` can return an array of `Order` objects which represent the orders placed by the specified customer. |
||||||
|
|
||||||
|
To declare a relationship, define a getter method which returns an [[ActiveRelation]] object. For example, |
||||||
|
|
||||||
|
```php |
||||||
|
class Customer extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
public function getOrders() |
||||||
|
{ |
||||||
|
return $this->hasMany('Order', array('customer_id' => 'id')); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Order extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
public function getCustomer() |
||||||
|
{ |
||||||
|
return $this->hasOne('Customer', array('id' => 'customer_id')); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Within the getter methods above, we call [[hasMany()]] or [[hasOne()]] methods to |
||||||
|
create a new [[ActiveRelation]] object. The [[hasMany()]] method declares |
||||||
|
a one-many relationship. For example, a customer has many orders. And the [[hasOne()]] |
||||||
|
method declares a many-one or one-one relationship. For example, an order has one customer. |
||||||
|
Both methods take two parameters: |
||||||
|
|
||||||
|
- `$class`: the name of the class related models should use. If specified without |
||||||
|
a namespace, the namespace will be taken from the declaring class. |
||||||
|
- `$link`: the association between columns from two tables. This should be given as an array. |
||||||
|
The keys of the array are the names of the columns from the table associated with `$class`, |
||||||
|
while the values of the array are the names of the columns from the declaring class. |
||||||
|
It is a good practice to define relationships based on table foreign keys. |
||||||
|
|
||||||
|
After declaring relationships getting relational data is as easy as accessing |
||||||
|
a component property that is defined by the getter method: |
||||||
|
|
||||||
|
```php |
||||||
|
// the orders of a customer |
||||||
|
$customer = Customer::find($id); |
||||||
|
$orders = $customer->orders; // $orders is an array of Order objects |
||||||
|
|
||||||
|
// the customer of the first order |
||||||
|
$customer2 = $orders[0]->customer; // $customer == $customer2 |
||||||
|
``` |
||||||
|
|
||||||
|
Because [[ActiveRelation]] extends from [[ActiveQuery]], it has the same query building methods, |
||||||
|
which allows us to customize the query for retrieving the related objects. |
||||||
|
For example, we may declare a `bigOrders` relationship which returns orders whose |
||||||
|
subtotal exceeds certain amount: |
||||||
|
|
||||||
|
```php |
||||||
|
class Customer extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
public function getBigOrders($threshold = 100) |
||||||
|
{ |
||||||
|
return $this->hasMany('Order', array('customer_id' => 'id')) |
||||||
|
->where('subtotal > :threshold', array(':threshold' => $threshold)) |
||||||
|
->orderBy('id'); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Sometimes, two tables are related together via an intermediary table called |
||||||
|
[pivot table](http://en.wikipedia.org/wiki/Pivot_table). To declare such relationships, we can customize |
||||||
|
the [[ActiveRelation]] object by calling its [[ActiveRelation::via()]] or [[ActiveRelation::viaTable()]] |
||||||
|
method. |
||||||
|
|
||||||
|
For example, if table `tbl_order` and table `tbl_item` are related via pivot table `tbl_order_item`, |
||||||
|
we can declare the `items` relation in the `Order` class like the following: |
||||||
|
|
||||||
|
```php |
||||||
|
class Order extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
public function getItems() |
||||||
|
{ |
||||||
|
return $this->hasMany('Item', array('id' => 'item_id')) |
||||||
|
->viaTable('tbl_order_item', array('order_id' => 'id')); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
[[ActiveRelation::via()]] method is similar to [[ActiveRelation::viaTable()]] except that |
||||||
|
the first parameter of [[ActiveRelation::via()]] takes a relation name declared in the ActiveRecord class. |
||||||
|
For example, the above `items` relation can be equivalently declared as follows: |
||||||
|
|
||||||
|
```php |
||||||
|
class Order extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
public function getOrderItems() |
||||||
|
{ |
||||||
|
return $this->hasMany('OrderItem', array('order_id' => 'id')); |
||||||
|
} |
||||||
|
|
||||||
|
public function getItems() |
||||||
|
{ |
||||||
|
return $this->hasMany('Item', array('id' => 'item_id')) |
||||||
|
->via('orderItems'); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
When you access the related objects the first time, behind the scene ActiveRecord performs a DB query |
||||||
|
to retrieve the corresponding data and populate it into the related objects. No query will be performed |
||||||
|
if you access the same related objects again. We call this *lazy loading*. For example, |
||||||
|
|
||||||
|
```php |
||||||
|
// SQL executed: SELECT * FROM tbl_customer WHERE id=1 |
||||||
|
$customer = Customer::find(1); |
||||||
|
// SQL executed: SELECT * FROM tbl_order WHERE customer_id=1 |
||||||
|
$orders = $customer->orders; |
||||||
|
// no SQL executed |
||||||
|
$orders2 = $customer->orders; |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
Lazy loading is very convenient to use. However, it may suffer from performance |
||||||
|
issue in the following scenario: |
||||||
|
|
||||||
|
```php |
||||||
|
// SQL executed: SELECT * FROM tbl_customer LIMIT 100 |
||||||
|
$customers = Customer::find()->limit(100)->all(); |
||||||
|
|
||||||
|
foreach ($customers as $customer) { |
||||||
|
// SQL executed: SELECT * FROM tbl_order WHERE customer_id=... |
||||||
|
$orders = $customer->orders; |
||||||
|
// ...handle $orders... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
How many SQL queries will be performed in the above code, assuming there are more than 100 customers in |
||||||
|
the database? 101! The first SQL query brings back 100 customers. Then for each customer, a SQL query |
||||||
|
is performed to bring back the customer's orders. |
||||||
|
|
||||||
|
To solve the above performance problem, you can use the so-called *eager loading* by calling [[ActiveQuery::with()]]: |
||||||
|
|
||||||
|
```php |
||||||
|
// SQL executed: SELECT * FROM tbl_customer LIMIT 100 |
||||||
|
// SELECT * FROM tbl_orders WHERE customer_id IN (1,2,...) |
||||||
|
$customers = Customer::find()->limit(100) |
||||||
|
->with('orders')->all(); |
||||||
|
|
||||||
|
foreach ($customers as $customer) { |
||||||
|
// no SQL executed |
||||||
|
$orders = $customer->orders; |
||||||
|
// ...handle $orders... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
As you can see, only two SQL queries are needed for the same task. |
||||||
|
|
||||||
|
|
||||||
|
Sometimes, you may want to customize the relational queries on the fly. It can be |
||||||
|
done for both lazy loading and eager loading. For example, |
||||||
|
|
||||||
|
```php |
||||||
|
$customer = Customer::find(1); |
||||||
|
// lazy loading: SELECT * FROM tbl_order WHERE customer_id=1 AND subtotal>100 |
||||||
|
$orders = $customer->getOrders()->where('subtotal>100')->all(); |
||||||
|
|
||||||
|
// eager loading: SELECT * FROM tbl_customer LIMIT 10 |
||||||
|
SELECT * FROM tbl_order WHERE customer_id IN (1,2,...) AND subtotal>100 |
||||||
|
$customers = Customer::find()->limit(100)->with(array( |
||||||
|
'orders' => function($query) { |
||||||
|
$query->andWhere('subtotal>100'); |
||||||
|
}, |
||||||
|
))->all(); |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
Working with Relationships |
||||||
|
-------------------------- |
||||||
|
|
||||||
|
ActiveRecord provides the following two methods for establishing and breaking a |
||||||
|
relationship between two ActiveRecord objects: |
||||||
|
|
||||||
|
- [[link()]] |
||||||
|
- [[unlink()]] |
||||||
|
|
||||||
|
For example, given a customer and a new order, we can use the following code to make the |
||||||
|
order owned by the customer: |
||||||
|
|
||||||
|
```php |
||||||
|
$customer = Customer::find(1); |
||||||
|
$order = new Order; |
||||||
|
$order->subtotal = 100; |
||||||
|
$customer->link('orders', $order); |
||||||
|
``` |
||||||
|
|
||||||
|
The [[link()]] call above will set the `customer_id` of the order to be the primary key |
||||||
|
value of `$customer` and then call [[save()]] to save the order into database. |
||||||
|
|
||||||
|
|
||||||
|
Data Input and Validation |
||||||
|
------------------------- |
||||||
|
|
||||||
|
ActiveRecord inherits data validation and data input features from [[\yii\base\Model]]. Data validation is called |
||||||
|
automatically when `save()` is performed and is canceling saving in case attributes aren't valid. |
||||||
|
|
||||||
|
For more details refer to [Model](model.md) section of the guide. |
||||||
|
|
||||||
|
|
||||||
|
Life Cycles of an ActiveRecord Object |
||||||
|
------------------------------------- |
||||||
|
|
||||||
|
An ActiveRecord object undergoes different life cycles when it is used in different cases. |
||||||
|
Subclasses or ActiveRecord behaviors may "inject" custom code in these life cycles through |
||||||
|
method overriding and event handling mechanisms. |
||||||
|
|
||||||
|
When instantiating a new ActiveRecord instance, we will have the following life cycles: |
||||||
|
|
||||||
|
1. constructor |
||||||
|
2. [[init()]]: will trigger an [[EVENT_INIT]] event |
||||||
|
|
||||||
|
When getting an ActiveRecord instance through the [[find()]] method, we will have the following life cycles: |
||||||
|
|
||||||
|
1. constructor |
||||||
|
2. [[init()]]: will trigger an [[EVENT_INIT]] event |
||||||
|
3. [[afterFind()]]: will trigger an [[EVENT_AFTER_FIND]] event |
||||||
|
|
||||||
|
When calling [[save()]] to insert or update an ActiveRecord, we will have the following life cycles: |
||||||
|
|
||||||
|
1. [[beforeValidate()]]: will trigger an [[EVENT_BEFORE_VALIDATE]] event |
||||||
|
2. [[afterValidate()]]: will trigger an [[EVENT_AFTER_VALIDATE]] event |
||||||
|
3. [[beforeSave()]]: will trigger an [[EVENT_BEFORE_INSERT]] or [[EVENT_BEFORE_UPDATE]] event |
||||||
|
4. perform the actual data insertion or updating |
||||||
|
5. [[afterSave()]]: will trigger an [[EVENT_AFTER_INSERT]] or [[EVENT_AFTER_UPDATE]] event |
||||||
|
|
||||||
|
Finally when calling [[delete()]] to delete an ActiveRecord, we will have the following life cycles: |
||||||
|
|
||||||
|
1. [[beforeDelete()]]: will trigger an [[EVENT_BEFORE_DELETE]] event |
||||||
|
2. perform the actual data deletion |
||||||
|
3. [[afterDelete()]]: will trigger an [[EVENT_AFTER_DELETE]] event |
||||||
|
|
||||||
|
|
||||||
|
Scopes |
||||||
|
------ |
||||||
|
|
||||||
|
A scope is a method that customizes a given [[ActiveQuery]] object. Scope methods are defined |
||||||
|
in the ActiveRecord classes. They can be invoked through the [[ActiveQuery]] object that is created |
||||||
|
via [[find()]] or [[findBySql()]]. The following is an example: |
||||||
|
|
||||||
|
```php |
||||||
|
class Customer extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
// ... |
||||||
|
|
||||||
|
/** |
||||||
|
* @param ActiveQuery $query |
||||||
|
*/ |
||||||
|
public static function active($query) |
||||||
|
{ |
||||||
|
$query->andWhere('status = 1'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$customers = Customer::find()->active()->all(); |
||||||
|
``` |
||||||
|
|
||||||
|
In the above, the `active()` method is defined in `Customer` while we are calling it |
||||||
|
through `ActiveQuery` returned by `Customer::find()`. |
||||||
|
|
||||||
|
Scopes can be parameterized. For example, we can define and use the following `olderThan` scope: |
||||||
|
|
||||||
|
```php |
||||||
|
class Customer extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
// ... |
||||||
|
|
||||||
|
/** |
||||||
|
* @param ActiveQuery $query |
||||||
|
* @param integer $age |
||||||
|
*/ |
||||||
|
public static function olderThan($query, $age = 30) |
||||||
|
{ |
||||||
|
$query->andWhere('age > :age', array(':age' => $age)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$customers = Customer::find()->olderThan(50)->all(); |
||||||
|
``` |
||||||
|
|
||||||
|
The parameters should follow after the `$query` parameter when defining the scope method, and they |
||||||
|
can take default values like shown above. |
||||||
|
|
||||||
|
Atomic operations and scenarios |
||||||
|
------------------------------- |
||||||
|
|
||||||
|
TODO: FIXME: WIP, TBD, https://github.com/yiisoft/yii2/issues/226 |
||||||
|
|
||||||
|
Imagine situation where you have to save something related to the main model in [[beforeSave()]], |
||||||
|
[[afterSave()]], [[beforeDelete()]] and/or [[afterDelete()]] life cycle methods. Developer may come |
||||||
|
to solution of overriding ActiveRecord [[save()]] method with database transaction wrapping or |
||||||
|
even using transaction in controller action, which is strictly speaking doesn't seems to be a good |
||||||
|
practice (recall skinny-controller fat-model fundamental rule). |
||||||
|
|
||||||
|
Here these ways are (**DO NOT** use them unless you're sure what are you actually doing). Models: |
||||||
|
|
||||||
|
```php |
||||||
|
class Feature extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
// ... |
||||||
|
|
||||||
|
public function getProduct() |
||||||
|
{ |
||||||
|
return $this->hasOne('Product', array('product_id' => 'id')); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Product extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
// ... |
||||||
|
|
||||||
|
public function getFeatures() |
||||||
|
{ |
||||||
|
return $this->hasMany('Feature', array('id' => 'product_id')); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Overriding [[save()]] method: |
||||||
|
|
||||||
|
```php |
||||||
|
|
||||||
|
class ProductController extends \yii\web\Controller |
||||||
|
{ |
||||||
|
public function actionCreate() |
||||||
|
{ |
||||||
|
// FIXME: TODO: WIP, TBD |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Using transactions within controller layer: |
||||||
|
|
||||||
|
```php |
||||||
|
class ProductController extends \yii\web\Controller |
||||||
|
{ |
||||||
|
public function actionCreate() |
||||||
|
{ |
||||||
|
// FIXME: TODO: WIP, TBD |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Instead of using these fragile methods you should consider using atomic scenarios and operations feature. |
||||||
|
|
||||||
|
```php |
||||||
|
class Feature extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
// ... |
||||||
|
|
||||||
|
public function getProduct() |
||||||
|
{ |
||||||
|
return $this->hasOne('Product', array('product_id' => 'id')); |
||||||
|
} |
||||||
|
|
||||||
|
public function scenarios() |
||||||
|
{ |
||||||
|
return array( |
||||||
|
'userCreates' => array( |
||||||
|
'attributes' => array('name', 'value'), |
||||||
|
'atomic' => array(self::OP_INSERT), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Product extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
// ... |
||||||
|
|
||||||
|
public function getFeatures() |
||||||
|
{ |
||||||
|
return $this->hasMany('Feature', array('id' => 'product_id')); |
||||||
|
} |
||||||
|
|
||||||
|
public function scenarios() |
||||||
|
{ |
||||||
|
return array( |
||||||
|
'userCreates' => array( |
||||||
|
'attributes' => array('title', 'price'), |
||||||
|
'atomic' => array(self::OP_INSERT), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public function afterValidate() |
||||||
|
{ |
||||||
|
parent::afterValidate(); |
||||||
|
// FIXME: TODO: WIP, TBD |
||||||
|
} |
||||||
|
|
||||||
|
public function afterSave($insert) |
||||||
|
{ |
||||||
|
parent::afterSave(); |
||||||
|
if ($this->getScenario() === 'userCreates') { |
||||||
|
// FIXME: TODO: WIP, TBD |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Controller is very thin and neat: |
||||||
|
|
||||||
|
```php |
||||||
|
class ProductController extends \yii\web\Controller |
||||||
|
{ |
||||||
|
public function actionCreate() |
||||||
|
{ |
||||||
|
// FIXME: TODO: WIP, TBD |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
See also |
||||||
|
-------- |
||||||
|
|
||||||
|
- [Model](model.md) |
||||||
|
- [[\yii\db\ActiveRecord]] |
@ -1,3 +1,188 @@ |
|||||||
Caching |
Caching |
||||||
======= |
======= |
||||||
|
|
||||||
|
Overview and Base Concepts |
||||||
|
-------------------------- |
||||||
|
|
||||||
|
Caching is a cheap and effective way to improve the performance of a web application. By storing relatively |
||||||
|
static data in cache and serving it from cache when requested, we save the time needed to generate the data. |
||||||
|
|
||||||
|
Using cache in Yii mainly involves configuring and accessing a cache application component. The following |
||||||
|
application configuration specifies a cache component that uses [memcached](http://memcached.org/) with |
||||||
|
two cache servers. Note, this configuration should be done in file located at `@app/config/web.php` alias |
||||||
|
in case you're using basic sample application. |
||||||
|
|
||||||
|
```php |
||||||
|
'components' => array( |
||||||
|
'cache' => array( |
||||||
|
'class' => '\yii\caching\MemCache', |
||||||
|
'servers' => array( |
||||||
|
array( |
||||||
|
'host' => 'server1', |
||||||
|
'port' => 11211, |
||||||
|
'weight' => 100, |
||||||
|
), |
||||||
|
array( |
||||||
|
'host' => 'server2', |
||||||
|
'port' => 11211, |
||||||
|
'weight' => 50, |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
``` |
||||||
|
|
||||||
|
When the application is running, the cache component can be accessed through `Yii::$app->cache` call. |
||||||
|
|
||||||
|
Yii provides various cache components that can store cached data in different media. The following |
||||||
|
is a summary of the available cache components: |
||||||
|
|
||||||
|
* [[\yii\caching\ApcCache]]: uses PHP [APC](http://php.net/manual/en/book.apc.php) extension. This option can be |
||||||
|
considered as the fastest one when dealing with cache for a centralized thick application (e.g. one |
||||||
|
server, no dedicated load balancers, etc.). |
||||||
|
|
||||||
|
* [[\yii\caching\DbCache]]: uses a database table to store cached data. By default, it will create and use a |
||||||
|
[SQLite3](http://sqlite.org/) database under the runtime directory. You can explicitly specify a database for |
||||||
|
it to use by setting its `db` property. |
||||||
|
|
||||||
|
* [[\yii\caching\DummyCache]]: presents dummy cache that does no caching at all. The purpose of this component |
||||||
|
is to simplify the code that needs to check the availability of cache. For example, during development or if |
||||||
|
the server doesn't have actual cache support, we can use this cache component. When an actual cache support |
||||||
|
is enabled, we can switch to use the corresponding cache component. In both cases, we can use the same |
||||||
|
code `Yii::$app->cache->get($key)` to attempt retrieving a piece of data without worrying that |
||||||
|
`Yii::$all->cache` might be `null`. |
||||||
|
|
||||||
|
* [[\yii\caching\FileCache]]: uses standard files to store cached data. This is particular suitable |
||||||
|
to cache large chunk of data (such as pages). |
||||||
|
|
||||||
|
* [[\yii\caching\MemCache]]: uses PHP [memcache](http://php.net/manual/en/book.memcache.php) |
||||||
|
and [memcached](http://php.net/manual/en/book.memcached.php) extensions. This option can be considered as |
||||||
|
the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load |
||||||
|
balancers, etc.) |
||||||
|
|
||||||
|
* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) NoSQL database. |
||||||
|
|
||||||
|
* [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) |
||||||
|
([see also](http://php.net/manual/en/book.wincache.php)) extension. |
||||||
|
|
||||||
|
* [[\yii\caching\XCache]]: uses PHP [XCache](http://xcache.lighttpd.net/) extension. |
||||||
|
|
||||||
|
* [[\yii\caching\ZendDataCache]]: uses |
||||||
|
[Zend Data Cache](http://files.zend.com/help/Zend-Server-6/zend-server.htm#data_cache_component.htm) |
||||||
|
as the underlying caching medium. |
||||||
|
|
||||||
|
Tip: because all these cache components extend from the same base class [[Cache]], one can switch to use |
||||||
|
a different type of cache without modifying the code that uses cache. |
||||||
|
|
||||||
|
Caching can be used at different levels. At the lowest level, we use cache to store a single piece of data, |
||||||
|
such as a variable, and we call this data caching. At the next level, we store in cache a page fragment which |
||||||
|
is generated by a portion of a view script. And at the highest level, we store a whole page in cache and serve |
||||||
|
it from cache as needed. |
||||||
|
|
||||||
|
In the next few subsections, we elaborate how to use cache at these levels. |
||||||
|
|
||||||
|
Note, by definition, cache is a volatile storage medium. It does not ensure the existence of the cached |
||||||
|
data even if it does not expire. Therefore, do not use cache as a persistent storage (e.g. do not use cache |
||||||
|
to store session data or other valuable information). |
||||||
|
|
||||||
|
Data Caching |
||||||
|
------------ |
||||||
|
|
||||||
|
Data caching is about storing some PHP variable in cache and retrieving it later from cache. For this purpose, |
||||||
|
the cache component base class [[\yii\caching\Cache]] provides two methods that are used most of the time: |
||||||
|
[[set()]] and [[get()]]. Note, only serializable variables and objects could be cached successfully. |
||||||
|
|
||||||
|
To store a variable `$value` in cache, we choose a unique `$key` and call [[set()]] to store it: |
||||||
|
|
||||||
|
```php |
||||||
|
Yii::$app->cache->set($key, $value); |
||||||
|
``` |
||||||
|
|
||||||
|
The cached data will remain in the cache forever unless it is removed because of some caching policy |
||||||
|
(e.g. caching space is full and the oldest data are removed). To change this behavior, we can also supply |
||||||
|
an expiration parameter when calling [[set()]] so that the data will be removed from the cache after |
||||||
|
a certain period of time: |
||||||
|
|
||||||
|
```php |
||||||
|
// keep the value in cache for at most 45 seconds |
||||||
|
Yii::$app->cache->set($key, $value, 45); |
||||||
|
``` |
||||||
|
|
||||||
|
Later when we need to access this variable (in either the same or a different web request), we call [[get()]] |
||||||
|
with the key to retrieve it from cache. If the value returned is `false`, it means the value is not available |
||||||
|
in cache and we should regenerate it: |
||||||
|
|
||||||
|
```php |
||||||
|
public function getCachedData() |
||||||
|
{ |
||||||
|
$key = /* generate unique key here */; |
||||||
|
$value = Yii::$app->getCache()->get($key); |
||||||
|
if ($value === false) { |
||||||
|
$value = /* regenerate value because it is not found in cache and then save it in cache for later use */; |
||||||
|
Yii::$app->cache->set($id, $value); |
||||||
|
} |
||||||
|
return $value; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This is the common pattern of arbitrary data caching for general use. |
||||||
|
|
||||||
|
When choosing the key for a variable to be cached, make sure the key is unique among all other variables that |
||||||
|
may be cached in the application. It is **NOT** required that the key is unique across applications because |
||||||
|
the cache component is intelligent enough to differentiate keys for different applications. |
||||||
|
|
||||||
|
Some cache storages, such as MemCache, APC, support retrieving multiple cached values in a batch mode, |
||||||
|
which may reduce the overhead involved in retrieving cached data. A method named [[mget()]] is provided |
||||||
|
to exploit this feature. In case the underlying cache storage does not support this feature, |
||||||
|
[[mget()]] will still simulate it. |
||||||
|
|
||||||
|
To remove a cached value from cache, call [[delete()]]; and to remove everything from cache, call [[flush()]]. |
||||||
|
Be very careful when calling [[flush()]] because it also removes cached data that are from other applications. |
||||||
|
|
||||||
|
Note, because [[Cache]] implements `ArrayAccess`, a cache component can be used liked an array. The followings |
||||||
|
are some examples: |
||||||
|
|
||||||
|
```php |
||||||
|
$cache = Yii::$app->getComponent('cache'); |
||||||
|
$cache['var1'] = $value1; // equivalent to: $cache->set('var1', $value1); |
||||||
|
$value2 = $cache['var2']; // equivalent to: $value2 = $cache->get('var2'); |
||||||
|
``` |
||||||
|
|
||||||
|
### Cache Dependency |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.data#cache-dependency |
||||||
|
|
||||||
|
### Query Caching |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.data#query-caching |
||||||
|
|
||||||
|
Fragment Caching |
||||||
|
---------------- |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment |
||||||
|
|
||||||
|
### Caching Options |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment#caching-options |
||||||
|
|
||||||
|
### Nested Caching |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.fragment#nested-caching |
||||||
|
|
||||||
|
Dynamic Content |
||||||
|
--------------- |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.dynamic |
||||||
|
|
||||||
|
Page Caching |
||||||
|
------------ |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page |
||||||
|
|
||||||
|
### Output Caching |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#output-caching |
||||||
|
|
||||||
|
### HTTP Caching |
||||||
|
|
||||||
|
TBD: http://www.yiiframework.com/doc/guide/1.1/en/caching.page#http-caching |
||||||
|
@ -0,0 +1,187 @@ |
|||||||
|
Controller |
||||||
|
========== |
||||||
|
|
||||||
|
Controller is one of the key parts of the application. It determines how to handle incoming request and creates a response. |
||||||
|
|
||||||
|
Most often a controller takes HTTP request data and returns HTML, JSON or XML as a response. |
||||||
|
|
||||||
|
Basics |
||||||
|
------ |
||||||
|
|
||||||
|
Controller resides in application's `controllers` directory is is named like `SiteController.php` where `Site` |
||||||
|
part could be anything describing a set of actions it contains. |
||||||
|
|
||||||
|
The basic web controller is a class that extends [[\yii\web\Controller]] and could be very simple: |
||||||
|
|
||||||
|
```php |
||||||
|
namespace app\controllers; |
||||||
|
|
||||||
|
use yii\web\Controller; |
||||||
|
|
||||||
|
class SiteController extends Controller |
||||||
|
{ |
||||||
|
public function actionIndex() |
||||||
|
{ |
||||||
|
// will render view from "views/site/index.php" |
||||||
|
return $this->render('index'); |
||||||
|
} |
||||||
|
|
||||||
|
public function actionTest() |
||||||
|
{ |
||||||
|
// will just print "test" to the browser |
||||||
|
return 'test'; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
As you can see, typical controller contains actions that are public class methods named as `actionSomething`. |
||||||
|
|
||||||
|
Routes |
||||||
|
------ |
||||||
|
|
||||||
|
Each controller action has a corresponding internal route. In our example above `actionIndex` has `site/index` route |
||||||
|
and `actionTest` has `site/test` route. In this route `site` is referred to as controller ID while `test` is referred to |
||||||
|
as action ID. |
||||||
|
|
||||||
|
By default you can access specific controller and action using the `http://example.com/?r=controller/action` URL. This |
||||||
|
behavior is fully customizable. For details refer to [URL Management](url.md). |
||||||
|
|
||||||
|
If controller is located inside a module its action internal route will be `module/controller/action`. |
||||||
|
|
||||||
|
In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404. |
||||||
|
|
||||||
|
### Defaults |
||||||
|
|
||||||
|
If user isn't specifying any route i.e. using URL like `http://example.com/`, Yii assumes that default route should be |
||||||
|
used. It is determined by [[\yii\web\Application::defaultRoute]] method and is `site` by default meaning that `SiteController` |
||||||
|
will be loaded. |
||||||
|
|
||||||
|
A controller has a default action. When the user request does not specify which action to execute by usign an URL such as |
||||||
|
`http://example.com/?r=site`, the default action will be executed. By default, the default action is named as `index`. |
||||||
|
It can be changed by setting the [[\yii\base\Controller::defaultAction]] property. |
||||||
|
|
||||||
|
Action parameters |
||||||
|
----------------- |
||||||
|
|
||||||
|
It was already mentioned that a simple action is just a public method named as `actionSomething`. Now we'll review |
||||||
|
ways that an action can get parameters from HTTP. |
||||||
|
|
||||||
|
### Action parameters |
||||||
|
|
||||||
|
You can define named arguments for an action and these will be automatically populated from corresponding values from |
||||||
|
`$_GET`. This is very convenient both because of the short syntax and an ability to specify defaults: |
||||||
|
|
||||||
|
```php |
||||||
|
namespace app\controllers; |
||||||
|
|
||||||
|
use yii\web\Controller; |
||||||
|
|
||||||
|
class BlogController extends Controller |
||||||
|
{ |
||||||
|
public function actionView($id, $version = null) |
||||||
|
{ |
||||||
|
$post = Post::find($id); |
||||||
|
$text = $post->text; |
||||||
|
|
||||||
|
if($version) { |
||||||
|
$text = $post->getHistory($version); |
||||||
|
} |
||||||
|
|
||||||
|
return $this->render('view', array( |
||||||
|
'post' => $post, |
||||||
|
'text' => $text, |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The action above can be accessed using either `http://example.com/?r=blog/view&id=42` or |
||||||
|
`http://example.com/?r=blog/view&id=42&version=3`. In the first case `version` isn't specified and default parameter |
||||||
|
value is used instead. |
||||||
|
|
||||||
|
### Getting data from request |
||||||
|
|
||||||
|
If your action is working with data from HTTP POST or has too many GET parameters you can rely on request object that |
||||||
|
is accessible via `\Yii::$app->request`: |
||||||
|
|
||||||
|
```php |
||||||
|
namespace app\controllers; |
||||||
|
|
||||||
|
use yii\web\Controller; |
||||||
|
use yii\web\HttpException; |
||||||
|
|
||||||
|
class BlogController extends Controller |
||||||
|
{ |
||||||
|
public function actionUpdate($id) |
||||||
|
{ |
||||||
|
$post = Post::find($id); |
||||||
|
if(!$post) { |
||||||
|
throw new HttpException(404); |
||||||
|
} |
||||||
|
|
||||||
|
if(\Yii::$app->request->isPost)) { |
||||||
|
$post->load($_POST); |
||||||
|
if($post->save()) { |
||||||
|
$this->redirect(array('view', 'id' => $post->id)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $this->render('update', array( |
||||||
|
'post' => $post, |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Standalone actions |
||||||
|
------------------ |
||||||
|
|
||||||
|
If action is generic enough it makes sense to implement it in a separate class to be able to reuse it. |
||||||
|
Create `actions/Page.php` |
||||||
|
|
||||||
|
```php |
||||||
|
namespace \app\actions; |
||||||
|
|
||||||
|
class Page extends \yii\base\Action |
||||||
|
{ |
||||||
|
public $view = 'index'; |
||||||
|
|
||||||
|
public function run() |
||||||
|
{ |
||||||
|
$this->controller->render($view); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The following code is too simple to implement as a separate action but gives an idea of how it works. Action implemented |
||||||
|
can be used in your controller as following: |
||||||
|
|
||||||
|
```php |
||||||
|
public SiteController extends \yii\web\Controller |
||||||
|
{ |
||||||
|
public function actions() |
||||||
|
{ |
||||||
|
return array( |
||||||
|
'about' => array( |
||||||
|
'class' => '@app/actions/Page', |
||||||
|
'view' => 'about', |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
After doing so you can access your action as `http://example.com/?r=site/about`. |
||||||
|
|
||||||
|
Filters |
||||||
|
------- |
||||||
|
|
||||||
|
Catching all incoming requests |
||||||
|
------------------------------ |
||||||
|
|
||||||
|
|
||||||
|
See also |
||||||
|
-------- |
||||||
|
|
||||||
|
- [Console](console.md) |
@ -0,0 +1,237 @@ |
|||||||
|
Database basics |
||||||
|
=============== |
||||||
|
|
||||||
|
Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides |
||||||
|
uniform API and solves some inconsistencies between different DBMS. By default Yii supports MySQL, SQLite, PostgreSQL, |
||||||
|
Oracle and MSSQL. |
||||||
|
|
||||||
|
|
||||||
|
Configuration |
||||||
|
------------- |
||||||
|
|
||||||
|
In order to start using database you need to configure database connection component first by adding `db` component |
||||||
|
to application configuration (for "basic" web application it's `config/web.php`) like the following: |
||||||
|
|
||||||
|
```php |
||||||
|
return array( |
||||||
|
// ... |
||||||
|
'components' => array( |
||||||
|
// ... |
||||||
|
'db' => array( |
||||||
|
'class' => 'yii\db\Connection', |
||||||
|
'dsn' => 'mysql:host=localhost;dbname=mydatabase', // MySQL, MariaDB |
||||||
|
//'dsn' => 'sqlite:/path/to/database/file', // SQLite |
||||||
|
//'dsn' => 'pgsql:host=localhost;port=5432;dbname=mydatabase', // PostgreSQL |
||||||
|
//'dsn' => 'sqlsrv:Server=localhost;Database=mydatabase', // MS SQL Server, sqlsrv driver |
||||||
|
//'dsn' => 'dblib:host=localhost;dbname=mydatabase', // MS SQL Server, dblib driver |
||||||
|
//'dsn' => 'mssql:host=localhost;dbname=mydatabase', // MS SQL Server, mssql driver |
||||||
|
//'dsn' => 'oci:dbname=//localhost:1521/testdb', // Oracle |
||||||
|
'username' => 'root', |
||||||
|
'password' => '', |
||||||
|
'charset' => 'utf8', |
||||||
|
), |
||||||
|
), |
||||||
|
// ... |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
After the component is configured you can access it using the following syntax: |
||||||
|
|
||||||
|
```php |
||||||
|
$connection = \Yii::$app->db; |
||||||
|
``` |
||||||
|
|
||||||
|
You can refer to [[\yii\db\Connection]] for a list of properties you can configure. Also note that you can define more |
||||||
|
than one connection component and use both at the same time if needed: |
||||||
|
|
||||||
|
```php |
||||||
|
$primaryConnection = \Yii::$app->db; |
||||||
|
$secondaryConnection = \Yii::$app->secondDb; |
||||||
|
``` |
||||||
|
|
||||||
|
If you don't want to define the connection as an application component you can instantiate it directly: |
||||||
|
|
||||||
|
```php |
||||||
|
$connection = new \yii\db\Connection(array( |
||||||
|
'dsn' => $dsn, |
||||||
|
'username' => $username, |
||||||
|
'password' => $password, |
||||||
|
)); |
||||||
|
$connection->open(); |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
Basic SQL queries |
||||||
|
----------------- |
||||||
|
|
||||||
|
Once you have a connection instance you can execute SQL queries using [[\yii\db\Command]]. |
||||||
|
|
||||||
|
### SELECT |
||||||
|
|
||||||
|
When query returns a set of rows: |
||||||
|
|
||||||
|
```php |
||||||
|
$command = $connection->createCommand('SELECT * FROM tbl_post'); |
||||||
|
$posts = $command->queryAll(); |
||||||
|
``` |
||||||
|
|
||||||
|
When only a single row is returned: |
||||||
|
|
||||||
|
```php |
||||||
|
$command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=1'); |
||||||
|
$post = $command->query(); |
||||||
|
``` |
||||||
|
|
||||||
|
When there are multiple values from the same column: |
||||||
|
|
||||||
|
```php |
||||||
|
$command = $connection->createCommand('SELECT title FROM tbl_post'); |
||||||
|
$titles = $command->queryColumn(); |
||||||
|
``` |
||||||
|
|
||||||
|
When there's a scalar value: |
||||||
|
|
||||||
|
```php |
||||||
|
$command = $connection->createCommand('SELECT COUNT(*) FROM tbl_post'); |
||||||
|
$postCount = $command->queryScalar(); |
||||||
|
``` |
||||||
|
|
||||||
|
### UPDATE, INSERT, DELETE etc. |
||||||
|
|
||||||
|
If SQL executed doesn't return any data you can use command's `execute` method: |
||||||
|
|
||||||
|
```php |
||||||
|
$command = $connection->createCommand('UPDATE tbl_post SET status=1 WHERE id=1'); |
||||||
|
$command->execute(); |
||||||
|
``` |
||||||
|
|
||||||
|
Alternatively the following syntax that takes care of proper table and column names quoting is possible: |
||||||
|
|
||||||
|
```php |
||||||
|
// INSERT |
||||||
|
$connection->createCommand()->insert('tbl_user', array( |
||||||
|
'name' => 'Sam', |
||||||
|
'age' => 30, |
||||||
|
))->execute(); |
||||||
|
|
||||||
|
// INSERT multiple rows at once |
||||||
|
$connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( |
||||||
|
array('Tom', 30), |
||||||
|
array('Jane', 20), |
||||||
|
array('Linda', 25), |
||||||
|
))->execute(); |
||||||
|
|
||||||
|
// UPDATE |
||||||
|
$connection->createCommand()->update('tbl_user', array( |
||||||
|
'status' => 1, |
||||||
|
), 'age > 30')->execute(); |
||||||
|
|
||||||
|
// DELETE |
||||||
|
$connection->createCommand()->delete('tbl_user', 'status = 0')->execute(); |
||||||
|
``` |
||||||
|
|
||||||
|
Quoting table and column names |
||||||
|
------------------------------ |
||||||
|
|
||||||
|
Most of the time you would use the following syntax for quoting table and column names: |
||||||
|
|
||||||
|
```php |
||||||
|
$sql = "SELECT COUNT([[$column]]) FROM {{$table}}"; |
||||||
|
$rowCount = $connection->createCommand($sql)->queryScalar(); |
||||||
|
``` |
||||||
|
|
||||||
|
In the code above `[[X]]` will be converted to properly quoted column name while `{{Y}}` will be converted to properly |
||||||
|
quoted table name. |
||||||
|
|
||||||
|
The alternative is to quote table and column names manually using [[\yii\db\Connection::quoteTableName()]] and |
||||||
|
[[\yii\db\Connection::quoteColumnName()]]: |
||||||
|
|
||||||
|
```php |
||||||
|
$column = $connection->quoteColumnName($column); |
||||||
|
$table = $connection->quoteTableName($table); |
||||||
|
$sql = "SELECT COUNT($column) FROM $table"; |
||||||
|
$rowCount = $connection->createCommand($sql)->queryScalar(); |
||||||
|
``` |
||||||
|
|
||||||
|
Prepared statements |
||||||
|
------------------- |
||||||
|
|
||||||
|
In order to securely pass query parameters you can use prepared statements: |
||||||
|
|
||||||
|
```php |
||||||
|
$command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=:id'); |
||||||
|
$command->bindValue(':id', $_GET['id']); |
||||||
|
$post = $command->query(); |
||||||
|
``` |
||||||
|
|
||||||
|
Another usage is performing a query multiple times while preparing it only once: |
||||||
|
|
||||||
|
```php |
||||||
|
$command = $connection->createCommand('DELETE FROM tbl_post WHERE id=:id'); |
||||||
|
$command->bindParam(':id', $id); |
||||||
|
|
||||||
|
$id = 1; |
||||||
|
$command->execute(); |
||||||
|
|
||||||
|
$id = 2; |
||||||
|
$command->execute(); |
||||||
|
``` |
||||||
|
|
||||||
|
Transactions |
||||||
|
------------ |
||||||
|
|
||||||
|
If the underlying DBMS supports transactions, you can perform transactional SQL queries like the following: |
||||||
|
|
||||||
|
```php |
||||||
|
$transaction = $connection->beginTransaction(); |
||||||
|
try { |
||||||
|
$connection->createCommand($sql1)->execute(); |
||||||
|
$connection->createCommand($sql2)->execute(); |
||||||
|
// ... executing other SQL statements ... |
||||||
|
$transaction->commit(); |
||||||
|
} catch(Exception $e) { |
||||||
|
$transaction->rollback(); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Working with database schema |
||||||
|
---------------------------- |
||||||
|
|
||||||
|
### Getting schema information |
||||||
|
|
||||||
|
You can get a [[\yii\db\Schema]] instance like the following: |
||||||
|
|
||||||
|
```php |
||||||
|
$schema = $connection->getSchema(); |
||||||
|
``` |
||||||
|
|
||||||
|
It contains a set of methods allowing you to retrieve various information about the database: |
||||||
|
|
||||||
|
```php |
||||||
|
$tables = $schema->getTableNames(); |
||||||
|
``` |
||||||
|
|
||||||
|
For the full reference check [[\yii\db\Schema]]. |
||||||
|
|
||||||
|
### Modifying schema |
||||||
|
|
||||||
|
Aside from basic SQL queries [[\yii\db\Command]] contains a set of methods allowing to modify database schema: |
||||||
|
|
||||||
|
- createTable, renameTable, dropTable, truncateTable |
||||||
|
- addColumn, renameColumn, dropColumn, alterColumn |
||||||
|
- addPrimaryKey, dropPrimaryKey |
||||||
|
- addForeignKey, dropForeignKey |
||||||
|
- createIndex, dropIndex |
||||||
|
|
||||||
|
These can be used as follows: |
||||||
|
|
||||||
|
```php |
||||||
|
// CREATE TABLE |
||||||
|
$connection->createCommand()->createTable('tbl_post', array( |
||||||
|
'id' => 'pk', |
||||||
|
'title' => 'string', |
||||||
|
'text' => 'text', |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
For the full reference check [[\yii\db\Command]]. |
@ -1,30 +1,64 @@ |
|||||||
* [Overview](overview.md) |
Introduction |
||||||
* [Installation](installation.md) |
============ |
||||||
* [Bootstrap with Yii](bootstrap.md) |
|
||||||
* [MVC Overview](mvc.md) |
- [Overview](overview.md) |
||||||
* [Controller](controller.md) |
|
||||||
* [Model](model.md) |
Getting started |
||||||
* [View](view.md) |
=============== |
||||||
* [Application](application.md) |
|
||||||
* [Form](form.md) |
- [Installation](installation.md) |
||||||
* [Data Validation](validation.md) |
- [Bootstrap with Yii](bootstrap.md) |
||||||
* [Database Access Objects](dao.md) |
- [Configuration](configuration.md) |
||||||
* [Query Builder](query-builder.md) |
|
||||||
* [ActiveRecord](active-record.md) |
Base concepts |
||||||
* [Database Migration](migration.md) |
============= |
||||||
* [Caching](caching.md) |
|
||||||
* [Internationalization](i18n.md) |
- [MVC Overview](mvc.md) |
||||||
* [Extending Yii](extension.md) |
- [Controller](controller.md) |
||||||
* [Authentication](authentication.md) |
- [Model](model.md) |
||||||
* [Authorization](authorization.md) |
- [View](view.md) |
||||||
* [Logging](logging.md) |
- [Application](application.md) |
||||||
* [URL Management](url.md) |
|
||||||
* [Theming](theming.md) |
Database |
||||||
* [Error Handling](error.md) |
======== |
||||||
* [Template](template.md) |
|
||||||
* [Console Application](console.md) |
- [Basics](database-basics.md) |
||||||
* [Security](security.md) |
- [Query Builder](query-builder.md) |
||||||
* [Performance Tuning](performance.md) |
- [ActiveRecord](active-record.md) |
||||||
* [Testing](testing.md) |
- [Database Migration](migration.md) |
||||||
* [Automatic Code Generation](gii.md) |
|
||||||
* [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) |
Extensions |
||||||
|
========== |
||||||
|
|
||||||
|
- [Extending Yii](extension.md) |
||||||
|
- [Using template engines](template.md) |
||||||
|
|
||||||
|
Security and access control |
||||||
|
=========================== |
||||||
|
|
||||||
|
- [Authentication](authentication.md) |
||||||
|
- [Authorization](authorization.md) |
||||||
|
- [Security](security.md) |
||||||
|
- Role based access control |
||||||
|
|
||||||
|
Toolbox |
||||||
|
======= |
||||||
|
|
||||||
|
- [Automatic Code Generation](gii.md) |
||||||
|
- Debug toolbar |
||||||
|
- [Error Handling](error.md) |
||||||
|
- [Logging](logging.md) |
||||||
|
|
||||||
|
More |
||||||
|
==== |
||||||
|
|
||||||
|
- [Form](form.md) |
||||||
|
- [Model validation reference](validation.md) |
||||||
|
- [Caching](caching.md) |
||||||
|
- [Internationalization](i18n.md) |
||||||
|
- [URL Management](url.md) |
||||||
|
- [Theming](theming.md) |
||||||
|
- [Console Application](console.md) |
||||||
|
- [Performance Tuning](performance.md) |
||||||
|
- [Testing](testing.md) |
||||||
|
- [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) |
||||||
|
@ -0,0 +1,260 @@ |
|||||||
|
Model |
||||||
|
===== |
||||||
|
|
||||||
|
A model in Yii is intended for application data storage and has the following basic features: |
||||||
|
|
||||||
|
- attribute declaration: a model defines what is considered an attribute. |
||||||
|
- attribute labels: each attribute may be associated with a label for display purpose. |
||||||
|
- massive attribute assignment. |
||||||
|
- scenario-based data validation. |
||||||
|
|
||||||
|
Models extending from [[\yii\base\Model]] class are typically used to hold data and corresponding validation rules of complex web forms. |
||||||
|
The class is also a base for more advanced models with additional functionality such as [Active Record](active-record.md). |
||||||
|
|
||||||
|
Attributes |
||||||
|
---------- |
||||||
|
|
||||||
|
Attributes store the actual data represented by a model and can |
||||||
|
be accessed like object member variables. For example, a `Post` model |
||||||
|
may contain a `title` attribute and a `content` attribute which may be |
||||||
|
accessed as follows: |
||||||
|
|
||||||
|
```php |
||||||
|
$post = new Post; |
||||||
|
$post->title = 'Hello, world'; |
||||||
|
$post->content = 'Something interesting is happening'; |
||||||
|
echo $post->title; |
||||||
|
echo $post->content; |
||||||
|
``` |
||||||
|
|
||||||
|
Since model implements [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) interface you can use it |
||||||
|
as if it was an array: |
||||||
|
|
||||||
|
```php |
||||||
|
$post = new Post; |
||||||
|
$post['title'] = 'Hello, world'; |
||||||
|
$post['content'] = 'Something interesting is happening'; |
||||||
|
echo $post['title']; |
||||||
|
echo $post['content']; |
||||||
|
``` |
||||||
|
|
||||||
|
Default model implementation has a strict rule that all its attributes should be explicitly declared as public and |
||||||
|
non-static class properties such as the following: |
||||||
|
|
||||||
|
```php |
||||||
|
// LoginForm has two attributes: username and password |
||||||
|
class LoginForm extends \yii\base\Model |
||||||
|
{ |
||||||
|
public $username; |
||||||
|
public $password; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
In order to change this, you can override `attributes()` method that returns a list of model attribute names. |
||||||
|
|
||||||
|
|
||||||
|
Attribute labels |
||||||
|
---------------- |
||||||
|
|
||||||
|
Attribute labels are mainly used for display purpose. For example, given an attribute `firstName`, we can declare |
||||||
|
a label `First Name` which is more user-friendly and can be displayed to end users for example as a form label. |
||||||
|
|
||||||
|
By default an attribute label is generated using [[\yii\base\Model\generateAttributeLabel()]] but the better way is to |
||||||
|
specify it explicitly like the following: |
||||||
|
|
||||||
|
```php |
||||||
|
// LoginForm has two attributes: username and password |
||||||
|
class LoginForm extends \yii\base\Model |
||||||
|
{ |
||||||
|
public $username; |
||||||
|
public $password; |
||||||
|
|
||||||
|
public function attributeLabels() |
||||||
|
{ |
||||||
|
reuturn array( |
||||||
|
'username' => 'Your name', |
||||||
|
'password' => 'Your password', |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Scenarios |
||||||
|
--------- |
||||||
|
|
||||||
|
A model may be used in different scenarios. For example, a `User` model may be used to collect user login inputs, |
||||||
|
and it may also be used for user registration purpose. For this reason, each model has a property named `scenario` |
||||||
|
which stores the name of the scenario that the model is currently being used in. As we will explain in the next |
||||||
|
few sections, the concept of scenario is mainly used for validation and massive attribute assignment. |
||||||
|
|
||||||
|
Associated with each scenario is a list of attributes that are *active* in that particular scenario. For example, |
||||||
|
in the `login` scenario, only the `username` and `password` attributes are active; while in the `register` scenario, |
||||||
|
additional attributes such as `email` are *active*. |
||||||
|
|
||||||
|
Possible scenarios should be listed in the `scenarios()` method which returns an array whose keys are the scenario |
||||||
|
names and whose values are the corresponding active attribute lists. Below is an example: |
||||||
|
|
||||||
|
```php |
||||||
|
class User extends \yii\db\ActiveRecord |
||||||
|
{ |
||||||
|
public function scenarios() |
||||||
|
{ |
||||||
|
return array( |
||||||
|
'login' => array('username', 'password'), |
||||||
|
'register' => array('username', 'email', 'password'), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want it to be validated). |
||||||
|
We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example, |
||||||
|
|
||||||
|
```php |
||||||
|
array('username', 'password', '!secret') |
||||||
|
``` |
||||||
|
|
||||||
|
Active model scenario could be set using one of the following ways: |
||||||
|
|
||||||
|
```php |
||||||
|
class EmployeeController extends \yii\web\Controller |
||||||
|
{ |
||||||
|
public function actionCreate($id = null) |
||||||
|
{ |
||||||
|
// first way |
||||||
|
$employee = new Employee(array('scenario' => 'managementPanel')); |
||||||
|
|
||||||
|
// second way |
||||||
|
$employee = new Employee; |
||||||
|
$employee->scenario = 'managementPanel'; |
||||||
|
|
||||||
|
// third way |
||||||
|
$employee = Employee::find()->where('id = :id', array(':id' => $id))->one(); |
||||||
|
if ($employee !== null) { |
||||||
|
$employee->setScenario('managementPanel'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Validation |
||||||
|
---------- |
||||||
|
|
||||||
|
When a model is used to collect user input data via its attributes, it usually needs to validate the affected attributes |
||||||
|
to make sure they satisfy certain requirements, such as an attribute cannot be empty, an attribute must contain letters |
||||||
|
only, etc. If errors are found in validation, they may be presented to the user to help him fix the errors. |
||||||
|
The following example shows how the validation is performed: |
||||||
|
|
||||||
|
```php |
||||||
|
$model = new LoginForm; |
||||||
|
$model->username = $_POST['username']; |
||||||
|
$model->password = $_POST['password']; |
||||||
|
if ($model->validate()) { |
||||||
|
// ... login the user ... |
||||||
|
} else { |
||||||
|
$errors = $model->getErrors(); |
||||||
|
// ... display the errors to the end user ... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The possible validation rules for a model should be listed in its `rules()` method. Each validation rule applies to one |
||||||
|
or several attributes and is effective in one or several scenarios. A rule can be specified using a validator object - an |
||||||
|
instance of a [[\yii\validators\Validator]] child class, or an array with the following format: |
||||||
|
|
||||||
|
```php |
||||||
|
array( |
||||||
|
'attribute1, attribute2, ...', |
||||||
|
'validator class or alias', |
||||||
|
// specifies in which scenario(s) this rule is active. |
||||||
|
// if not given, it means it is active in all scenarios |
||||||
|
'on' => 'scenario1, scenario2, ...', |
||||||
|
// the following name-value pairs will be used |
||||||
|
// to initialize the validator properties |
||||||
|
'property1' => 'value1', |
||||||
|
'property2' => 'value2', |
||||||
|
// ... |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
When `validate()` is called, the actual validation rules executed are determined using both of the following criteria: |
||||||
|
|
||||||
|
- the rule must be associated with at least one active attribute; |
||||||
|
- the rule must be active for the current scenario. |
||||||
|
|
||||||
|
|
||||||
|
### Active Attributes |
||||||
|
|
||||||
|
An attribute is *active* if it is subject to some validations in the current scenario. |
||||||
|
|
||||||
|
|
||||||
|
### Safe Attributes |
||||||
|
|
||||||
|
An attribute is *safe* if it can be massively assigned in the current scenario. |
||||||
|
|
||||||
|
|
||||||
|
Massive Attribute Retrieval and Assignment |
||||||
|
------------------------------------------ |
||||||
|
|
||||||
|
Attributes can be massively retrieved via the `attributes` property. |
||||||
|
The following code will return *all* attributes in the `$post` model |
||||||
|
as an array of name-value pairs. |
||||||
|
|
||||||
|
```php |
||||||
|
$attributes = $post->attributes; |
||||||
|
var_dump($attributes); |
||||||
|
``` |
||||||
|
|
||||||
|
Using the same `attributes` property you can massively assign data from associative array to model attributes: |
||||||
|
|
||||||
|
```php |
||||||
|
$attributes = array( |
||||||
|
'title' => 'Model attributes', |
||||||
|
'create_time' => time(), |
||||||
|
); |
||||||
|
$post->attributes = $attributes; |
||||||
|
``` |
||||||
|
|
||||||
|
In the code above we're assigning corresponding data to model attributes named as array keys. The key difference from mass |
||||||
|
retrieval that always works for all attributes is that in order to be assigned an attribute should be **safe** else |
||||||
|
it will be ignored. |
||||||
|
|
||||||
|
|
||||||
|
Validation rules and mass assignment |
||||||
|
------------------------------------ |
||||||
|
|
||||||
|
In Yii2 unlike Yii 1.x validation rules are separated from mass assignment. Validation |
||||||
|
rules are described in `rules()` method of the model while what's safe for mass |
||||||
|
assignment is described in `scenarios` method: |
||||||
|
|
||||||
|
```php |
||||||
|
function rules() |
||||||
|
{ |
||||||
|
return array( |
||||||
|
// rule applied when corresponding field is "safe" |
||||||
|
array('username', 'length', 'min' => 2), |
||||||
|
array('first_name', 'length', 'min' => 2), |
||||||
|
array('password', 'required'), |
||||||
|
|
||||||
|
// rule applied when scenario is "signup" no matter if field is "safe" or not |
||||||
|
array('hashcode', 'check', 'on' => 'signup'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function scenarios() |
||||||
|
{ |
||||||
|
return array( |
||||||
|
// on signup allow mass assignment of username |
||||||
|
'signup' => array('username', 'password'), |
||||||
|
'update' => array('username', 'first_name'), |
||||||
|
); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Note that everything is unsafe by default and you can't make field "safe" without specifying scenario. |
||||||
|
|
||||||
|
|
||||||
|
See also |
||||||
|
-------- |
||||||
|
|
||||||
|
- [Model validation reference](validation.md) |
||||||
|
- [[\yii\base\Model]] |
@ -1,3 +1,86 @@ |
|||||||
Template |
Using template engines |
||||||
======== |
====================== |
||||||
|
|
||||||
|
By default Yii uses PHP as template language but you can configure it to be able |
||||||
|
to render templates with special engines such as Twig or Smarty. |
||||||
|
|
||||||
|
The component responsible for rendering a view is called `view`. You can add |
||||||
|
a custom template engines as follows: |
||||||
|
|
||||||
|
```php |
||||||
|
array( |
||||||
|
'components' => array( |
||||||
|
'view' => array( |
||||||
|
'class' => 'yii\base\View', |
||||||
|
'renderers' => array( |
||||||
|
'tpl' => array( |
||||||
|
'class' => 'yii\renderers\SmartyViewRenderer', |
||||||
|
), |
||||||
|
'twig' => array( |
||||||
|
'class' => 'yii\renderers\TwigViewRenderer', |
||||||
|
'twigPath' => '@app/vendors/Twig', |
||||||
|
), |
||||||
|
// ... |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
Note that Smarty and Twig are not bundled with Yii and you have to download and |
||||||
|
unpack these yourself and then specify `twigPath` and `smartyPath` respectively. |
||||||
|
|
||||||
|
Twig |
||||||
|
---- |
||||||
|
|
||||||
|
In order to use Twig you need to put you templates in files with extension `.twig` |
||||||
|
(or another one if configured differently). |
||||||
|
Also you need to specify this extension explicitly when calling `$this->render()` |
||||||
|
or `$this->renderPartial()` from your controller: |
||||||
|
|
||||||
|
```php |
||||||
|
echo $this->render('renderer.twig', array('username' => 'Alex')); |
||||||
|
``` |
||||||
|
|
||||||
|
### Additional functions |
||||||
|
|
||||||
|
Additionally to regular Twig syntax the following is available in Yii: |
||||||
|
|
||||||
|
```php |
||||||
|
<a href="{{ path('blog/view', {'alias' : post.alias}) }}">{{ post.title }}</a> |
||||||
|
``` |
||||||
|
|
||||||
|
path function calls `Html::url()` internally. |
||||||
|
|
||||||
|
### Additional variables |
||||||
|
|
||||||
|
- `app` = `\Yii::$app` |
||||||
|
- `this` = current `View` object |
||||||
|
|
||||||
|
Smarty |
||||||
|
------ |
||||||
|
|
||||||
|
In order to use Smarty you need to put you templates in files with extension `.tpl` |
||||||
|
(or another one if configured differently). |
||||||
|
Also you need to specify this extension explicitly when calling `$this->render()` |
||||||
|
or `$this->renderPartial()` from your controller: |
||||||
|
|
||||||
|
```php |
||||||
|
echo $this->render('renderer.tpl', array('username' => 'Alex')); |
||||||
|
``` |
||||||
|
|
||||||
|
### Additional functions |
||||||
|
|
||||||
|
Additionally to regular Smarty syntax the following is available in Yii: |
||||||
|
|
||||||
|
```php |
||||||
|
<a href="{path route='blog/view' alias=$post.alias}">{$post.title}</a> |
||||||
|
``` |
||||||
|
|
||||||
|
path function calls `Html::url()` internally. |
||||||
|
|
||||||
|
### Additional variables |
||||||
|
|
||||||
|
- `$app` = `\Yii::$app` |
||||||
|
- `$this` = current `View` object |
||||||
|
|
||||||
|
@ -1,214 +0,0 @@ |
|||||||
Model |
|
||||||
===== |
|
||||||
|
|
||||||
Attributes |
|
||||||
---------- |
|
||||||
|
|
||||||
Attributes store the actual data represented by a model and can |
|
||||||
be accessed like object member variables. For example, a `Post` model |
|
||||||
may contain a `title` attribute and a `content` attribute which may be |
|
||||||
accessed as follows: |
|
||||||
|
|
||||||
~~~php |
|
||||||
$post->title = 'Hello, world'; |
|
||||||
$post->content = 'Something interesting is happening'; |
|
||||||
echo $post->title; |
|
||||||
echo $post->content; |
|
||||||
~~~ |
|
||||||
|
|
||||||
A model should list all its available attributes in the `attributes()` method. |
|
||||||
|
|
||||||
Attributes may be implemented in various ways. The [[\yii\base\Model]] class |
|
||||||
implements attributes as public member variables of the class, while the |
|
||||||
[[\yii\db\ActiveRecord]] class implements them as DB table columns. For example, |
|
||||||
|
|
||||||
~~~php |
|
||||||
// LoginForm has two attributes: username and password |
|
||||||
class LoginForm extends \yii\base\Model |
|
||||||
{ |
|
||||||
public $username; |
|
||||||
public $password; |
|
||||||
} |
|
||||||
|
|
||||||
// Post is associated with the tbl_post DB table. |
|
||||||
// Its attributes correspond to the columns in tbl_post |
|
||||||
class Post extends \yii\db\ActiveRecord |
|
||||||
{ |
|
||||||
public function table() |
|
||||||
{ |
|
||||||
return 'tbl_post'; |
|
||||||
} |
|
||||||
} |
|
||||||
~~~ |
|
||||||
|
|
||||||
|
|
||||||
### Attribute Labels |
|
||||||
|
|
||||||
|
|
||||||
Scenarios |
|
||||||
--------- |
|
||||||
|
|
||||||
A model may be used in different scenarios. For example, a `User` model may be |
|
||||||
used to collect user login inputs, and it may also be used for user registration |
|
||||||
purpose. For this reason, each model has a property named `scenario` which stores |
|
||||||
the name of the scenario that the model is currently being used in. As we will explain |
|
||||||
in the next few sections, the concept of scenario is mainly used in validation and |
|
||||||
massive attribute assignment. |
|
||||||
|
|
||||||
Associated with each scenario is a list of attributes that are *active* in that |
|
||||||
particular scenario. For example, in the `login` scenario, only the `username` |
|
||||||
and `password` attributes are active; while in the `register` scenario, |
|
||||||
additional attributes such as `email` are *active*. |
|
||||||
|
|
||||||
Possible scenarios should be listed in the `scenarios()` method which returns an array |
|
||||||
whose keys are the scenario names and whose values are the corresponding |
|
||||||
active attribute lists. Below is an example: |
|
||||||
|
|
||||||
~~~php |
|
||||||
class User extends \yii\db\ActiveRecord |
|
||||||
{ |
|
||||||
public function table() |
|
||||||
{ |
|
||||||
return 'tbl_user'; |
|
||||||
} |
|
||||||
|
|
||||||
public function scenarios() |
|
||||||
{ |
|
||||||
return array( |
|
||||||
'login' => array('username', 'password'), |
|
||||||
'register' => array('username', 'email', 'password'), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
~~~ |
|
||||||
|
|
||||||
Sometimes, we want to mark that an attribute is not safe for massive assignment |
|
||||||
(but we still want it to be validated). We may do so by prefixing an exclamation |
|
||||||
character to the attribute name when declaring it in `scenarios()`. For example, |
|
||||||
|
|
||||||
~~~php |
|
||||||
array('username', 'password', '!secret') |
|
||||||
~~~ |
|
||||||
|
|
||||||
|
|
||||||
Validation |
|
||||||
---------- |
|
||||||
|
|
||||||
When a model is used to collect user input data via its attributes, |
|
||||||
it usually needs to validate the affected attributes to make sure they |
|
||||||
satisfy certain requirements, such as an attribute cannot be empty, |
|
||||||
an attribute must contain letters only, etc. If errors are found in |
|
||||||
validation, they may be presented to the user to help him fix the errors. |
|
||||||
The following example shows how the validation is performed: |
|
||||||
|
|
||||||
~~~php |
|
||||||
$model = new LoginForm; |
|
||||||
$model->username = $_POST['username']; |
|
||||||
$model->password = $_POST['password']; |
|
||||||
if ($model->validate()) { |
|
||||||
// ...login the user... |
|
||||||
} else { |
|
||||||
$errors = $model->getErrors(); |
|
||||||
// ...display the errors to the end user... |
|
||||||
} |
|
||||||
~~~ |
|
||||||
|
|
||||||
The possible validation rules for a model should be listed in its |
|
||||||
`rules()` method. Each validation rule applies to one or several attributes |
|
||||||
and is effective in one or several scenarios. A rule can be specified |
|
||||||
using a validator object - an instance of a [[\yii\validators\Validator]] |
|
||||||
child class, or an array with the following format: |
|
||||||
|
|
||||||
~~~php |
|
||||||
array( |
|
||||||
'attribute1, attribute2, ...', |
|
||||||
'validator class or alias', |
|
||||||
// specifies in which scenario(s) this rule is active. |
|
||||||
// if not given, it means it is active in all scenarios |
|
||||||
'on' => 'scenario1, scenario2, ...', |
|
||||||
// the following name-value pairs will be used |
|
||||||
// to initialize the validator properties... |
|
||||||
'name1' => 'value1', |
|
||||||
'name2' => 'value2', |
|
||||||
.... |
|
||||||
) |
|
||||||
~~~ |
|
||||||
|
|
||||||
When `validate()` is called, the actual validation rules executed are |
|
||||||
determined using both of the following criteria: |
|
||||||
|
|
||||||
* the rules must be associated with at least one active attribute; |
|
||||||
* the rules must be active for the current scenario. |
|
||||||
|
|
||||||
|
|
||||||
### Active Attributes |
|
||||||
|
|
||||||
An attribute is *active* if it is subject to some validations in the current scenario. |
|
||||||
|
|
||||||
|
|
||||||
### Safe Attributes |
|
||||||
|
|
||||||
An attribute is *safe* if it can be massively assigned in the current scenario. |
|
||||||
|
|
||||||
|
|
||||||
Massive Access of Attributes |
|
||||||
---------------------------- |
|
||||||
|
|
||||||
|
|
||||||
Massive Attribute Retrieval |
|
||||||
--------------------------- |
|
||||||
|
|
||||||
Attributes can be massively retrieved via the `attributes` property. |
|
||||||
The following code will return *all* attributes in the `$post` model |
|
||||||
as an array of name-value pairs. |
|
||||||
|
|
||||||
~~~php |
|
||||||
$attributes = $post->attributes; |
|
||||||
var_dump($attributes); |
|
||||||
~~~ |
|
||||||
|
|
||||||
|
|
||||||
Massive Attribute Assignment |
|
||||||
---------------------------- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Safe Attributes |
|
||||||
--------------- |
|
||||||
|
|
||||||
Safe attributes are those that can be massively assigned. For example, |
|
||||||
|
|
||||||
Validation rules and mass assignment |
|
||||||
------------------------------------ |
|
||||||
|
|
||||||
In Yii2 unlike Yii 1.x validation rules are separated from mass assignment. Validation |
|
||||||
rules are described in `rules()` method of the model while what's safe for mass |
|
||||||
assignment is described in `scenarios` method: |
|
||||||
|
|
||||||
```php |
|
||||||
|
|
||||||
function rules() { |
|
||||||
return array( |
|
||||||
// rule applied when corresponding field is "safe" |
|
||||||
array('username', 'length', 'min' => 2), |
|
||||||
array('first_name', 'length', 'min' => 2), |
|
||||||
array('password', 'required'), |
|
||||||
|
|
||||||
// rule applied when scenario is "signup" no matter if field is "safe" or not |
|
||||||
array('hashcode', 'check', 'on' => 'signup'), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
function scenarios() { |
|
||||||
return array( |
|
||||||
// on signup allow mass assignment of username |
|
||||||
'signup' => array('username', 'password'), |
|
||||||
'update' => array('username', 'first_name'), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
``` |
|
||||||
|
|
||||||
Note that everything is unsafe by default and you can't make field "safe" |
|
||||||
without specifying scenario. |
|
@ -1,85 +0,0 @@ |
|||||||
Yii2 view renderers |
|
||||||
=================== |
|
||||||
|
|
||||||
By default Yii uses PHP as template language but you can configure it to be able |
|
||||||
to render templates with special engines such as Twig or Smarty. |
|
||||||
|
|
||||||
The component responsible for rendering a view is called `view`. You can add |
|
||||||
a custom template engines as follows: |
|
||||||
|
|
||||||
```php |
|
||||||
array( |
|
||||||
'components' => array( |
|
||||||
'view' => array( |
|
||||||
'class' => 'yii\base\View', |
|
||||||
'renderers' => array( |
|
||||||
'tpl' => array( |
|
||||||
'class' => 'yii\renderers\SmartyViewRenderer', |
|
||||||
), |
|
||||||
'twig' => array( |
|
||||||
'class' => 'yii\renderers\TwigViewRenderer', |
|
||||||
'twigPath' => '@app/vendors/Twig', |
|
||||||
), |
|
||||||
// ... |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
) |
|
||||||
``` |
|
||||||
|
|
||||||
Note that Smarty and Twig are not bundled with Yii and you have to download and |
|
||||||
unpack these yourself and then specify `twigPath` and `smartyPath` respectively. |
|
||||||
|
|
||||||
Twig |
|
||||||
---- |
|
||||||
|
|
||||||
In order to use Twig you need to put you templates in files with extension `.twig` |
|
||||||
(or another one if configured differently). |
|
||||||
Also you need to specify this extension explicitly when calling `$this->render()` |
|
||||||
or `$this->renderPartial()` from your controller: |
|
||||||
|
|
||||||
```php |
|
||||||
echo $this->render('renderer.twig', array('username' => 'Alex')); |
|
||||||
``` |
|
||||||
|
|
||||||
### Additional functions |
|
||||||
|
|
||||||
Additionally to regular Twig syntax the following is available in Yii: |
|
||||||
|
|
||||||
```php |
|
||||||
<a href="{{ path('blog/view', {'alias' : post.alias}) }}">{{ post.title }}</a> |
|
||||||
``` |
|
||||||
|
|
||||||
path function calls `Html::url()` internally. |
|
||||||
|
|
||||||
### Additional variables |
|
||||||
|
|
||||||
- `app` = `\Yii::$app` |
|
||||||
- `this` = current `View` object |
|
||||||
|
|
||||||
Smarty |
|
||||||
------ |
|
||||||
|
|
||||||
In order to use Smarty you need to put you templates in files with extension `.tpl` |
|
||||||
(or another one if configured differently). |
|
||||||
Also you need to specify this extension explicitly when calling `$this->render()` |
|
||||||
or `$this->renderPartial()` from your controller: |
|
||||||
|
|
||||||
```php |
|
||||||
echo $this->render('renderer.tpl', array('username' => 'Alex')); |
|
||||||
``` |
|
||||||
|
|
||||||
### Additional functions |
|
||||||
|
|
||||||
Additionally to regular Smarty syntax the following is available in Yii: |
|
||||||
|
|
||||||
```php |
|
||||||
<a href="{path route='blog/view' alias=$post.alias}">{$post.title}</a> |
|
||||||
``` |
|
||||||
|
|
||||||
path function calls `Html::url()` internally. |
|
||||||
|
|
||||||
### Additional variables |
|
||||||
|
|
||||||
- `$app` = `\Yii::$app` |
|
||||||
- `$this` = current `View` object |
|
@ -1,38 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* @link http://www.yiiframework.com/ |
|
||||||
* @copyright Copyright (c) 2008 Yii Software LLC |
|
||||||
* @license http://www.yiiframework.com/license/ |
|
||||||
*/ |
|
||||||
|
|
||||||
namespace yii\debug; |
|
||||||
|
|
||||||
use Yii; |
|
||||||
use yii\base\Widget; |
|
||||||
use yii\helpers\Html; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author Qiang Xue <qiang.xue@gmail.com> |
|
||||||
* @since 2.0 |
|
||||||
*/ |
|
||||||
class Toolbar extends Widget |
|
||||||
{ |
|
||||||
public $debugAction = 'debug/default/toolbar'; |
|
||||||
|
|
||||||
public function run() |
|
||||||
{ |
|
||||||
if (Yii::$app->hasModule('debug')) { |
|
||||||
$id = 'yii-debug-toolbar'; |
|
||||||
$url = Yii::$app->getUrlManager()->createUrl($this->debugAction, array( |
|
||||||
'tag' => Yii::getLogger()->tag, |
|
||||||
)); |
|
||||||
$view = $this->getView(); |
|
||||||
$view->registerJs("yii.debug.load('$id', '$url');"); |
|
||||||
$view->registerAssetBundle('yii/debug'); |
|
||||||
echo Html::tag('div', '', array( |
|
||||||
'id' => $id, |
|
||||||
'style' => 'display: none', |
|
||||||
)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,21 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @var \yii\base\View $this |
||||||
|
* @var string $content |
||||||
|
*/ |
||||||
|
use yii\helpers\Html; |
||||||
|
?> |
||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<?php $this->beginPage(); ?> |
||||||
|
<head> |
||||||
|
<title><?php echo Html::encode($this->title); ?></title>
|
||||||
|
<?php $this->head(); ?> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<?php $this->beginBody(); ?> |
||||||
|
<?php echo $content; ?> |
||||||
|
<?php $this->endBody(); ?> |
||||||
|
</body> |
||||||
|
<?php $this->endPage(); ?> |
||||||
|
</html> |
@ -1,98 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* @link http://www.yiiframework.com/ |
|
||||||
* @copyright Copyright (c) 2008 Yii Software LLC |
|
||||||
* @license http://www.yiiframework.com/license/ |
|
||||||
*/ |
|
||||||
|
|
||||||
namespace yii\logging; |
|
||||||
|
|
||||||
use Yii; |
|
||||||
use yii\base\Component; |
|
||||||
|
|
||||||
/** |
|
||||||
* Router manages [[Target|log targets]] that record log messages in different media. |
|
||||||
* |
|
||||||
* For example, a [[FileTarget|file log target]] records log messages |
|
||||||
* in files; an [[EmailTarget|email log target]] sends log messages |
|
||||||
* to specific email addresses. Each log target may specify filters on |
|
||||||
* message levels and categories to record specific messages only. |
|
||||||
* |
|
||||||
* Router and the targets it manages may be configured in application configuration, |
|
||||||
* like the following: |
|
||||||
* |
|
||||||
* ~~~ |
|
||||||
* array( |
|
||||||
* // preload log component when application starts |
|
||||||
* 'preload' => array('log'), |
|
||||||
* 'components' => array( |
|
||||||
* 'log' => array( |
|
||||||
* 'class' => 'yii\logging\Router', |
|
||||||
* 'targets' => array( |
|
||||||
* 'file' => array( |
|
||||||
* 'class' => 'yii\logging\FileTarget', |
|
||||||
* 'levels' => array('trace', 'info'), |
|
||||||
* 'categories' => array('yii\*'), |
|
||||||
* ), |
|
||||||
* 'email' => array( |
|
||||||
* 'class' => 'yii\logging\EmailTarget', |
|
||||||
* 'levels' => array('error', 'warning'), |
|
||||||
* 'emails' => array('admin@example.com'), |
|
||||||
* ), |
|
||||||
* ), |
|
||||||
* ), |
|
||||||
* ), |
|
||||||
* ) |
|
||||||
* ~~~ |
|
||||||
* |
|
||||||
* Each log target can have a name and can be referenced via the [[targets]] property |
|
||||||
* as follows: |
|
||||||
* |
|
||||||
* ~~~ |
|
||||||
* Yii::$app->log->targets['file']->enabled = false; |
|
||||||
* ~~~ |
|
||||||
* |
|
||||||
* @author Qiang Xue <qiang.xue@gmail.com> |
|
||||||
* @since 2.0 |
|
||||||
*/ |
|
||||||
class Router extends Component |
|
||||||
{ |
|
||||||
/** |
|
||||||
* @var Target[] list of log target objects or configurations. If the latter, target objects will |
|
||||||
* be created in [[init()]] by calling [[Yii::createObject()]] with the corresponding object configuration. |
|
||||||
*/ |
|
||||||
public $targets = array(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Initializes this application component. |
|
||||||
* This method is invoked when the Router component is created by the application. |
|
||||||
* The method attaches the [[processLogs]] method to both the [[Logger::EVENT_FLUSH]] event |
|
||||||
* and the [[Logger::EVENT_FINAL_FLUSH]] event. |
|
||||||
*/ |
|
||||||
public function init() |
|
||||||
{ |
|
||||||
parent::init(); |
|
||||||
foreach ($this->targets as $name => $target) { |
|
||||||
if (!$target instanceof Target) { |
|
||||||
$this->targets[$name] = Yii::createObject($target); |
|
||||||
} |
|
||||||
} |
|
||||||
Yii::getLogger()->router = $this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Dispatches log messages to [[targets]]. |
|
||||||
* This method is called by [[Logger]] when its [[Logger::flush()]] method is called. |
|
||||||
* It will forward the messages to each log target registered in [[targets]]. |
|
||||||
* @param array $messages the messages to be processed |
|
||||||
* @param boolean $final whether this is the final call during a request cycle |
|
||||||
*/ |
|
||||||
public function dispatch($messages, $final = false) |
|
||||||
{ |
|
||||||
foreach ($this->targets as $target) { |
|
||||||
if ($target->enabled) { |
|
||||||
$target->collect($messages, $final); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,206 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\widgets; |
||||||
|
|
||||||
|
use Yii; |
||||||
|
use yii\base\Arrayable; |
||||||
|
use yii\base\Formatter; |
||||||
|
use yii\base\InvalidConfigException; |
||||||
|
use yii\base\Model; |
||||||
|
use yii\base\Widget; |
||||||
|
use yii\helpers\ArrayHelper; |
||||||
|
use yii\helpers\Html; |
||||||
|
use yii\helpers\Inflector; |
||||||
|
|
||||||
|
/** |
||||||
|
* DetailView displays the detail of a single data [[model]]. |
||||||
|
* |
||||||
|
* DetailView is best used for displaying a model in a regular format (e.g. each model attribute |
||||||
|
* is displayed as a row in a table.) The model can be either an instance of [[Model]] or |
||||||
|
* or an associative array. |
||||||
|
* |
||||||
|
* DetailView uses the [[attributes]] property to determines which model attributes |
||||||
|
* should be displayed and how they should be formatted. |
||||||
|
* |
||||||
|
* A typical usage of DetailView is as follows: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* \yii\widgets\DetailView::widget(array( |
||||||
|
* 'data' => $model, |
||||||
|
* 'attributes' => array( |
||||||
|
* 'title', // title attribute (in plain text) |
||||||
|
* 'description:html', // description attribute in HTML |
||||||
|
* array( // the owner name of the model |
||||||
|
* 'label' => 'Owner', |
||||||
|
* 'value' => $model->owner->name, |
||||||
|
* ), |
||||||
|
* ), |
||||||
|
* )); |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class DetailView extends Widget |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var array|object the data model whose details are to be displayed. This can be either a [[Model]] instance |
||||||
|
* or an associative array. |
||||||
|
*/ |
||||||
|
public $model; |
||||||
|
/** |
||||||
|
* @var array a list of attributes to be displayed in the detail view. Each array element |
||||||
|
* represents the specification for displaying one particular attribute. |
||||||
|
* |
||||||
|
* An attribute can be specified as a string in the format of "Name" or "Name:Type", where "Name" refers to |
||||||
|
* the attribute name, and "Type" represents the type of the attribute. The "Type" is passed to the [[Formatter::format()]] |
||||||
|
* method to format an attribute value into a displayable text. Please refer to [[Formatter]] for the supported types. |
||||||
|
* |
||||||
|
* An attribute can also be specified in terms of an array with the following elements: |
||||||
|
* |
||||||
|
* - name: the attribute name. This is required if either "label" or "value" is not specified. |
||||||
|
* - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name. |
||||||
|
* - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name |
||||||
|
* by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text |
||||||
|
* according to the "type" option. |
||||||
|
* - type: the type of the value that determines how the value would be formatted into a displayable text. |
||||||
|
* Please refer to [[Formatter]] for supported types. |
||||||
|
* - visible: whether the attribute is visible. If set to `false`, the attribute will be displayed. |
||||||
|
*/ |
||||||
|
public $attributes; |
||||||
|
/** |
||||||
|
* @var string|\Closure the template used to render a single attribute. If a string, the token `{label}` |
||||||
|
* and `{value}` will be replaced with the label and the value of the corresponding attribute. |
||||||
|
* If an anonymous function, the signature must be as follows: |
||||||
|
* |
||||||
|
* ~~~ |
||||||
|
* function ($attribute, $index, $widget) |
||||||
|
* ~~~ |
||||||
|
* |
||||||
|
* where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based |
||||||
|
* index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance. |
||||||
|
*/ |
||||||
|
public $template = "<tr><th>{label}</th><td>{value}</td></tr>"; |
||||||
|
/** |
||||||
|
* @var array the HTML attributes for the container tag of this widget. The "tag" option specifies |
||||||
|
* what container tag should be used. It defaults to "table" if not set. |
||||||
|
*/ |
||||||
|
public $options = array('class' => 'table table-striped'); |
||||||
|
/** |
||||||
|
* @var array|Formatter the formatter used to format model attribute values into displayable texts. |
||||||
|
* This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]] |
||||||
|
* instance. If this property is not set, the "formatter" application component will be used. |
||||||
|
*/ |
||||||
|
public $formatter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes the detail view. |
||||||
|
* This method will initialize required property values. |
||||||
|
*/ |
||||||
|
public function init() |
||||||
|
{ |
||||||
|
if ($this->model === null) { |
||||||
|
throw new InvalidConfigException('Please specify the "data" property.'); |
||||||
|
} |
||||||
|
if ($this->formatter == null) { |
||||||
|
$this->formatter = Yii::$app->getFormatter(); |
||||||
|
} elseif (is_array($this->formatter)) { |
||||||
|
$this->formatter = Yii::createObject($this->formatter); |
||||||
|
} elseif (!$this->formatter instanceof Formatter) { |
||||||
|
throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.'); |
||||||
|
} |
||||||
|
$this->normalizeAttributes(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Renders the detail view. |
||||||
|
* This is the main entry of the whole detail view rendering. |
||||||
|
*/ |
||||||
|
public function run() |
||||||
|
{ |
||||||
|
$rows = array(); |
||||||
|
$i = 0; |
||||||
|
foreach ($this->attributes as $attribute) { |
||||||
|
$rows[] = $this->renderAttribute($attribute, $i++); |
||||||
|
} |
||||||
|
|
||||||
|
$tag = ArrayHelper::remove($this->options, 'tag', 'table'); |
||||||
|
echo Html::tag($tag, implode("\n", $rows), $this->options); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Renders a single attribute. |
||||||
|
* @param array $attribute the specification of the attribute to be rendered. |
||||||
|
* @param integer $index the zero-based index of the attribute in the [[attributes]] array |
||||||
|
* @return string the rendering result |
||||||
|
*/ |
||||||
|
protected function renderAttribute($attribute, $index) |
||||||
|
{ |
||||||
|
if (is_string($this->template)) { |
||||||
|
return strtr($this->template, array( |
||||||
|
'{label}' => $attribute['label'], |
||||||
|
'{value}' => $this->formatter->format($attribute['value'], $attribute['type']), |
||||||
|
)); |
||||||
|
} else { |
||||||
|
return call_user_func($this->template, $attribute, $index, $this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Normalizes the attribute specifications. |
||||||
|
* @throws InvalidConfigException |
||||||
|
*/ |
||||||
|
protected function normalizeAttributes() |
||||||
|
{ |
||||||
|
if ($this->attributes === null) { |
||||||
|
if ($this->model instanceof Model) { |
||||||
|
$this->attributes = $this->model->attributes(); |
||||||
|
} elseif (is_object($this->model)) { |
||||||
|
$this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model)); |
||||||
|
} elseif (is_array($this->model)) { |
||||||
|
$this->attributes = array_keys($this->model); |
||||||
|
} else { |
||||||
|
throw new InvalidConfigException('The "data" property must be either an array or an object.'); |
||||||
|
} |
||||||
|
sort($this->attributes); |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($this->attributes as $i => $attribute) { |
||||||
|
if (is_string($attribute)) { |
||||||
|
if (!preg_match('/^(\w+)(\s*:\s*(\w+))?$/', $attribute, $matches)) { |
||||||
|
throw new InvalidConfigException('The attribute must be in the format of "Name" or "Name:Type"'); |
||||||
|
} |
||||||
|
$attribute = array( |
||||||
|
'name' => $matches[1], |
||||||
|
'type' => isset($matches[3]) ? $matches[3] : 'text', |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (!is_array($attribute)) { |
||||||
|
throw new InvalidConfigException('The attribute configuration must be an array.'); |
||||||
|
} |
||||||
|
|
||||||
|
if (!isset($attribute['type'])) { |
||||||
|
$attribute['type'] = 'text'; |
||||||
|
} |
||||||
|
if (isset($attribute['name'])) { |
||||||
|
$name = $attribute['name']; |
||||||
|
if (!isset($attribute['label'])) { |
||||||
|
$attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($name) : Inflector::camel2words($name, true); |
||||||
|
} |
||||||
|
if (!array_key_exists('value', $attribute)) { |
||||||
|
$attribute['value'] = ArrayHelper::getValue($this->model, $name); |
||||||
|
} |
||||||
|
} elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) { |
||||||
|
throw new InvalidConfigException('The attribute configuration requires the "name" element to determine the value and display label.'); |
||||||
|
} |
||||||
|
|
||||||
|
$this->attributes[$i] = $attribute; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue