Commit 0bdceee0 by Qiang Xue

Merge branch 'master' of git.yiisoft.com:yii2

Conflicts: tests/unit/data/ar/Customer.php
parents e24ebc3e faaff898
ActiveRecord
============
Query
-----
### Basic Queries
### Relational Queries
### Scopes
Working with Database
=====================
Architecture
------------
### Data Access Object (DAO)
* Connection
* Command
* DataReader
* Transaction
### Schema
* TableSchema
* ColumnSchema
### Query Builder
* Query
* QueryBuilder
### ActiveRecord
* ActiveRecord
* ActiveQuery
\ No newline at end of file
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\ar\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\ar\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. 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\ar\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
------------------------------------
......
......@@ -467,21 +467,21 @@ class ActiveFinder extends \yii\base\Object
}
}
if ($element->query->order !== null) {
if (!is_array($element->query->order)) {
$element->query->order = preg_split('/\s*,\s*/', trim($element->query->order), -1, PREG_SPLIT_NO_EMPTY);
if ($element->query->orderBy !== null) {
if (!is_array($element->query->orderBy)) {
$element->query->orderBy = preg_split('/\s*,\s*/', trim($element->query->orderBy), -1, PREG_SPLIT_NO_EMPTY);
}
foreach ($element->query->order as $order) {
$query->order[] = strtr($order, $prefixes);
foreach ($element->query->orderBy as $order) {
$query->orderBy[] = strtr($order, $prefixes);
}
}
if ($element->query->group !== null) {
if (!is_array($element->query->group)) {
$element->query->group = preg_split('/\s*,\s*/', trim($element->query->group), -1, PREG_SPLIT_NO_EMPTY);
if ($element->query->groupBy !== null) {
if (!is_array($element->query->groupBy)) {
$element->query->groupBy = preg_split('/\s*,\s*/', trim($element->query->groupBy), -1, PREG_SPLIT_NO_EMPTY);
}
foreach ($element->query->group as $group) {
$query->group[] = strtr($group, $prefixes);
foreach ($element->query->groupBy as $group) {
$query->groupBy[] = strtr($group, $prefixes);
}
}
......
......@@ -26,9 +26,9 @@ class ActiveMetaData
*/
public $modelClass;
/**
* @var array list of relations
* @var ActiveRecord the model instance that can be used to access non-static methods
*/
public $relations = array();
public $model;
/**
* Returns an instance of ActiveMetaData for the specified model class.
......@@ -55,21 +55,18 @@ class ActiveMetaData
public function __construct($modelClass)
{
$this->modelClass = $modelClass;
$tableName = $modelClass::tableName();
$this->table = $modelClass::getDbConnection()->getDriver()->getTableSchema($tableName);
$this->model = new $modelClass;
$tableName = $this->model->tableName();
$this->table = $this->model->getDbConnection()->getDriver()->getTableSchema($tableName);
if ($this->table === null) {
throw new Exception("Unable to find table '$tableName' for ActiveRecord class '$modelClass'.");
}
$primaryKey = $modelClass::primaryKey();
if ($primaryKey !== null) {
$primaryKey = $this->model->primaryKey();
if ($primaryKey !== $this->table->primaryKey) {
$this->table->fixPrimaryKey($primaryKey);
} elseif ($this->table->primaryKey === null) {
} elseif ($primaryKey === null) {
throw new Exception("The table '$tableName' for ActiveRecord class '$modelClass' does not have a primary key.");
}
foreach ($modelClass::relations() as $name => $config) {
$this->addRelation($name, $config);
}
}
/**
......
......@@ -59,15 +59,15 @@ class BaseQuery extends \yii\base\Component
* @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement.
* It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`).
*/
public $order;
public $orderBy;
/**
* @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement.
* It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`).
*/
public $group;
public $groupBy;
/**
* @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement.
* It can either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g.
* It can be either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g.
* `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`).
* @see join()
*/
......@@ -330,9 +330,9 @@ class BaseQuery extends \yii\base\Component
* @return BaseQuery the query object itself
* @see addGroup()
*/
public function group($columns)
public function groupBy($columns)
{
$this->group = $columns;
$this->groupBy = $columns;
return $this;
}
......@@ -347,16 +347,16 @@ class BaseQuery extends \yii\base\Component
*/
public function addGroup($columns)
{
if (empty($this->group)) {
$this->group = $columns;
if (empty($this->groupBy)) {
$this->groupBy = $columns;
} else {
if (!is_array($this->group)) {
$this->group = preg_split('/\s*,\s*/', trim($this->group), -1, PREG_SPLIT_NO_EMPTY);
if (!is_array($this->groupBy)) {
$this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY);
}
if (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$this->group = array_merge($this->group, $columns);
$this->groupBy = array_merge($this->groupBy, $columns);
}
return $this;
}
......@@ -428,9 +428,9 @@ class BaseQuery extends \yii\base\Component
* @return BaseQuery the query object itself
* @see addOrder()
*/
public function order($columns)
public function orderBy($columns)
{
$this->order = $columns;
$this->orderBy = $columns;
return $this;
}
......@@ -443,18 +443,18 @@ class BaseQuery extends \yii\base\Component
* @return BaseQuery the query object itself
* @see order()
*/
public function addOrder($columns)
public function addOrderBy($columns)
{
if (empty($this->order)) {
$this->order = $columns;
if (empty($this->orderBy)) {
$this->orderBy = $columns;
} else {
if (!is_array($this->order)) {
$this->order = preg_split('/\s*,\s*/', trim($this->order), -1, PREG_SPLIT_NO_EMPTY);
if (!is_array($this->orderBy)) {
$this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY);
}
if (!is_array($columns)) {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
}
$this->order = array_merge($this->order, $columns);
$this->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
......@@ -540,7 +540,7 @@ class BaseQuery extends \yii\base\Component
* takes precedence over this query.
* - [[where]], [[having]]: the new query's corresponding property value
* will be 'AND' together with the existing one.
* - [[params]], [[order]], [[group]], [[join]], [[union]]: the new query's
* - [[params]], [[orderBy]], [[groupBy]], [[join]], [[union]]: the new query's
* corresponding property value will be appended to the existing one.
*
* In general, the merging makes the resulting query more restrictive and specific.
......@@ -591,12 +591,12 @@ class BaseQuery extends \yii\base\Component
$this->addParams($query->params);
}
if ($query->order !== null) {
$this->addOrder($query->order);
if ($query->orderBy !== null) {
$this->addOrderBy($query->orderBy);
}
if ($query->group !== null) {
$this->addGroup($query->group);
if ($query->groupBy !== null) {
$this->addGroup($query->groupBy);
}
if ($query->join !== null) {
......
......@@ -461,6 +461,17 @@ class Connection extends \yii\base\ApplicationComponent
}
/**
* Obtains the metadata for the named table.
* @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
* @param boolean $refresh whether to reload the table schema even if it is found in the cache.
* @return TableSchema table metadata. Null if the named table does not exist.
*/
public function getTableSchema($name, $refresh = false)
{
return $this->getDriver()->getTableSchema($name, $refresh);
}
/**
* Returns the ID of the last inserted row or sequence value.
* @param string $sequenceName name of the sequence object (required by some DBMS)
* @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
......
......@@ -94,7 +94,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable
/**
* Advances the reader to the next row in a result set.
* @return array|false the current row, false if no more row available
* @return array the current row, false if no more row available
*/
public function read()
{
......@@ -104,7 +104,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable
/**
* Returns a single column from the next row of a result set.
* @param integer $columnIndex zero-based column index
* @return mixed|false the column of the current row, false if no more row available
* @return mixed the column of the current row, false if no more row available
*/
public function readColumn($columnIndex)
{
......@@ -115,7 +115,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable
* Returns an object populated with the next row of data.
* @param string $className class name of the object to be created and populated
* @param array $fields Elements of this array are passed to the constructor
* @return mixed|false the populated object, false if no more row of data available
* @return mixed the populated object, false if no more row of data available
*/
public function readObject($className, $fields)
{
......@@ -149,7 +149,7 @@ class DataReader extends \yii\base\Object implements \Iterator, \Countable
/**
* Closes the reader.
* This frees up the resources allocated for executing this SQL statement.
* Read attemps after this method call are unpredictable.
* Read attempts after this method call are unpredictable.
*/
public function close()
{
......
......@@ -75,6 +75,7 @@ class Query extends BaseQuery
$qb->query = $this;
return call_user_func_array(array($qb, $method), $params);
} else {
/** @var $qb QueryBuilder */
return $qb->build($this);
}
}
......
......@@ -69,10 +69,10 @@ class QueryBuilder extends \yii\base\Object
$this->buildFrom($query->from),
$this->buildJoin($query->join),
$this->buildWhere($query->where),
$this->buildGroup($query->group),
$this->buildGroup($query->groupBy),
$this->buildHaving($query->having),
$this->buildUnion($query->union),
$this->buildOrder($query->order),
$this->buildOrder($query->orderBy),
$this->buildLimit($query->limit, $query->offset),
);
return implode($this->separator, array_filter($clauses));
......@@ -92,7 +92,7 @@ class QueryBuilder extends \yii\base\Object
*
* @param string $table the table that new rows will be inserted into.
* @param array $columns the column data (name=>value) to be inserted into the table.
* @return integer number of rows affected by the execution.
* @return string the INSERT SQL
*/
public function insert($table, $columns)
{
......@@ -139,7 +139,7 @@ class QueryBuilder extends \yii\base\Object
* @param mixed $condition the condition that will be put in the WHERE part. Please
* refer to [[Query::where()]] on how to specify condition.
* @param array $params the parameters to be bound to the query.
* @return integer number of rows affected by the execution.
* @return string the UPDATE SQL
*/
public function update($table, $columns, $condition = '', $params = array())
{
......@@ -180,7 +180,7 @@ class QueryBuilder extends \yii\base\Object
* @param mixed $condition the condition that will be put in the WHERE part. Please
* refer to [[Query::where()]] on how to specify condition.
* @param array $params the parameters to be bound to the query.
* @return integer number of rows affected by the execution.
* @return string the DELETE SQL
*/
public function delete($table, $condition = '', $params = array())
{
......@@ -566,9 +566,7 @@ class QueryBuilder extends \yii\base\Object
list($column, $values) = $operands;
if (!is_array($values)) {
$values = array($values);
}
$values = (array)$values;
if ($values === array()) {
return $operator === 'in' ? '0=1' : '';
......@@ -594,9 +592,7 @@ class QueryBuilder extends \yii\base\Object
list($column, $values) = $operands;
if (!is_array($values)) {
$values = array($values);
}
$values = (array)$values;
if ($values === array()) {
return $operator === 'like' || $operator === 'or like' ? '0=1' : '';
......
......@@ -42,8 +42,6 @@ namespace yii\validators;
* - `captcha`: [[CaptchaValidator]]
* - `default`: [[DefaultValueValidator]]
* - `exist`: [[ExistValidator]]
* - `safe`: [[SafeValidator]]
* - `unsafe`: [[UnsafeValidator]]
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
......@@ -58,8 +56,6 @@ abstract class Validator extends \yii\base\Component
'match' => '\yii\validators\RegularExpressionValidator',
'email' => '\yii\validators\EmailValidator',
'url' => '\yii\validators\UrlValidator',
'safe' => '\yii\validators\SafeValidator',
'unsafe' => '\yii\validators\UnsafeValidator',
'filter' => '\yii\validators\FilterValidator',
'captcha' => '\yii\validators\CaptchaValidator',
'default' => '\yii\validators\DefaultValueValidator',
......@@ -103,11 +99,6 @@ abstract class Validator extends \yii\base\Component
*/
public $skipOnError = true;
/**
* @var boolean whether attributes listed with this validator should be considered safe for
* massive assignment. Defaults to true.
*/
public $safe = true;
/**
* @var boolean whether to enable client-side validation. Defaults to true.
* Please refer to [[\yii\web\ActiveForm::enableClientValidation]] for more details about
* client-side validation.
......@@ -187,8 +178,10 @@ abstract class Validator extends \yii\base\Component
/**
* Validates the specified object.
* @param \yii\base\Model $object the data object being validated
* @param array $attributes the list of attributes to be validated. Defaults to null,
* meaning every attribute listed in [[attributes]] will be validated.
* @param array|null $attributes the list of attributes to be validated.
* Note that if an attribute is not associated with the validator,
* it will be ignored.
* If this parameter is null, every attribute listed in [[attributes]] will be validated.
*/
public function validate($object, $attributes = null)
{
......@@ -228,10 +221,11 @@ abstract class Validator extends \yii\base\Component
}
/**
* Returns a value indicating whether the validator applies to the specified scenario.
* A validator applies to a scenario as long as any of the following conditions is met:
* Returns a value indicating whether the validator is active for the given scenario and attribute.
*
* A validator is active if
*
* - the validator's `on` property is empty
* - the validator's `on` property is empty, or
* - the validator's `on` property contains the specified scenario
*
* @param string $scenario scenario name
......@@ -239,7 +233,7 @@ abstract class Validator extends \yii\base\Component
* the method will also check if the attribute appears in [[attributes]].
* @return boolean whether the validator applies to the specified scenario.
*/
public function applyTo($scenario, $attribute = null)
public function isActive($scenario, $attribute = null)
{
$applies = !isset($this->except[$scenario]) && (empty($this->on) || isset($this->on[$scenario]));
return $attribute === null ? $applies : $applies && in_array($attribute, $this->attributes, true);
......
......@@ -429,7 +429,7 @@ class CSort extends CComponent
$attributes = $this->attributes;
} else {
if ($this->modelClass !== null) {
$attributes = CActiveRecord::model($this->modelClass)->attributeNames();
$attributes = CActiveRecord::model($this->modelClass)->attributes();
} else {
return false;
}
......
......@@ -8,23 +8,14 @@ class Customer extends ActiveRecord
const STATUS_ACTIVE = 1;
const STATUS_INACTIVE = 2;
public static function tableName()
public function tableName()
{
return 'tbl_customer';
}
public static function relations()
{
return array(
'orders:Order[]' => array(
'link' => array('customer_id' => 'id'),
),
);
}
public function orders()
{
return $this->hasMany('Order', array('id' => 'customer_id'));
return $this->hasMany('Order', array('customer_id' => 'id'));
}
/**
......@@ -33,6 +24,6 @@ class Customer extends ActiveRecord
*/
public function active($query)
{
return $query->andWhere('@.`status` = ' . self::STATUS_ACTIVE);
return $query->andWhere('`status` = ' . self::STATUS_ACTIVE);
}
}
\ No newline at end of file
......@@ -4,14 +4,8 @@ namespace yiiunit\data\ar;
class Item extends ActiveRecord
{
public static function tableName()
public function tableName()
{
return 'tbl_item';
}
public static function relations()
{
return array(
);
}
}
\ No newline at end of file
......@@ -4,41 +4,31 @@ namespace yiiunit\data\ar;
class Order extends ActiveRecord
{
public static function tableName()
public function tableName()
{
return 'tbl_order';
}
public static function relations()
public function customer()
{
return $this->hasOne('Customer', array('id' => 'customer_id'));
}
public function orderItems()
{
return $this->hasMany('OrderItem', array('order_id' => 'id'));
}
public function items()
{
return $this->hasMany('Item', array('id' => 'item_id'))
->via('orderItems')->orderBy('id');
}
public function books()
{
return array(
'customer:Customer' => array(
'link' => array('id' => 'customer_id'),
),
'orderItems:OrderItem' => array(
'link' => array('order_id' => 'id'),
),
'items:Item[]' => array(
'via' => 'orderItems',
'link' => array(
'id' => 'item_id',
),
'order' => '@.id',
),
'books:Item[]' => array(
'joinType' => 'INNER JOIN',
'via' => array(
'table' => 'tbl_order_item',
'link' => array(
'order_id' => 'id',
),
),
'link' => array(
'id' => 'item_id',
),
'on' => '@.category_id = 1',
),
);
return $this->manyMany('Item', array('id' => 'item_id'), 'tbl_order_item', array('item_id', 'id'))
->where('category_id = 1');
}
public function customer()
......
......@@ -4,20 +4,18 @@ namespace yiiunit\data\ar;
class OrderItem extends ActiveRecord
{
public static function tableName()
public function tableName()
{
return 'tbl_order_item';
}
public static function relations()
public function order()
{
return array(
'order:Order' => array(
'link' => array('order_id' => 'id'),
),
'item:Item' => array(
'link' => array('item_id' => 'id'),
),
);
return $this->hasOne('Order', array('id' => 'order_id'));
}
public function item()
{
return $this->hasOne('Item', array('id' => 'item_id'));
}
}
\ No newline at end of file
......@@ -56,14 +56,14 @@ class QueryTest extends \yiiunit\MysqlTestCase
function testGroup()
{
$query = new Query;
$query->group('team');
$this->assertEquals('team', $query->group);
$query->groupBy('team');
$this->assertEquals('team', $query->groupBy);
$query->addGroup('company');
$this->assertEquals(array('team', 'company'), $query->group);
$this->assertEquals(array('team', 'company'), $query->groupBy);
$query->addGroup('age');
$this->assertEquals(array('team', 'company', 'age'), $query->group);
$this->assertEquals(array('team', 'company', 'age'), $query->groupBy);
}
function testHaving()
......@@ -85,14 +85,14 @@ class QueryTest extends \yiiunit\MysqlTestCase
function testOrder()
{
$query = new Query;
$query->order('team');
$this->assertEquals('team', $query->order);
$query->orderBy('team');
$this->assertEquals('team', $query->orderBy);
$query->addOrder('company');
$this->assertEquals(array('team', 'company'), $query->order);
$query->addOrderBy('company');
$this->assertEquals(array('team', 'company'), $query->orderBy);
$query->addOrder('age');
$this->assertEquals(array('team', 'company', 'age'), $query->order);
$query->addOrderBy('age');
$this->assertEquals(array('team', 'company', 'age'), $query->orderBy);
}
function testLimitOffset()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment