resurtm
12 years ago
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 |
||||
======= |
||||
|
||||
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) |
||||
* [Installation](installation.md) |
||||
* [Bootstrap with Yii](bootstrap.md) |
||||
* [MVC Overview](mvc.md) |
||||
* [Controller](controller.md) |
||||
* [Model](model.md) |
||||
* [View](view.md) |
||||
* [Application](application.md) |
||||
* [Form](form.md) |
||||
* [Data Validation](validation.md) |
||||
* [Database Access Objects](dao.md) |
||||
* [Query Builder](query-builder.md) |
||||
* [ActiveRecord](active-record.md) |
||||
* [Database Migration](migration.md) |
||||
* [Caching](caching.md) |
||||
* [Internationalization](i18n.md) |
||||
* [Extending Yii](extension.md) |
||||
* [Authentication](authentication.md) |
||||
* [Authorization](authorization.md) |
||||
* [Logging](logging.md) |
||||
* [URL Management](url.md) |
||||
* [Theming](theming.md) |
||||
* [Error Handling](error.md) |
||||
* [Template](template.md) |
||||
* [Console Application](console.md) |
||||
* [Security](security.md) |
||||
* [Performance Tuning](performance.md) |
||||
* [Testing](testing.md) |
||||
* [Automatic Code Generation](gii.md) |
||||
* [Upgrading from 1.1 to 2.0](upgrade-from-v1.md) |
||||
Introduction |
||||
============ |
||||
|
||||
- [Overview](overview.md) |
||||
|
||||
Getting started |
||||
=============== |
||||
|
||||
- [Installation](installation.md) |
||||
- [Bootstrap with Yii](bootstrap.md) |
||||
- [Configuration](configuration.md) |
||||
|
||||
Base concepts |
||||
============= |
||||
|
||||
- [MVC Overview](mvc.md) |
||||
- [Controller](controller.md) |
||||
- [Model](model.md) |
||||
- [View](view.md) |
||||
- [Application](application.md) |
||||
|
||||
Database |
||||
======== |
||||
|
||||
- [Basics](database-basics.md) |
||||
- [Query Builder](query-builder.md) |
||||
- [ActiveRecord](active-record.md) |
||||
- [Database Migration](migration.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