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); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -1,11 +1,11 @@ | ||||
| ## Spicy Rice font | ||||
| 
 | ||||
| * **Author:** Brian J. Bonislawsky, Astigmatic (AOETI, Astigmatic One Eye Typographic Institute) | ||||
| * **License:** SIL Open Font License (OFL), version 1.1, [notes and FAQ](http://scripts.sil.org/OFL) | ||||
| 
 | ||||
| ## Links | ||||
| 
 | ||||
| * [Astigmatic](http://www.astigmatic.com/) | ||||
| * [Google WebFonts](http://www.google.com/webfonts/specimen/Spicy+Rice) | ||||
| * [fontsquirrel.com](http://www.fontsquirrel.com/fonts/spicy-rice) | ||||
| * [fontspace.com](http://www.fontspace.com/astigmatic-one-eye-typographic-institute/spicy-rice) | ||||
| ## Spicy Rice font | ||||
| 
 | ||||
| * **Author:** Brian J. Bonislawsky, Astigmatic (AOETI, Astigmatic One Eye Typographic Institute) | ||||
| * **License:** SIL Open Font License (OFL), version 1.1, [notes and FAQ](http://scripts.sil.org/OFL) | ||||
| 
 | ||||
| ## Links | ||||
| 
 | ||||
| * [Astigmatic](http://www.astigmatic.com/) | ||||
| * [Google WebFonts](http://www.google.com/webfonts/specimen/Spicy+Rice) | ||||
| * [fontsquirrel.com](http://www.fontsquirrel.com/fonts/spicy-rice) | ||||
| * [fontspace.com](http://www.fontspace.com/astigmatic-one-eye-typographic-institute/spicy-rice) | ||||
|  | ||||
| @ -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