|
|
|
@ -2,18 +2,21 @@ 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 columns of the corresponding database row. For example, a `Customer` object is associated with a row in the |
|
|
|
|
`tbl_customer` table. |
|
|
|
|
The idea is that an [[ActiveRecord]] object is associated with a row in a database table and its attributes are mapped |
|
|
|
|
to the columns of the corresponding table columns. Reading an ActiveRecord attribute is equivalent to accessing |
|
|
|
|
the corresponding table column. For example, a `Customer` object is associated with a row in the |
|
|
|
|
`tbl_customer` table, and its `name` attribute is mapped to the `name` column in the `tbl_customer` table. |
|
|
|
|
To get the value of the `name` column in the table row, you can simply use the expression `$customer->name`, |
|
|
|
|
just like reading an object property. |
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
Instead of writing raw SQL statements to perform database queries, you can call intuitive methods provided |
|
|
|
|
by ActiveRecord to achieve the same goals. For example, calling [[ActiveRecord::save()|save()]] would |
|
|
|
|
insert or update a row in the associated table of the ActiveRecord class: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
$customer = new Customer(); |
|
|
|
|
$customer->name = 'Qiang'; |
|
|
|
|
$customer->save(); |
|
|
|
|
$customer->save(); // a new row is inserted into tbl_customer |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -24,7 +27,9 @@ To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and
|
|
|
|
|
implement the `tableName` method like the following: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
class Customer extends \yii\db\ActiveRecord |
|
|
|
|
use yii\db\ActiveRecord; |
|
|
|
|
|
|
|
|
|
class Customer extends ActiveRecord |
|
|
|
|
{ |
|
|
|
|
/** |
|
|
|
|
* @return string the name of the table associated with this ActiveRecord class. |
|
|
|
@ -52,8 +57,6 @@ return array(
|
|
|
|
|
'dsn' => 'mysql:host=localhost;dbname=testdb', |
|
|
|
|
'username' => 'demo', |
|
|
|
|
'password' => 'demo', |
|
|
|
|
// turn on schema caching to improve performance in production mode |
|
|
|
|
// 'schemaCacheDuration' => 3600, |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
@ -62,19 +65,22 @@ return array(
|
|
|
|
|
Please read the [Database basics](database-basics.md) section to learn more on how to configure |
|
|
|
|
and use database connections. |
|
|
|
|
|
|
|
|
|
> Tip: To use a different database connection, you may override the [[ActiveRecord::getDb()]] method. |
|
|
|
|
You may create a base ActiveRecord class and override its [[ActiveRecord::getDb()]] method. You |
|
|
|
|
then extend from this base class for all those ActiveRecord classes that need to use the same |
|
|
|
|
DB connection. |
|
|
|
|
|
|
|
|
|
Getting Data from Database |
|
|
|
|
-------------------------- |
|
|
|
|
|
|
|
|
|
There are two ActiveRecord methods for getting data from database: |
|
|
|
|
Querying Data from Database |
|
|
|
|
--------------------------- |
|
|
|
|
|
|
|
|
|
- [[find()]] |
|
|
|
|
- [[findBySql()]] |
|
|
|
|
There are two ActiveRecord methods for querying data from database: |
|
|
|
|
|
|
|
|
|
They both return an [[ActiveQuery]] instance. Coupled with various query methods provided |
|
|
|
|
by [[ActiveQuery]], ActiveRecord supports very flexible and powerful data retrieval approaches. |
|
|
|
|
- [[ActiveRecord::find()]] |
|
|
|
|
- [[ActiveRecord::findBySql()]] |
|
|
|
|
|
|
|
|
|
The followings are some examples, |
|
|
|
|
They both return an [[ActiveQuery]] instance which extends from [[Query]] and thus supports |
|
|
|
|
the same set of flexible and powerful DB query methods. The followings are some examples, |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
// to retrieve all *active* customers and order them by their ID: |
|
|
|
@ -101,8 +107,10 @@ $count = Customer::find()
|
|
|
|
|
->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 |
|
|
|
|
$customers = Customer::find() |
|
|
|
|
->asArray() |
|
|
|
|
->all(); |
|
|
|
|
// each element of $customers is an array of name-value pairs |
|
|
|
|
|
|
|
|
|
// to index the result by customer IDs: |
|
|
|
|
$customers = Customer::find()->indexBy('id')->all(); |
|
|
|
@ -115,9 +123,9 @@ 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. |
|
|
|
|
name and is case-sensitive. |
|
|
|
|
|
|
|
|
|
To read the value of a column, we can use the following expression: |
|
|
|
|
To read the value of a column, you can use the following expression: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
// "id" is the name of a column in the table associated with $customer ActiveRecord object |
|
|
|
@ -126,28 +134,29 @@ $id = $customer->id;
|
|
|
|
|
$id = $customer->getAttribute('id'); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
We can get all column values through the [[attributes]] property: |
|
|
|
|
You can get all column values through the [[ActiveRecord::attributes]] property: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
$values = $customer->attributes; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Saving Data to Database |
|
|
|
|
----------------------- |
|
|
|
|
Manipulating Data in Database |
|
|
|
|
----------------------------- |
|
|
|
|
|
|
|
|
|
ActiveRecord provides the following methods to insert, update and delete data in the database: |
|
|
|
|
|
|
|
|
|
- [[save()]] |
|
|
|
|
- [[insert()]] |
|
|
|
|
- [[update()]] |
|
|
|
|
- [[delete()]] |
|
|
|
|
- [[updateCounters()]] |
|
|
|
|
- [[updateAll()]] |
|
|
|
|
- [[updateAllCounters()]] |
|
|
|
|
- [[deleteAll()]] |
|
|
|
|
|
|
|
|
|
Note that [[updateAll()]], [[updateAllCounters()]] and [[deleteAll()]] apply to the whole database |
|
|
|
|
- [[ActiveRecord::save()|save()]] |
|
|
|
|
- [[ActiveRecord::insert()|insert()]] |
|
|
|
|
- [[ActiveRecord::update()|update()]] |
|
|
|
|
- [[ActiveRecord::delete()|delete()]] |
|
|
|
|
- [[ActiveRecord::updateCounters()|updateCounters()]] |
|
|
|
|
- [[ActiveRecord::updateAll()|updateAll()]] |
|
|
|
|
- [[ActiveRecord::updateAllCounters()|updateAllCounters()]] |
|
|
|
|
- [[ActiveRecord::deleteAll()|deleteAll()]] |
|
|
|
|
|
|
|
|
|
Note that [[ActiveRecord::updateAll()|updateAll()]], [[ActiveRecord::updateAllCounters()|updateAllCounters()]] |
|
|
|
|
and [[ActiveRecord::deleteAll()|deleteAll()]] are static methods and 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: |
|
|
|
@ -163,25 +172,25 @@ $customer->save(); // equivalent to $customer->insert();
|
|
|
|
|
$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 |
|
|
|
|
// to increment the age of ALL customers by 1 |
|
|
|
|
Customer::updateAllCounters(array('age' => 1)); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Getting Relational Data |
|
|
|
|
----------------------- |
|
|
|
|
Querying 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. |
|
|
|
|
You can use ActiveRecord to query the relational data of a table. The relational data returned can |
|
|
|
|
be accessed like a property of the ActiveRecord object associated with the primary table. |
|
|
|
|
For example, with an appropriate relation declaration, by accessing `$customer->orders` you may obtain |
|
|
|
|
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, |
|
|
|
|
To declare a relation, define a getter method which returns an [[ActiveRelation]] object. For example, |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
class Customer extends \yii\db\ActiveRecord |
|
|
|
@ -201,11 +210,10 @@ class Order extends \yii\db\ActiveRecord
|
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
The methods [[ActiveRecord::hasMany()]] and [[ActiveRecord::hasOne()]] used in the above |
|
|
|
|
are used to model the many-one relationship and one-one relationship in a relational database. |
|
|
|
|
For example, a customer has many orders, and an order has one customer. |
|
|
|
|
Both methods take two parameters and return an [[ActiveRelation]] object: |
|
|
|
|
|
|
|
|
|
- `$class`: the name of the class of the related model(s). If specified without |
|
|
|
|
a namespace, the namespace of the related model class will be taken from the declaring class. |
|
|
|
@ -214,22 +222,30 @@ Both methods take two parameters:
|
|
|
|
|
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: |
|
|
|
|
After declaring relations, getting relational data is as easy as accessing a component property |
|
|
|
|
that is defined by the corresponding getter method: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
// the orders of a customer |
|
|
|
|
$customer = Customer::find($id); |
|
|
|
|
// get the orders of a customer |
|
|
|
|
$customer = Customer::find(1); |
|
|
|
|
$orders = $customer->orders; // $orders is an array of Order objects |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Behind the scene, the above code executes the following two SQL queries, one for each line of code: |
|
|
|
|
|
|
|
|
|
// the customer of the first order |
|
|
|
|
$customer2 = $orders[0]->customer; // $customer == $customer2 |
|
|
|
|
```sql |
|
|
|
|
SELECT * FROM tbl_customer WHERE id=1; |
|
|
|
|
SELECT * FROM tbl_order WHERE customer_id=1; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
> Tip: If you access the expression `$customer->orders` again, will it perform the second SQL query again? |
|
|
|
|
Nope. The SQL query is only performed the first time when this expression is accessed. Any further |
|
|
|
|
accesses will only return the previously fetched results that are cached internally. If you want to re-query |
|
|
|
|
the relational data, simply unset the existing one first: `unset($customer->orders);`. |
|
|
|
|
|
|
|
|
|
Sometimes, you may want to pass parameters to a relational query. For example, instead of returning |
|
|
|
|
all orders of a customer, you may want to return only big orders whose subtotal exceeds a specified amount. |
|
|
|
|
To do so, declare a `bigOrders` relation with the following getter method: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
class Customer extends \yii\db\ActiveRecord |
|
|
|
@ -243,8 +259,22 @@ class Customer extends \yii\db\ActiveRecord
|
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Remember that `hasMany()` returns an [[ActiveRelation]] object which extends from [[ActiveQuery]] |
|
|
|
|
and thus supports the same set of querying methods as [[ActiveQuery]]. |
|
|
|
|
|
|
|
|
|
With the above declaration, if you access `$customer->bigOrders`, it will only return the orders |
|
|
|
|
whose subtotal is greater than 100. To specify a different threshold value, use the following code: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
$orders = $customer->getBigOrders(200)->all(); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Relations with Pivot Table |
|
|
|
|
-------------------------- |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
[pivot table](http://en.wikipedia.org/wiki/Pivot_table). To declare such relations, we can customize |
|
|
|
|
the [[ActiveRelation]] object by calling its [[ActiveRelation::via()]] or [[ActiveRelation::viaTable()]] |
|
|
|
|
method. |
|
|
|
|
|
|
|
|
@ -283,7 +313,10 @@ class Order extends \yii\db\ActiveRecord
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
When you access the related objects the first time, behind the scene ActiveRecord performs a DB query |
|
|
|
|
Lazy and Eager Loading |
|
|
|
|
---------------------- |
|
|
|
|
|
|
|
|
|
As described earlier, when you access the related objects the first time, ActiveRecord will perform 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, |
|
|
|
|
|
|
|
|
@ -296,9 +329,7 @@ $orders = $customer->orders;
|
|
|
|
|
$orders2 = $customer->orders; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Lazy loading is very convenient to use. However, it may suffer from performance |
|
|
|
|
issue in the following scenario: |
|
|
|
|
Lazy loading is very convenient to use. However, it may suffer from a performance issue in the following scenario: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
// SQL executed: SELECT * FROM tbl_customer LIMIT 100 |
|
|
|
@ -313,12 +344,12 @@ foreach ($customers as $customer) {
|
|
|
|
|
|
|
|
|
|
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. |
|
|
|
|
is performed to bring back the orders of that customer. |
|
|
|
|
|
|
|
|
|
To solve the above performance problem, you can use the so-called *eager loading* approach by calling [[ActiveQuery::with()]]: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
// SQL executed: SELECT * FROM tbl_customer LIMIT 100 |
|
|
|
|
// 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(); |
|
|
|
@ -333,7 +364,7 @@ foreach ($customers as $customer) {
|
|
|
|
|
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 |
|
|
|
|
Sometimes, you may want to customize the relational queries on the fly. This can be |
|
|
|
|
done for both lazy loading and eager loading. For example, |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
@ -357,8 +388,8 @@ Working with Relationships
|
|
|
|
|
ActiveRecord provides the following two methods for establishing and breaking a |
|
|
|
|
relationship between two ActiveRecord objects: |
|
|
|
|
|
|
|
|
|
- [[link()]] |
|
|
|
|
- [[unlink()]] |
|
|
|
|
- [[ActiveRecord::link()|link()]] |
|
|
|
|
- [[ActiveRecord::unlink()|unlink()]] |
|
|
|
|
|
|
|
|
|
For example, given a customer and a new order, we can use the following code to make the |
|
|
|
|
order owned by the customer: |
|
|
|
@ -378,9 +409,9 @@ 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. |
|
|
|
|
automatically when `save()` is performed. If data validation fails, the saving operation will be cancelled. |
|
|
|
|
|
|
|
|
|
For more details refer to [Model](model.md) section of the guide. |
|
|
|
|
For more details refer to the [Model](model.md) section of this guide. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Life Cycles of an ActiveRecord Object |
|
|
|
@ -419,7 +450,7 @@ Finally when calling [[delete()]] to delete an ActiveRecord, we will have the fo
|
|
|
|
|
Scopes |
|
|
|
|
------ |
|
|
|
|
|
|
|
|
|
A scope is a method that customizes a given [[ActiveQuery]] object. Scope methods are defined |
|
|
|
|
A scope is a method that customizes a given [[ActiveQuery]] object. Scope methods are static and 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: |
|
|
|
|
|
|
|
|
@ -466,12 +497,16 @@ $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 |
|
|
|
|
------------------------------- |
|
|
|
|
|
|
|
|
|
Transactional operations |
|
|
|
|
------------------------ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
When a few DB operations are related and are executed |
|
|
|
|
|
|
|
|
|
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 the 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 |
|
|
|
@ -596,6 +631,16 @@ class ProductController extends \yii\web\Controller
|
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Optimistic Locks |
|
|
|
|
---------------- |
|
|
|
|
|
|
|
|
|
TODO |
|
|
|
|
|
|
|
|
|
Dirty Attributes |
|
|
|
|
---------------- |
|
|
|
|
|
|
|
|
|
TODO |
|
|
|
|
|
|
|
|
|
See also |
|
|
|
|
-------- |
|
|
|
|
|
|
|
|
|