Browse Source

...

tags/2.0.0-beta
Qiang Xue 13 years ago
parent
commit
a92acf65ce
  1. 131
      framework/base/Component.php
  2. 128
      framework/base/Model.php
  3. 76
      framework/db/ar/ActiveMetaData.php
  4. 233
      framework/db/ar/ActiveQuery.php
  5. 500
      framework/db/ar/ActiveRecord.php
  6. 43
      framework/db/ar/ActiveRelation.php
  7. 715
      framework/db/dao/BaseQuery.php
  8. 716
      framework/db/dao/Query.php
  9. 23
      framework/util/Text.php
  10. 23
      tests/unit/framework/base/BehaviorTest.php

131
framework/base/Component.php

@ -29,12 +29,12 @@ namespace yii\base;
* Event names are case-insensitive. * Event names are case-insensitive.
* *
* An event can be attached with one or multiple PHP callbacks, called *event handlers*. * An event can be attached with one or multiple PHP callbacks, called *event handlers*.
* One can call [[raiseEvent]] to raise an event. When an event is raised, the attached * One can call [[raiseEvent()]] to raise an event. When an event is raised, the attached
* event handlers will be invoked automatically in the order they are attached to the event. * event handlers will be invoked automatically in the order they are attached to the event.
* *
* To attach an event handler to an event, call [[attachEventHandler]]. Alternatively, * To attach an event handler to an event, call [[attachEventHandler]]. Alternatively,
* you can use the assignment syntax: `$component->onClick = $callback;`, * you can use the assignment syntax: `$component->onClick = $callback;`,
* where `$callback` refers to a valid PHP callback, which can be one of the followings: * where `$callback` refers to a valid PHP callback which can be one of the followings:
* *
* - global function: `'handleOnClick'` * - global function: `'handleOnClick'`
* - object method: `array($object, 'handleOnClick')` * - object method: `array($object, 'handleOnClick')`
@ -59,21 +59,25 @@ namespace yii\base;
* ~~~ * ~~~
* *
* *
* A behavior is an instance of [[Behavior]] or its child class. When a behavior is * A behavior is an instance of [[Behavior]] or its child class. A component can be attached
* attached to a component, its public properties and methods can be accessed via the * with one or multiple behaviors. When a behavior is attached to a component, its public
* component directly, as if the component owns those properties and methods. * properties and methods can be accessed via the component directly, as if the component owns
* those properties and methods.
* *
* Multiple behaviors can be attached to the same component. * To attach a behavior to a component, declare it in [[behaviors()]], or explicitly call [[attachBehavior]].
*
* To attach a behavior to a component, call [[attachBehavior]]; to detach a behavior
* from the component, call [[detachBehavior]].
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Component extends Object class Component extends Object
{ {
/**
* @var Vector[] the attached event handlers (event name => handlers)
*/
private $_e; private $_e;
/**
* @var Behavior[] the attached behaviors (behavior name => behavior)
*/
private $_b; private $_b;
/** /**
@ -104,12 +108,11 @@ class Component extends Object
$this->_e[$name] = new Vector; $this->_e[$name] = new Vector;
} }
return $this->_e[$name]; return $this->_e[$name];
} elseif (isset($this->_b[$name])) { // behavior } else { // behavior property
return $this->_b[$name]; $this->ensureBehaviors();
} elseif (is_array($this->_b)) { // a behavior property foreach ($this->_b as $behavior) {
foreach ($this->_b as $object) { if ($behavior->canGetProperty($name)) {
if ($object->canGetProperty($name)) { return $behavior->$name;
return $object->$name;
} }
} }
} }
@ -143,10 +146,11 @@ class Component extends Object
$this->_e[$name] = new Vector; $this->_e[$name] = new Vector;
} }
return $this->_e[$name]->add($value); return $this->_e[$name]->add($value);
} elseif (is_array($this->_b)) { // behavior } else { // behavior property
foreach ($this->_b as $object) { $this->ensureBehaviors();
if ($object->canSetProperty($name)) { foreach ($this->_b as $behavior) {
return $object->$name = $value; if ($behavior->canSetProperty($name)) {
return $behavior->$name = $value;
} }
} }
} }
@ -178,12 +182,11 @@ class Component extends Object
} elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // has event handler } elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // has event handler
$name = strtolower($name); $name = strtolower($name);
return isset($this->_e[$name]) && $this->_e[$name]->getCount(); return isset($this->_e[$name]) && $this->_e[$name]->getCount();
} elseif (isset($this->_b[$name])) { // has behavior } else { // behavior property
return true; $this->ensureBehaviors();
} elseif (is_array($this->_b)) { foreach ($this->_b as $behavior) {
foreach ($this->_b as $object) { if ($behavior->canGetProperty($name)) {
if ($object->canGetProperty($name)) { return $behavior->$name !== null;
return $object->$name !== null;
} }
} }
} }
@ -207,16 +210,17 @@ class Component extends Object
{ {
$setter = 'set' . $name; $setter = 'set' . $name;
if (method_exists($this, $setter)) { // write property if (method_exists($this, $setter)) { // write property
return $this->$setter(null); $this->$setter(null);
return;
} elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event } elseif (method_exists($this, $name) && strncasecmp($name, 'on', 2) === 0) { // event
unset($this->_e[strtolower($name)]); unset($this->_e[strtolower($name)]);
return; return;
} elseif (isset($this->_b[$name])) { // behavior } else { // behavior property
return $this->detachBehavior($name); $this->ensureBehaviors();
} elseif (is_array($this->_b)) { // behavior property foreach ($this->_b as $behavior) {
foreach ($this->_b as $object) { if ($behavior->canSetProperty($name)) {
if ($object->canSetProperty($name)) { $behavior->$name = null;
return $object->$name = null; return;
} }
} }
} }
@ -247,14 +251,42 @@ class Component extends Object
} }
} }
if ($this->_b !== null) { $this->ensureBehaviors();
foreach ($this->_b as $object) { foreach ($this->_b as $object) {
if (method_exists($object, $name)) { if (method_exists($object, $name)) {
return call_user_func_array(array($object, $name), $params); return call_user_func_array(array($object, $name), $params);
} }
} }
throw new Exception('Calling unknown method: ' . get_class($this) . "::$name()");
} }
throw new Exception('Unknown method: ' . get_class($this) . "::$name()");
/**
* Returns a list of behaviors that this component should behave as.
*
* Child classes may override this method to specify the behaviors they want to behave as.
*
* The return value of this method should be an array of behavior objects or configurations
* indexed by behavior names. A behavior configuration can be either a string specifying
* the behavior class or an array of the following structure:
*
* ~~~
* 'behaviorName' => array(
* 'class' => 'BehaviorClass',
* 'property1' => 'value1',
* 'property2' => 'value2',
* )
* ~~~
*
* Note that a behavior class must extend from [[Behavior]].
*
* Behaviors declared in this method will be attached to the component on demand.
*
* @return array the behavior configurations.
*/
public function behaviors()
{
return array();
} }
/** /**
@ -270,12 +302,13 @@ class Component extends Object
} }
/** /**
* Returns a value indicating whether there is any handler attached to the event. * Returns a value indicating whether there is any handler attached to the named event.
* @param string $name the event name * @param string $name the event name
* @return boolean whether there is any handler attached to the event. * @return boolean whether there is any handler attached to the event.
*/ */
public function hasEventHandlers($name) public function hasEventHandlers($name)
{ {
$this->ensureBehaviors();
$name = strtolower($name); $name = strtolower($name);
return isset($this->_e[$name]) && $this->_e[$name]->getCount(); return isset($this->_e[$name]) && $this->_e[$name]->getCount();
} }
@ -365,6 +398,7 @@ class Component extends Object
*/ */
public function raiseEvent($name, $event) public function raiseEvent($name, $event)
{ {
$this->ensureBehaviors();
$name = strtolower($name); $name = strtolower($name);
if ($event instanceof Event) { if ($event instanceof Event) {
$event->name = $name; $event->name = $name;
@ -406,6 +440,7 @@ class Component extends Object
*/ */
public function asa($behavior) public function asa($behavior)
{ {
$this->ensureBehaviors();
return isset($this->_b[$behavior]) ? $this->_b[$behavior] : null; return isset($this->_b[$behavior]) ? $this->_b[$behavior] : null;
} }
@ -415,11 +450,11 @@ class Component extends Object
* configuration. After that, the behavior object will be attached to * configuration. After that, the behavior object will be attached to
* this component by calling the [[Behavior::attach]] method. * this component by calling the [[Behavior::attach]] method.
* @param string $name the behavior's name. It should uniquely identify this behavior. * @param string $name the behavior's name. It should uniquely identify this behavior.
* @param mixed $behavior the behavior configuration. This can be one of the following: * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
* *
* - a [[Behavior]] object * - a [[Behavior]] object
* - a string specifying the behavior class * - a string specifying the behavior class
* - an object configuration array that will be passed to [[\Yii::createObject]] to create the behavior object. * - an object configuration array that will be passed to [[\Yii::createObject()]] to create the behavior object.
* *
* @return Behavior the behavior object * @return Behavior the behavior object
* @see detachBehavior * @see detachBehavior
@ -457,10 +492,12 @@ class Component extends Object
public function detachBehavior($name) public function detachBehavior($name)
{ {
if (isset($this->_b[$name])) { if (isset($this->_b[$name])) {
$this->_b[$name]->detach($this);
$behavior = $this->_b[$name]; $behavior = $this->_b[$name];
unset($this->_b[$name]); unset($this->_b[$name]);
$behavior->detach($this);
return $behavior; return $behavior;
} else {
return null;
} }
} }
@ -470,10 +507,24 @@ class Component extends Object
public function detachBehaviors() public function detachBehaviors()
{ {
if ($this->_b !== null) { if ($this->_b !== null) {
foreach ($this->_b as $name => $behavior) { $behaviors = $this->_b;
$this->_b = null;
foreach ($behaviors as $name => $behavior) {
$this->detachBehavior($name); $this->detachBehavior($name);
} }
$this->_b = null; }
}
/**
* Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
*/
public function ensureBehaviors()
{
if ($this->_b === null) {
$this->_b = array();
foreach ($this->behaviors() as $name => $behavior) {
$this->attachBehavior($name, $behavior);
}
} }
} }
} }

128
framework/base/Model.php

@ -9,6 +9,8 @@
namespace yii\base; namespace yii\base;
use yii\util\Text;
/** /**
* Model is the base class for data models. * Model is the base class for data models.
* *
@ -22,7 +24,6 @@ namespace yii\base;
* *
* Model also provides a set of events for further customization: * Model also provides a set of events for further customization:
* *
* - [[onAfterInit]]: an event raised at the end of [[init()]]
* - [[onBeforeValidate]]: an event raised at the beginning of [[validate()]] * - [[onBeforeValidate]]: an event raised at the beginning of [[validate()]]
* - [[onAfterValidate]]: an event raised at the end of [[validate()]] * - [[onAfterValidate]]: an event raised at the end of [[validate()]]
* *
@ -32,7 +33,7 @@ namespace yii\base;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Model extends Component implements Initable, \IteratorAggregate, \ArrayAccess class Model extends Component implements \IteratorAggregate, \ArrayAccess
{ {
private static $_attributes = array(); // class name => array of attribute names private static $_attributes = array(); // class name => array of attribute names
private $_errors; // attribute name => array of errors private $_errors; // attribute name => array of errors
@ -49,50 +50,9 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
} }
/** /**
* Initializes this model.
*
* This method is required by the [[Initable]] interface. It is invoked by [[\Yii::createObject]]
* after it creates the new model instance and initializes the model properties.
*
* The default implementation calls [[behaviors]] and registers any available behaviors.
* You may override this method with additional initialization logic (e.g. establish DB connection).
* Make sure you call the parent implementation.
*/
public function init()
{
$this->attachBehaviors($this->behaviors());
$this->afterInit();
}
/**
* Returns a list of behaviors that this model should behave as.
* The return value should be an array of behavior configurations indexed by
* behavior names. Each behavior configuration can be either a string specifying
* the behavior class or an array of the following structure:
*
* ~~~
* 'behaviorName' => array(
* 'class' => 'BehaviorClass',
* 'property1' => 'value1',
* 'property2' => 'value2',
* )
* ~~~
*
* Note that a behavior class must extend from [[Behavior]]. Behaviors declared
* in this method will be attached to the model when [[init]] is invoked.
*
* @return array the behavior configurations.
* @see init
*/
public function behaviors()
{
return array();
}
/**
* Returns the list of attribute names. * Returns the list of attribute names.
* By default, this method returns all public non-static properties of the class. * By default, this method returns all public non-static properties of the class.
* You may override this method to change the default. * You may override this method to change the default behavior.
* @return array list of attribute names. * @return array list of attribute names.
*/ */
public function attributeNames() public function attributeNames()
@ -116,7 +76,7 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
/** /**
* Returns the validation rules for attributes. * Returns the validation rules for attributes.
* *
* Validation rules are used by [[validate]] to check if attribute values are valid. * Validation rules are used by [[validate()]] to check if attribute values are valid.
* Child classes may override this method to declare different validation rules. * Child classes may override this method to declare different validation rules.
* *
* Each rule is an array with the following structure: * Each rule is an array with the following structure:
@ -140,26 +100,31 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* - additional name-value pairs can be specified to initialize the corresponding validator properties. * - additional name-value pairs can be specified to initialize the corresponding validator properties.
* Please refer to individual validator class API for possible properties. * Please refer to individual validator class API for possible properties.
* *
* A validator can be either a model class method or an object. * A validator can be either an object of a class extending [[\yii\validators\Validator]],
* If the former, the method must have the following signature: * or a model class method (called *inline validator*) that has the following signature:
* *
* ~~~ * ~~~
* // $params refers to validation parameters given in the rule * // $params refers to validation parameters given in the rule
* function validatorName($attribute, $params) * function validatorName($attribute, $params)
* ~~~ * ~~~
* *
* If the latter, the object must be extending from [[\yii\validators\Validator]]. * Yii also provides a set of [[\yii\validators\Validator::builtInValidators|built-in validators]].
* Yii provides a set of [[\yii\validators\Validator::builtInValidators|built-in validators]]. * They each has an alias name which can be used when specifying a validation rule.
* They each have an alias name which can be used when specifying a validation rule.
* *
* The following are some examples: * Below are some examples:
* *
* ~~~ * ~~~
* array( * array(
* // built-in "required" validator
* array('username', 'required'), * array('username', 'required'),
* // built-in "length" validator customized with "min" and "max" properties
* array('username', 'length', 'min'=>3, 'max'=>12), * array('username', 'length', 'min'=>3, 'max'=>12),
* // built-in "compare" validator that is used in "register" scenario only
* array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'), * array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
* // an inline validator defined via the "authenticate()" method in the model class
* array('password', 'authenticate', 'on'=>'login'), * array('password', 'authenticate', 'on'=>'login'),
* // a validator of class "CaptchaValidator"
* array('captcha', 'CaptchaValidator'),
* ); * );
* ~~~ * ~~~
* *
@ -180,7 +145,7 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* `firstName`, we can declare a label `First Name` which is more user-friendly and can * `firstName`, we can declare a label `First Name` which is more user-friendly and can
* be displayed to end users. * be displayed to end users.
* *
* By default an attribute label is generated using [[generateAttributeLabel]]. * By default an attribute label is generated using [[generateAttributeLabel()]].
* This method allows you to explicitly specify attribute labels. * This method allows you to explicitly specify attribute labels.
* *
* Note, in order to inherit labels defined in the parent class, a child class needs to * Note, in order to inherit labels defined in the parent class, a child class needs to
@ -197,21 +162,21 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
/** /**
* Performs the data validation. * Performs the data validation.
* *
* This method executes the validation rules as declared in [[rules]]. * This method executes the validation rules as declared in [[rules()]].
* Only the rules applicable to the current [[scenario]] will be executed. * Only the rules applicable to the current [[scenario]] will be executed.
* A rule is considered applicable to a scenario if its `on` option is not set * A rule is considered applicable to a scenario if its `on` option is not set
* or contains the scenario. * or contains the scenario.
* *
* This method will call [[beforeValidate]] and [[afterValidate]] before and * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
* after actual validation, respectively. If [[beforeValidate]] returns false, * after actual validation, respectively. If [[beforeValidate()]] returns false,
* the validation and [[afterValidate]] will be cancelled. * the validation and [[afterValidate()]] will be cancelled.
* *
* Errors found during the validation can be retrieved via [[getErrors]]. * Errors found during the validation can be retrieved via [[getErrors()]].
* *
* @param array $attributes list of attributes that should be validated. * @param array $attributes list of attributes that should be validated.
* If this parameter is empty, it means any attribute listed in the applicable * If this parameter is empty, it means any attribute listed in the applicable
* validation rules should be validated. * validation rules should be validated.
* @param boolean $clearErrors whether to call [[clearErrors]] before performing validation * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
* @return boolean whether the validation is successful without any error. * @return boolean whether the validation is successful without any error.
* @see beforeValidate * @see beforeValidate
* @see afterValidate * @see afterValidate
@ -232,19 +197,6 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
} }
/** /**
* This method is invoked at the end of [[init()]].
* The default implementation raises the [[onAfterInit]] event.
* You may override this method to do postprocessing after model creation.
* Make sure you call the parent implementation so that the event is raised properly.
*/
public function afterInit()
{
if ($this->hasEventHandlers('onAfterInit')) {
$this->onAfterInit(new Event($this));
}
}
/**
* This method is invoked before validation starts. * This method is invoked before validation starts.
* The default implementation raises the [[onBeforeValidate]] event. * The default implementation raises the [[onBeforeValidate]] event.
* You may override this method to do preliminary checks before validation. * You may override this method to do preliminary checks before validation.
@ -276,24 +228,6 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
} }
/** /**
* This event is raised by [[init]] when initializing the model.
* @param Event $event the event parameter
*/
public function onInit($event)
{
$this->raiseEvent(__FUNCTION__, $event);
}
/**
* This event is raised at the end of [[init()]].
* @param Event $event the event parameter
*/
public function onAfterInit($event)
{
$this->raiseEvent(__FUNCTION__, $event);
}
/**
* This event is raised before the validation is performed. * This event is raised before the validation is performed.
* @param ValidationEvent $event the event parameter * @param ValidationEvent $event the event parameter
*/ */
@ -312,9 +246,9 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
} }
/** /**
* Returns all the validators declared in [[rules]]. * Returns all the validators declared in [[rules()]].
* *
* This method differs from [[getActiveValidators]] in that the latter * This method differs from [[getActiveValidators()]] in that the latter
* only returns the validators applicable to the current [[scenario]]. * only returns the validators applicable to the current [[scenario]].
* *
* Because this method returns a [[Vector]] object, you may * Because this method returns a [[Vector]] object, you may
@ -339,7 +273,7 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
* Returns the validators applicable to the current [[scenario]]. * Returns the validators applicable to the current [[scenario]].
* @param string $attribute the name of the attribute whose applicable validators should be returned. * @param string $attribute the name of the attribute whose applicable validators should be returned.
* If this is null, the validators for ALL attributes in the model will be returned. * If this is null, the validators for ALL attributes in the model will be returned.
* @return array the validators applicable to the current [[scenario]]. * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
*/ */
public function getActiveValidators($attribute = null) public function getActiveValidators($attribute = null)
{ {
@ -356,8 +290,8 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
} }
/** /**
* Creates validator objects based on the validation rules specified in [[rules]]. * Creates validator objects based on the validation rules specified in [[rules()]].
* Unlike [[getValidators]], calling this method each time, it will return a new list of validators. * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
* @return Vector validators * @return Vector validators
*/ */
public function createValidators() public function createValidators()
@ -399,7 +333,7 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
*/ */
public function isAttributeSafe($attribute) public function isAttributeSafe($attribute)
{ {
$validators = $this->getActiveValidators(); $validators = $this->getActiveValidators($attribute);
foreach ($validators as $validator) { foreach ($validators as $validator) {
if (!$validator->safe) { if (!$validator->safe) {
return false; return false;
@ -523,13 +457,13 @@ class Model extends Component implements Initable, \IteratorAggregate, \ArrayAcc
*/ */
public function generateAttributeLabel($name) public function generateAttributeLabel($name)
{ {
return ucwords(trim(strtolower(str_replace(array('-', '_', '.'), ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name))))); return Text::name2words($name, true);
} }
/** /**
* Returns attribute values. * Returns attribute values.
* @param array $names list of attributes whose value needs to be returned. * @param array $names list of attributes whose value needs to be returned.
* Defaults to null, meaning all attributes listed in [[attributeNames]] will be returned. * Defaults to null, meaning all attributes listed in [[attributeNames()]] will be returned.
* If it is an array, only the attributes in the array will be returned. * If it is an array, only the attributes in the array will be returned.
* @return array attribute values (name=>value). * @return array attribute values (name=>value).
*/ */

76
framework/db/ar/ActiveMetaData.php

@ -0,0 +1,76 @@
<?php
namespace yii\db\ar;
use yii\db\Exception;
/**
* ActiveMetaData represents the meta-data for an Active Record class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveMetaData
{
/**
* @var TableSchema the table schema information
*/
public $table;
/**
* @var array list of relations
*/
public $relations = array();
/**
* Constructor.
* @param string $modelClass the model class name
*/
public function __construct($modelClass)
{
$tableName = $modelClass::tableName();
$table = $modelClass::getDbConnection()->getDriver()->getTableSchema($tableName);
if ($table === null) {
throw new Exception("Unable to find table '$tableName' for ActiveRecord class '$modelClass'.");
}
if ($table->primaryKey === null) {
$primaryKey = $modelClass::primaryKey();
if ($primaryKey !== null) {
$table->fixPrimaryKey($primaryKey);
} else {
throw new Exception("The table '$tableName' for ActiveRecord class '$modelClass' does not have a primary key.");
}
}
$this->table = $table;
foreach ($modelClass::relations() as $name => $config) {
$this->addRelation($name, $config);
}
}
/**
* Adds a relation.
*
* $config is an array with three elements:
* relation type, the related active record class and the foreign key.
*
* @throws Exception
* @param string $name $name Name of the relation.
* @param array $config $config Relation parameters.
* @return void
*/
public function addRelation($name, $config)
{
if (preg_match('/^(\w+)\s*:\s*\\\\?([\w\\\\]+)(\[\])?$/', $name, $matches)) {
if (is_string($config)) {
$config = array('on' => $config);
}
$relation = ActiveRelation::newInstance($config);
$relation->name = $matches[1];
$relation->modelClass = '\\' . $matches[2];
$relation->hasMany = isset($matches[3]);
$this->relations[$relation->name] = $relation;
} else {
throw new Exception("Relation name in bad format: $name");
}
}
}

233
framework/db/ar/ActiveQuery.php

@ -0,0 +1,233 @@
<?php
/**
* ActiveQuery class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\ar;
/**
* ActiveFinder.php is ...
* todo: add SQL monitor
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveQuery extends \yii\db\dao\BaseQuery implements \IteratorAggregate, \ArrayAccess, \Countable
{
public $modelClass;
public $with;
public $alias;
public $index;
private $_count;
private $_sql;
private $_countSql;
private $_asArray;
private $_records;
public function all()
{
return $this->performQuery();
}
public function one()
{
$this->limit = 1;
$records = $this->performQuery();
if (isset($records[0])) {
$this->_count = 1;
return $records[0];
} else {
$this->_count = 0;
return null;
}
}
public function asArray($value = true)
{
$this->_asArray = $value;
}
protected function performQuery()
{
$class = $this->modelClass;
$db = $class::getDbConnection();
$this->_sql = $this->getSql($db);
$command = $db->createCommand($this->_sql);
$command->bindValues($this->params);
$rows = $command->queryAll();
if ($this->_asArray) {
$records = $rows;
} else {
$records = array();
foreach ($rows as $row) {
$records[] = $class::populateRecord($row);
}
}
$this->_count = count($records);
return $records;
}
public function with()
{
}
//
// public function getSql($connection = null)
// {
//
// }
public function setSql($value)
{
$this->_sql = $value;
}
public function getCountSql()
{
}
public function getOneSql()
{
}
/**
* Returns the number of items in the vector.
* @return integer the number of items in the vector
*/
public function getCount()
{
if ($this->_count !== null) {
return $this->_count;
} else {
return $this->_count = $this->performCountQuery();
}
}
protected function performCountQuery()
{
$select = $this->select;
$this->select = 'COUNT(*)';
$class = $this->modelClass;
$command = $this->createCommand($class::getDbConnection());
$this->_countSql = $command->getSql();
$count = $command->queryScalar();
$this->select = $select;
return $count;
}
/**
* Sets the parameters about query caching.
* This is a shortcut method to {@link CDbConnection::cache()}.
* It changes the query caching parameter of the {@link dbConnection} instance.
* @param integer $duration the number of seconds that query results may remain valid in cache.
* If this is 0, the caching will be disabled.
* @param CCacheDependency $dependency the dependency that will be used when saving the query results into cache.
* @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
* meaning that the next SQL query will be cached.
* @return ActiveRecord the active record instance itself.
*/
public function cache($duration, $dependency = null, $queryCount = 1)
{
$this->connection->cache($duration, $dependency, $queryCount);
return $this;
}
/**
* Returns an iterator for traversing the items in the vector.
* This method is required by the SPL interface `IteratorAggregate`.
* It will be implicitly called when you use `foreach` to traverse the vector.
* @return Iterator an iterator for traversing the items in the vector.
*/
public function getIterator()
{
$records = $this->performQuery();
return new \yii\base\VectorIterator($records);
}
/**
* Returns the number of items in the vector.
* This method is required by the SPL `Countable` interface.
* It will be implicitly called when you use `count($vector)`.
* @return integer number of items in the vector.
*/
public function count()
{
return $this->getCount();
}
/**
* Returns a value indicating whether there is an item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `isset($vector[$offset])`.
* @param integer $offset the offset to be checked
* @return boolean whether there is an item at the specified offset.
*/
public function offsetExists($offset)
{
if ($this->_records === null) {
$this->_records = $this->performQuery();
}
return isset($this->_records[$offset]);
}
/**
* Returns the item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `$value = $vector[$offset];`.
* This is equivalent to [[itemAt]].
* @param integer $offset the offset to retrieve item.
* @return mixed the item at the offset
* @throws Exception if the offset is out of range
*/
public function offsetGet($offset)
{
if ($this->_records === null) {
$this->_records = $this->performQuery();
}
return isset($this->_records[$offset]) ? $this->_records[$offset] : null;
}
/**
* Sets the item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `$vector[$offset] = $item;`.
* If the offset is null or equal to the number of the existing items,
* the new item will be appended to the vector.
* Otherwise, the existing item at the offset will be replaced with the new item.
* @param integer $offset the offset to set item
* @param mixed $item the item value
* @throws Exception if the offset is out of range, or the vector is read only.
*/
public function offsetSet($offset, $item)
{
if ($this->_records === null) {
$this->_records = $this->performQuery();
}
$this->_records[$offset] = $item;
}
/**
* Unsets the item at the specified offset.
* This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `unset($vector[$offset])`.
* This is equivalent to [[removeAt]].
* @param integer $offset the offset to unset item
* @throws Exception if the offset is out of range, or the vector is read only.
*/
public function offsetUnset($offset)
{
if ($this->_records === null) {
$this->_records = $this->performQuery();
}
unset($this->_records[$offset]);
}
}

500
framework/db/ar/ActiveRecord.php

@ -51,26 +51,40 @@ abstract class ActiveRecord extends \yii\base\Model
} }
} }
public static function find($query = null) /**
{ * @static
$finder = static::createFinder(); * @param string|array|ActiveQuery $q
if ($query instanceof Query) { * @return ActiveQuery
$finder->query = $query; * @throws \yii\db\Exception
} elseif ($query !== null) { */
// todo: findByPk public static function find($q = null)
{
$query = $q instanceof ActiveQuery? $q : static::createQuery();
$query->modelClass = '\\' . get_called_class();
$query->from = static::tableName();
if (is_array($q)) {
$query->where($q);
} elseif ($q !== null && $query !== $q) {
$primaryKey = static::getMetaData()->table->primaryKey;
if (is_string($primaryKey)) {
$query->where(array($primaryKey => $q));
} else {
throw new Exception("Multiple column values are required to find by composite primary keys.");
}
} }
return $finder; return $query;
} }
public static function findBySql($sql, $params = array()) public static function findBySql($sql, $params = array())
{ {
$finder = static::createFinder(); $query = static::createQuery();
if (!is_array($params)) { if (!is_array($params)) {
$params = func_get_args(); $params = func_get_args();
array_shift($params); array_shift($params);
} }
$finder->setSql($sql); $query->setSql($sql);
return $finder->params($params); $query->modelClass = '\\' . get_called_class();
return $query->params($params);
} }
public static function exists($condition, $params) public static function exists($condition, $params)
@ -93,9 +107,9 @@ abstract class ActiveRecord extends \yii\base\Model
} }
public static function createFinder() public static function createQuery()
{ {
return new ActiveFinder('\\' . get_called_class()); return new ActiveQuery('\\' . get_called_class());
} }
/** /**
@ -149,6 +163,26 @@ abstract class ActiveRecord extends \yii\base\Model
} }
/** /**
* Declares the relations for this ActiveRecord class.
*
* Child classes may want to override this method to specify their relations.
*
* The following shows how to declare relations for a Programmer AR class:
*
* ~~~
* return array(
* 'manager:Manager' => '?.manager_id = manager.id',
* 'assignments:Assignment[]' => array(
* 'on' => '?.id = assignments.owner_id AND assignments.status=1',
* 'orderBy' => 'assignments.create_time DESC',
* ),
* 'projects:Project[]' => array(
* 'via' => 'assignments',
* 'on' => 'projects.id = assignments.project_id',
* ),
* );
* ~~~
*
* This method should be overridden to declare related objects. * This method should be overridden to declare related objects.
* *
* There are four types of relations that may exist between two active record objects: * There are four types of relations that may exist between two active record objects:
@ -1194,443 +1228,3 @@ abstract class ActiveRecord extends \yii\base\Model
return $this->__isset($offset); return $this->__isset($offset);
} }
} }
/**
* CBaseActiveRelation is the base class for all active relations.
* @author Qiang Xue <qiang.xue@gmail.com>
*/
class CBaseActiveRelation extends \yii\base\Component
{
/**
* @var string name of the related object
*/
public $name;
/**
* @var string name of the related active record class
*/
public $className;
/**
* @var string the foreign key in this relation
*/
public $foreignKey;
/**
* @var mixed list of column names (an array, or a string of names separated by commas) to be selected.
* Do not quote or prefix the column names unless they are used in an expression.
* In that case, you should prefix the column names with 'relationName.'.
*/
public $select = '*';
/**
* @var string WHERE clause. For {@link CActiveRelation} descendant classes, column names
* referenced in the condition should be disambiguated with prefix 'relationName.'.
*/
public $condition = '';
/**
* @var array the parameters that are to be bound to the condition.
* The keys are parameter placeholder names, and the values are parameter values.
*/
public $params = array();
/**
* @var string GROUP BY clause. For {@link CActiveRelation} descendant classes, column names
* referenced in this property should be disambiguated with prefix 'relationName.'.
*/
public $group = '';
/**
* @var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
* For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
*/
public $join = '';
/**
* @var string HAVING clause. For {@link CActiveRelation} descendant classes, column names
* referenced in this property should be disambiguated with prefix 'relationName.'.
*/
public $having = '';
/**
* @var string ORDER BY clause. For {@link CActiveRelation} descendant classes, column names
* referenced in this property should be disambiguated with prefix 'relationName.'.
*/
public $order = '';
/**
* Constructor.
* @param string $name name of the relation
* @param string $className name of the related active record class
* @param string $foreignKey foreign key for this relation
* @param array $options additional options (name=>value). The keys must be the property names of this class.
*/
public function __construct($name, $className, $foreignKey, $options = array())
{
$this->name = $name;
$this->className = $className;
$this->foreignKey = $foreignKey;
foreach ($options as $name => $value)
{
$this->$name = $value;
}
}
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria) {
$criteria = $criteria->toArray();
}
if (isset($criteria['select']) && $this->select !== $criteria['select']) {
if ($this->select === '*') {
$this->select = $criteria['select'];
}
elseif ($criteria['select'] !== '*')
{
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($criteria['select']) ? preg_split('/\s*,\s*/', trim($criteria['select']), -1, PREG_SPLIT_NO_EMPTY) : $criteria['select'];
$this->select = array_merge($select1, array_diff($select2, $select1));
}
}
if (isset($criteria['condition']) && $this->condition !== $criteria['condition']) {
if ($this->condition === '') {
$this->condition = $criteria['condition'];
}
elseif ($criteria['condition'] !== '')
{
$this->condition = "( {$this->condition}) AND ( {$criteria['condition']})";
}
}
if (isset($criteria['params']) && $this->params !== $criteria['params']) {
$this->params = array_merge($this->params, $criteria['params']);
}
if (isset($criteria['order']) && $this->order !== $criteria['order']) {
if ($this->order === '') {
$this->order = $criteria['order'];
}
elseif ($criteria['order'] !== '')
{
$this->order = $criteria['order'] . ', ' . $this->order;
}
}
if (isset($criteria['group']) && $this->group !== $criteria['group']) {
if ($this->group === '') {
$this->group = $criteria['group'];
}
elseif ($criteria['group'] !== '')
{
$this->group .= ', ' . $criteria['group'];
}
}
if (isset($criteria['join']) && $this->join !== $criteria['join']) {
if ($this->join === '') {
$this->join = $criteria['join'];
}
elseif ($criteria['join'] !== '')
{
$this->join .= ' ' . $criteria['join'];
}
}
if (isset($criteria['having']) && $this->having !== $criteria['having']) {
if ($this->having === '') {
$this->having = $criteria['having'];
}
elseif ($criteria['having'] !== '')
{
$this->having = "( {$this->having}) AND ( {$criteria['having']})";
}
}
}
}
/**
* CStatRelation represents a statistical relational query.
* @author Qiang Xue <qiang.xue@gmail.com>
*/
class CStatRelation extends CBaseActiveRelation
{
/**
* @var string the statistical expression. Defaults to 'COUNT(*)', meaning
* the count of child objects.
*/
public $select = 'COUNT(*)';
/**
* @var mixed the default value to be assigned to those records that do not
* receive a statistical query result. Defaults to 0.
*/
public $defaultValue = 0;
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria) {
$criteria = $criteria->toArray();
}
parent::mergeWith($criteria, $fromScope);
if (isset($criteria['defaultValue'])) {
$this->defaultValue = $criteria['defaultValue'];
}
}
}
/**
* CActiveRelation is the base class for representing active relations that bring back related objects.
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CActiveRelation extends CBaseActiveRelation
{
/**
* @var string join type. Defaults to 'LEFT OUTER JOIN'.
*/
public $joinType = 'LEFT OUTER JOIN';
/**
* @var string ON clause. The condition specified here will be appended to the joining condition using AND operator.
*/
public $on = '';
/**
* @var string the alias for the table that this relation refers to. Defaults to null, meaning
* the alias will be the same as the relation name.
*/
public $alias;
/**
* @var string|array specifies which related objects should be eagerly loaded when this related object is lazily loaded.
* For more details about this property, see {@link ActiveRecord::with()}.
*/
public $with = array();
/**
* @var boolean whether this table should be joined with the primary table.
* When setting this property to be false, the table associated with this relation will
* appear in a separate JOIN statement.
* If this property is set true, then the corresponding table will ALWAYS be joined together
* with the primary table, no matter the primary table is limited or not.
* If this property is not set, the corresponding table will be joined with the primary table
* only when the primary table is not limited.
*/
public $together;
/**
* @var mixed scopes to apply
* Can be set to the one of the following:
* <ul>
* <li>Single scope: 'scopes'=>'scopeName'.</li>
* <li>Multiple scopes: 'scopes'=>array('scopeName1','scopeName2').</li>
* </ul>
*/
public $scopes;
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria) {
$criteria = $criteria->toArray();
}
if ($fromScope) {
if (isset($criteria['condition']) && $this->on !== $criteria['condition']) {
if ($this->on === '') {
$this->on = $criteria['condition'];
}
elseif ($criteria['condition'] !== '')
{
$this->on = "( {$this->on}) AND ( {$criteria['condition']})";
}
}
unset($criteria['condition']);
}
parent::mergeWith($criteria);
if (isset($criteria['joinType'])) {
$this->joinType = $criteria['joinType'];
}
if (isset($criteria['on']) && $this->on !== $criteria['on']) {
if ($this->on === '') {
$this->on = $criteria['on'];
}
elseif ($criteria['on'] !== '')
{
$this->on = "( {$this->on}) AND ( {$criteria['on']})";
}
}
if (isset($criteria['with'])) {
$this->with = $criteria['with'];
}
if (isset($criteria['alias'])) {
$this->alias = $criteria['alias'];
}
if (isset($criteria['together'])) {
$this->together = $criteria['together'];
}
}
}
/**
* CBelongsToRelation represents the parameters specifying a BELONGS_TO relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CBelongsToRelation extends CActiveRelation
{
}
/**
* CHasOneRelation represents the parameters specifying a HAS_ONE relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CHasOneRelation extends CActiveRelation
{
/**
* @var string the name of the relation that should be used as the bridge to this relation.
* Defaults to null, meaning don't use any bridge.
*/
public $through;
}
/**
* CHasManyRelation represents the parameters specifying a HAS_MANY relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CHasManyRelation extends CActiveRelation
{
/**
* @var integer limit of the rows to be selected. It is effective only for lazy loading this related object. Defaults to -1, meaning no limit.
*/
public $limit = -1;
/**
* @var integer offset of the rows to be selected. It is effective only for lazy loading this related object. Defaults to -1, meaning no offset.
*/
public $offset = -1;
/**
* @var string the name of the column that should be used as the key for storing related objects.
* Defaults to null, meaning using zero-based integer IDs.
*/
public $index;
/**
* @var string the name of the relation that should be used as the bridge to this relation.
* Defaults to null, meaning don't use any bridge.
*/
public $through;
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria) {
$criteria = $criteria->toArray();
}
parent::mergeWith($criteria, $fromScope);
if (isset($criteria['limit']) && $criteria['limit'] > 0) {
$this->limit = $criteria['limit'];
}
if (isset($criteria['offset']) && $criteria['offset'] >= 0) {
$this->offset = $criteria['offset'];
}
if (isset($criteria['index'])) {
$this->index = $criteria['index'];
}
}
}
/**
* CManyManyRelation represents the parameters specifying a MANY_MANY relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CManyManyRelation extends CHasManyRelation
{
}
/**
* ActiveMetaData represents the meta-data for an Active Record class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveMetaData
{
/**
* @var TableSchema the table schema information
*/
public $table;
/**
* @var array list of relations
*/
public $relations = array();
/**
* Constructor.
* @param string $modelClass the model class name
*/
public function __construct($modelClass)
{
$tableName = $modelClass::tableName();
$table = $modelClass::getDbConnection()->getDriver()->getTableSchema($tableName);
if ($table === null) {
throw new Exception("Unable to find table '$tableName' for ActiveRecord class '$modelClass'.");
}
if ($table->primaryKey === null) {
$primaryKey = $modelClass::primaryKey();
if ($primaryKey !== null) {
$table->fixPrimaryKey($primaryKey);
} else {
throw new Exception("The table '$tableName' for ActiveRecord class '$modelClass' does not have a primary key.");
}
}
$this->table = $table;
foreach ($modelClass::relations() as $name => $config) {
$this->addRelation($name, $config);
}
}
/**
* Adds a relation.
*
* $config is an array with three elements:
* relation type, the related active record class and the foreign key.
*
* @throws Exception
* @param string $name $name Name of the relation.
* @param array $config $config Relation parameters.
* @return void
*/
public function addRelation($name, $config)
{
// relation class, AR class, FK
if (isset($config[0], $config[1], $config[2])) {
$this->relations[$name] = new $config[0]($name, $config[1], $config[2], array_slice($config, 3));
} else {
throw new Exception(Yii::t('yii', 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.', array('{class}' => get_class($this->_model), '{relation}' => $name)));
}
}
}

43
framework/db/ar/ActiveRelation.php

@ -0,0 +1,43 @@
<?php
namespace yii\db\ar;
class ActiveRelation extends \yii\db\dao\BaseQuery
{
public $name;
public $modelClass;
public $hasMany;
public $joinType;
public $alias;
public $on;
public $via;
public $index;
public $with;
public $scopes;
public function mergeWith($relation)
{
parent::mergeWith($relation);
if ($relation->joinType !== null) {
$this->joinType = $relation->joinType;
}
if ($relation->alias !== null) {
$this->alias = $relation->alias;
}
if ($relation->on !== null) {
if (!empty($this->on)) {
$this->on = "({$this->on}) AND ({$relation->on})";
} else {
$this->on = $relation->on;
}
}
if ($relation->via !== null) {
$this->via = $relation->via;
}
if ($relation->index !== null) {
$this->index = $relation->index;
}
// todo: with, scopes
}
}

715
framework/db/dao/BaseQuery.php

@ -0,0 +1,715 @@
<?php
/**
* BaseQuery class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\dao;
/**
* Query represents a SQL statement in a way that is independent of DBMS.
*
* Query not only can represent a SELECT statement, it can also represent INSERT, UPDATE, DELETE,
* and other commonly used DDL statements, such as CREATE TABLE, CREATE INDEX, etc.
*
* Query provides a set of methods to facilitate the specification of different clauses.
* These methods can be chained together. For example,
*
* ~~~
* $query = new Query;
* $query->select('id, name')
* ->from('tbl_user')
* ->limit(10);
* // get the actual SQL statement
* echo $query->getSql();
* // or execute the query
* $users = $query->createCommand()->queryAll();
* ~~~
*
* By calling [[getSql()]], we can obtain the actual SQL statement from a Query object.
* And by calling [[createCommand()]], we can get a [[Command]] instance which can be further
* used to perform/execute the DB query against a database.
*
* @property string $sql the SQL statement represented by this query object.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class BaseQuery extends \yii\base\Object
{
/**
* @var string|array the columns being selected. This refers to the SELECT clause in a SQL
* statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`).
* If not set, if means all columns.
* @see select()
*/
public $select;
/**
* @var string additional option that should be appended to the 'SELECT' keyword. For example,
* in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
*/
public $selectOption;
/**
* @var boolean whether to select distinct rows of data only. If this is set true,
* the SELECT clause would be changed to SELECT DISTINCT.
*/
public $distinct;
/**
* @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement.
* It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`).
* @see from()
*/
public $from;
/**
* @var string|array query condition. This refers to the WHERE clause in a SQL statement.
* For example, `age > 31 AND team = 1`.
* @see where()
*/
public $where;
/**
* @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
*/
public $limit;
/**
* @var integer zero-based offset from where the records are to be returned. If not set or
* less than 0, it means starting from the beginning.
*/
public $offset;
/**
* @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 $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 $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.
* `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`).
* @see join()
*/
public $join;
/**
* @var string|array the condition to be applied in the GROUP BY clause.
* It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
*/
public $having;
/**
* @var array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
*/
public $params;
/**
* @var string|array the UNION clause(s) in a SQL statement. This can be either a string
* representing a single UNION clause or an array representing multiple UNION clauses.
* Each union clause can be a string or a `Query` object which refers to the SQL statement.
*/
public $union;
/**
* Sets the SELECT part of the query.
* @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @param boolean $distinct whether to use 'SELECT DISTINCT'.
* @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
* in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
* @return Query the query object itself
*/
public function select($columns = '*', $distinct = false, $option = '')
{
$this->select = $columns;
$this->distinct = $distinct;
$this->selectOption = $option;
return $this;
}
/**
* Sets the FROM part of the query.
* @param mixed $tables the table(s) to be selected from. This can be either a string (e.g. 'tbl_user')
* or an array (e.g. array('tbl_user', 'tbl_profile')) specifying one or several table names.
* Table names can contain schema prefixes (e.g. 'public.tbl_user') and/or table aliases (e.g. 'tbl_user u').
* The method will automatically quote the table names unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return Query the query object itself
*/
public function from($tables)
{
$this->from = $tables;
return $this;
}
/**
* Sets the WHERE part of the query.
*
* The method requires a $condition parameter, and optionally a $params parameter
* specifying the values to be bound to the query.
*
* The $condition parameter should be either a string (e.g. 'id=1') or an array.
* If the latter, it must be in one of the following two formats:
*
* - hash format: `array('column1' => value1, 'column2' => value2, ...)`
* - operator format: `array(operator, operand1, operand2, ...)`
*
* A condition in hash format represents the following SQL expression in general:
* `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
* an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
* in the generated expression. Below are some examples:
*
* - `array('type'=>1, 'status'=>2)` generates `(type=1) AND (status=2)`.
* - `array('id'=>array(1,2,3), 'status'=>2)` generates `(id IN (1,2,3)) AND (status=2)`.
* - `array('status'=>null) generates `status IS NULL`.
*
* A condition in operator format generates the SQL expression according to the specified operator, which
* can be one of the followings:
*
* - `and`: the operands should be concatenated together using `AND`. For example,
* `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array,
* it will be converted into a string using the rules described here. For example,
* `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`.
* The method will NOT do any quoting or escaping.
*
* - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
*
* - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
* starting and ending values of the range that the column is in.
* For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`.
*
* - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
* in the generated condition.
*
* - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
* the range of the values that the column or DB expression should be in. For example,
* `array('in', 'id', array(1,2,3))` will generate `id IN (1,2,3)`.
* The method will properly quote the column name and escape values in the range.
*
* - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
*
* - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
* the values that the column or DB expression should be like.
* For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`.
* When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
* using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate
* `name LIKE '%test%' AND name LIKE '%sample%'`.
* The method will properly quote the column name and escape values in the range.
*
* - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* predicates when operand 2 is an array.
*
* - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
* in the generated condition.
*
* - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
* the `NOT LIKE` predicates.
*
* @param string|array $condition the conditions that should be put in the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query.
* For anonymous parameters, they can alternatively be specified as separate parameters to this method.
* For example, `where('type=? AND status=?', 100, 1)`.
* @return Query the query object itself
* @see andWhere()
* @see orWhere()
*/
public function where($condition, $params = array())
{
$this->where = $condition;
if (!is_array($params)) {
$params = func_get_args();
unset($params[0]);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see where()
* @see orWhere()
*/
public function andWhere($condition, $params = array())
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = array('and', $this->where, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
unset($params[0]);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see where()
* @see andWhere()
*/
public function orWhere($condition, $params = array())
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = array('or', $this->where, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
unset($params[0]);
}
$this->addParams($params);
return $this;
}
/**
* Appends an INNER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param string|array $condition the join condition that should appear in the ON part.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
*/
public function join($table, $condition, $params = array())
{
$this->join[] = array('JOIN', $table, $condition);
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
unset($params[0]);
}
return $this->addParams($params);
}
/**
* Appends a LEFT OUTER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param string|array $condition the join condition that should appear in the ON part.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Query the query object itself
*/
public function leftJoin($table, $condition, $params = array())
{
$this->join[] = array('LEFT JOIN', $table, $condition);
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
unset($params[0]);
}
return $this->addParams($params);
}
/**
* Appends a RIGHT OUTER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param string|array $condition the join condition that should appear in the ON part.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Query the query object itself
*/
public function rightJoin($table, $condition, $params = array())
{
$this->join[] = array('RIGHT JOIN', $table, $condition);
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
unset($params[0]);
}
return $this->addParams($params);
}
/**
* Appends a CROSS JOIN part to the query.
* Note that not all DBMS support CROSS JOIN.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return Query the query object itself
*/
public function crossJoin($table)
{
$this->join[] = array('CROSS JOIN', $table);
return $this;
}
/**
* Appends a NATURAL JOIN part to the query.
* Note that not all DBMS support NATURAL JOIN.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return Query the query object itself
*/
public function naturalJoin($table)
{
$this->join[] = array('NATURAL JOIN', $table);
return $this;
}
/**
* Sets the GROUP BY part of the query.
* @param string|array $columns the columns to be grouped by.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see addGroupBy()
*/
public function groupBy($columns)
{
$this->groupBy = $columns;
return $this;
}
/**
* Adds additional group-by columns to the existing ones.
* @param string|array $columns additional columns to be grouped by.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see groupBy()
*/
public function addGroupBy($columns)
{
if (empty($this->groupBy)) {
$this->groupBy = $columns;
} else {
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->groupBy = array_merge($this->groupBy, $columns);
}
return $this;
}
/**
* Sets the HAVING part of the query.
* @param string|array $condition the conditions to be put after HAVING.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see andHaving()
* @see orHaving()
*/
public function having($condition, $params = array())
{
$this->having = $condition;
if (!is_array($params)) {
$params = func_get_args();
unset($params[0]);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional HAVING condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param string|array $condition the new HAVING condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see having()
* @see orHaving()
*/
public function andHaving($condition, $params = array())
{
if ($this->having === null) {
$this->having = $condition;
} else {
$this->having = array('and', $this->having, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
unset($params[0]);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional HAVING condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* @param string|array $condition the new HAVING condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see having()
* @see andHaving()
*/
public function orHaving($condition, $params = array())
{
if ($this->having === null) {
$this->having = $condition;
} else {
$this->having = array('or', $this->having, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
unset($params[0]);
}
$this->addParams($params);
return $this;
}
/**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see addOrderBy()
*/
public function orderBy($columns)
{
$this->orderBy = $columns;
return $this;
}
/**
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see orderBy()
*/
public function addOrderBy($columns)
{
if (empty($this->orderBy)) {
$this->orderBy = $columns;
} else {
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->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit
* @return Query the query object itself
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset
* @return Query the query object itself
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* Appends a SQL statement using UNION operator.
* @param string $sql the SQL statement to be appended using UNION
* @return Query the query object itself
*/
public function union($sql)
{
$this->union[] = $sql;
return $this;
}
/**
* Sets the parameters to be bound to the query.
* @param array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see addParams()
*/
public function params($params)
{
$this->params = $params;
return $this;
}
/**
* Adds additional parameters to be bound to the query.
* @param array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see params()
*/
public function addParams($params)
{
if ($this->params === null) {
$this->params = $params;
} else {
foreach ($params as $name => $value) {
if (is_integer($name)) {
$this->params[] = $value;
} else {
$this->params[$name] = $value;
}
}
}
return $this;
}
/**
* Merges this query with another one.
*
* The merging is done according to the following rules:
*
* - [[select]]: the union of both queries' [[select]] property values.
* - [[selectOption]], [[distinct]], [[limit]], [[offset]]: the new query
* takes precedence over this query.
* - [[where]], [[having]]: the new query's corresponding property value
* will be 'AND' together with the existing one.
* - [[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.
* @param BaseQuery $query the new query to be merged with this query.
* @return BaseQuery the query object itself
*/
public function mergeWith($query)
{
if ($this->select !== $query->select) {
if (empty($this->select)) {
$this->select = $query->select;
} elseif (!empty($query->select)) {
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($query->select) ? preg_split('/\s*,\s*/', trim($query->select), -1, PREG_SPLIT_NO_EMPTY) : $query->select;
$this->select = array_merge($select1, array_diff($select2, $select1));
}
}
if ($query->selectOption !== null) {
$this->selectOption = $query->selectOption;
}
if ($query->distinct !== null) {
$this->distinct = $query->distinct;
}
if ($query->limit !== null) {
$this->limit = $query->limit;
}
if ($query->offset !== null) {
$this->offset = $query->offset;
}
if ($query->where !== null) {
$this->andWhere($query->where);
}
if ($query->having !== null) {
$this->andHaving($query->having);
}
if ($query->params !== null) {
$this->addParams($query->params);
}
if ($query->orderBy !== null) {
$this->addOrderBy($query->orderBy);
}
if ($query->groupBy !== null) {
$this->addGroupBy($query->groupBy);
}
if ($query->join !== null) {
if (empty($this->join)) {
$this->join = $query->join;
} else {
if (!is_array($this->join)) {
$this->join = array($this->join);
}
if (is_array($query->join)) {
$this->join = array_merge($this->join, $query->join);
} else {
$this->join[] = $query->join;
}
}
}
if ($query->union !== null) {
if (empty($this->union)) {
$this->union = $query->union;
} else {
if (!is_array($this->union)) {
$this->union = array($this->union);
}
if (is_array($query->union)) {
$this->union = array_merge($this->union, $query->union);
} else {
$this->union[] = $query->union;
}
}
}
return $this;
}
/**
* Resets the query object to its original state.
* @return Query the query object itself
*/
public function reset()
{
foreach (get_object_vars($this) as $name => $value) {
$this->$name = null;
}
return $this;
}
}

716
framework/db/dao/Query.php

@ -39,80 +39,9 @@ namespace yii\db\dao;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Query extends \yii\base\Object class Query extends BaseQuery
{ {
/** /**
* @var string|array the columns being selected. This refers to the SELECT clause in a SQL
* statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`).
* If not set, if means all columns.
* @see select()
*/
public $select;
/**
* @var string additional option that should be appended to the 'SELECT' keyword. For example,
* in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
*/
public $selectOption;
/**
* @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement.
* It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`).
* @see from()
*/
public $from;
/**
* @var boolean whether to select distinct rows of data only. If this is set true,
* the SELECT clause would be changed to SELECT DISTINCT.
*/
public $distinct;
/**
* @var string|array query condition. This refers to the WHERE clause in a SQL statement.
* For example, `age > 31 AND team = 1`.
* @see where()
*/
public $where;
/**
* @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
*/
public $limit;
/**
* @var integer zero-based offset from where the records are to be returned. If not set or
* less than 0, it means starting from the beginning.
*/
public $offset;
/**
* @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 $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 $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.
* `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`).
* @see join()
*/
public $join;
/**
* @var string|array the condition to be applied in the GROUP BY clause.
* It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
*/
public $having;
/**
* @var array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
*/
public $params;
/**
* @var string|array the UNION clause(s) in a SQL statement. This can be either a string
* representing a single UNION clause or an array representing multiple UNION clauses.
* Each union clause can be a string or a `Query` object which refers to the SQL statement.
*/
public $union;
/**
* @var array the operation that this query represents. This refers to the method call as well as * @var array the operation that this query represents. This refers to the method call as well as
* the corresponding parameters for constructing a non-query SQL statement (e.g. INSERT, CREATE TABLE). * the corresponding parameters for constructing a non-query SQL statement (e.g. INSERT, CREATE TABLE).
* This property is mainly maintained by methods such as [[insert()]], [[update()]], [[createTable()]]. * This property is mainly maintained by methods such as [[insert()]], [[update()]], [[createTable()]].
@ -120,497 +49,33 @@ class Query extends \yii\base\Object
*/ */
public $operation; public $operation;
/**
/**
* Sets the SELECT part of the query.
* @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @param boolean $distinct whether to use 'SELECT DISTINCT'.
* @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
* in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
* @return Query the query object itself
*/
public function select($columns = '*', $distinct = false, $option = '')
{
$this->select = $columns;
$this->distinct = $distinct;
$this->selectOption = $option;
return $this;
}
/**
* Sets the FROM part of the query.
* @param mixed $tables the table(s) to be selected from. This can be either a string (e.g. 'tbl_user')
* or an array (e.g. array('tbl_user', 'tbl_profile')) specifying one or several table names.
* Table names can contain schema prefixes (e.g. 'public.tbl_user') and/or table aliases (e.g. 'tbl_user u').
* The method will automatically quote the table names unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return Query the query object itself
*/
public function from($tables)
{
$this->from = $tables;
return $this;
}
/**
* Sets the WHERE part of the query.
*
* The method requires a $condition parameter, and optionally a $params parameter
* specifying the values to be bound to the query.
*
* The $condition parameter should be either a string (e.g. 'id=1') or an array.
* If the latter, it must be in one of the following two formats:
*
* - hash format: `array('column1' => value1, 'column2' => value2, ...)`
* - operator format: `array(operator, operand1, operand2, ...)`
*
* A condition in hash format represents the following SQL expression in general:
* `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
* an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
* in the generated expression. Below are some examples:
*
* - `array('type'=>1, 'status'=>2)` generates `(type=1) AND (status=2)`.
* - `array('id'=>array(1,2,3), 'status'=>2)` generates `(id IN (1,2,3)) AND (status=2)`.
* - `array('status'=>null) generates `status IS NULL`.
*
* A condition in operator format generates the SQL expression according to the specified operator, which
* can be one of the followings:
*
* - `and`: the operands should be concatenated together using `AND`. For example,
* `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array,
* it will be converted into a string using the rules described here. For example,
* `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`.
* The method will NOT do any quoting or escaping.
*
* - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
*
* - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
* starting and ending values of the range that the column is in.
* For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`.
*
* - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
* in the generated condition.
*
* - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
* the range of the values that the column or DB expression should be in. For example,
* `array('in', 'id', array(1,2,3))` will generate `id IN (1,2,3)`.
* The method will properly quote the column name and escape values in the range.
*
* - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
*
* - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
* the values that the column or DB expression should be like.
* For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`.
* When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
* using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate
* `name LIKE '%test%' AND name LIKE '%sample%'`.
* The method will properly quote the column name and escape values in the range.
*
* - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* predicates when operand 2 is an array.
*
* - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
* in the generated condition.
*
* - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
* the `NOT LIKE` predicates.
*
* @param string|array $condition the conditions that should be put in the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query.
* For anonymous parameters, they can alternatively be specified as separate parameters to this method.
* For example, `where('type=? AND status=?', 100, 1)`.
* @return Query the query object itself
* @see andWhere()
* @see orWhere()
*/
public function where($condition, $params = array())
{
$this->where = $condition;
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see where()
* @see orWhere()
*/
public function andWhere($condition, $params = array())
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = array('and', $this->where, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see where()
* @see andWhere()
*/
public function orWhere($condition, $params = array())
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = array('or', $this->where, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
}
$this->addParams($params);
return $this;
}
/**
* Appends an INNER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param string|array $condition the join condition that should appear in the ON part.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
*/
public function join($table, $condition, $params = array())
{
$this->join[] = array('JOIN', $table, $condition);
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
array_shift($params);
}
return $this->addParams($params);
}
/**
* Appends a LEFT OUTER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param string|array $condition the join condition that should appear in the ON part.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Query the query object itself
*/
public function leftJoin($table, $condition, $params = array())
{
$this->join[] = array('LEFT JOIN', $table, $condition);
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
array_shift($params);
}
return $this->addParams($params);
}
/**
* Appends a RIGHT OUTER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param string|array $condition the join condition that should appear in the ON part.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query
* @return Query the query object itself
*/
public function rightJoin($table, $condition, $params = array())
{
$this->join[] = array('RIGHT JOIN', $table, $condition);
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
array_shift($params);
}
return $this->addParams($params);
}
/**
* Appends a CROSS JOIN part to the query.
* Note that not all DBMS support CROSS JOIN.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return Query the query object itself
*/
public function crossJoin($table)
{
$this->join[] = array('CROSS JOIN', $table);
return $this;
}
/**
* Appends a NATURAL JOIN part to the query.
* Note that not all DBMS support NATURAL JOIN.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return Query the query object itself
*/
public function naturalJoin($table)
{
$this->join[] = array('NATURAL JOIN', $table);
return $this;
}
/**
* Sets the GROUP BY part of the query.
* @param string|array $columns the columns to be grouped by.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see addGroupBy()
*/
public function groupBy($columns)
{
$this->groupBy = $columns;
return $this;
}
/**
* Adds additional group-by columns to the existing ones.
* @param string|array $columns additional columns to be grouped by.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see groupBy()
*/
public function addGroupBy($columns)
{
if (empty($this->groupBy)) {
$this->groupBy = $columns;
} else {
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->groupBy = array_merge($this->groupBy, $columns);
}
return $this;
}
/**
* Sets the HAVING part of the query.
* @param string|array $condition the conditions to be put after HAVING.
* Please refer to [[where()]] on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see andHaving()
* @see orHaving()
*/
public function having($condition, $params = array())
{
$this->having = $condition;
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional HAVING condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param string|array $condition the new HAVING condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see having()
* @see orHaving()
*/
public function andHaving($condition, $params = array())
{
if ($this->having === null) {
$this->having = $condition;
} else {
$this->having = array('and', $this->having, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
}
$this->addParams($params);
return $this;
}
/**
* Adds an additional HAVING condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* @param string|array $condition the new HAVING condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name=>value) to be bound to the query.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see having()
* @see andHaving()
*/
public function orHaving($condition, $params = array())
{
if ($this->having === null) {
$this->having = $condition;
} else {
$this->having = array('or', $this->having, $condition);
}
if (!is_array($params)) {
$params = func_get_args();
array_shift($params);
}
$this->addParams($params);
return $this;
}
/**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see addOrderBy()
*/
public function orderBy($columns)
{
$this->orderBy = $columns;
return $this;
}
/** /**
* Adds additional ORDER BY columns to the query. * Generates and returns the SQL statement according to this query.
* @param string|array $columns the columns (and the directions) to be ordered by. * @param Connection $connection the database connection used to generate the SQL statement.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')). * If this parameter is not given, the `db` application component will be used.
* The method will automatically quote the column names unless a column contains some parenthesis * @return string the generated SQL statement
* (which means the column contains a DB expression).
* @return Query the query object itself
* @see orderBy()
*/
public function addOrderBy($columns)
{
if (empty($this->orderBy)) {
$this->orderBy = $columns;
} else {
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->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit
* @return Query the query object itself
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset
* @return Query the query object itself
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* Appends a SQL statement using UNION operator.
* @param string $sql the SQL statement to be appended using UNION
* @return Query the query object itself
*/
public function union($sql)
{
$this->union[] = $sql;
return $this;
}
/**
* Sets the parameters to be bound to the query.
* @param array list of query parameter values indexed by parameter placeholders.
* For example, `array(':name'=>'Dan', ':age'=>31)`.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters.
* @return Query the query object itself
* @see addParams()
*/ */
public function params($params) public function getSql($connection = null)
{ {
if (!is_array($params)) { if ($connection === null) {
$params = func_get_args(); $connection = \Yii::$application->db;
} }
$this->params = $params; return $connection->getQueryBuilder()->build($this);
return $this;
} }
/** /**
* Adds additional parameters to be bound to the query. * Creates a DB command that can be used to execute this query.
* @param array list of query parameter values indexed by parameter placeholders. * @param Connection $connection the database connection used to generate the SQL statement.
* For example, `array(':name'=>'Dan', ':age'=>31)`. * If this parameter is not given, the `db` application component will be used.
* Please refer to [[where()]] on alternative syntax of specifying anonymous parameters. * @return Command the created DB command instance.
* @return Query the query object itself
* @see params()
*/ */
public function addParams($params) public function createCommand($connection = null)
{ {
if (!is_array($params)) { if ($connection === null) {
$params = func_get_args(); $connection = \Yii::$application->db;
}
foreach ($params as $name => $value) {
if (is_integer($name)) {
$this->params[] = $value;
} else {
$this->params[$name] = $value;
}
} }
return $this; return $connection->createCommand($this);
} }
/** /**
@ -643,7 +108,7 @@ class Query extends \yii\base\Object
$params = func_get_args(); $params = func_get_args();
array_shift($params); array_shift($params);
array_shift($params); array_shift($params);
array_shift($params); unset($params[0]);
} }
$this->addParams($params); $this->addParams($params);
$this->operation = array(__FUNCTION__, $table, $columns, $condition, array()); $this->operation = array(__FUNCTION__, $table, $columns, $condition, array());
@ -664,7 +129,7 @@ class Query extends \yii\base\Object
if (!is_array($params)) { if (!is_array($params)) {
$params = func_get_args(); $params = func_get_args();
array_shift($params); array_shift($params);
array_shift($params); unset($params[0]);
} }
$this->operation = array(__FUNCTION__, $table, $condition); $this->operation = array(__FUNCTION__, $table, $condition);
return $this->addParams($params); return $this->addParams($params);
@ -839,147 +304,4 @@ class Query extends \yii\base\Object
$this->operation = array(__FUNCTION__, $name, $table); $this->operation = array(__FUNCTION__, $name, $table);
return $this; return $this;
} }
/**
* Generates and returns the SQL statement according to this query.
* @param Connection $connection the database connection used to generate the SQL statement.
* If this parameter is not given, the `db` application component will be used.
* @return string the generated SQL statement
*/
public function getSql($connection = null)
{
if ($connection === null) {
$connection = \Yii::$application->db;
}
return $connection->getQueryBuilder()->build($this);
}
/**
* Creates a DB command that can be used to execute this query.
* @param Connection $connection the database connection used to generate the SQL statement.
* If this parameter is not given, the `db` application component will be used.
* @return Command the created DB command instance.
*/
public function createCommand($connection = null)
{
if ($connection === null) {
$connection = \Yii::$application->db;
}
return $connection->createCommand($this);
}
/**
* Resets the query object to its original state.
* @return Query the query object itself
*/
public function reset()
{
foreach (get_object_vars($this) as $name => $value) {
$this->$name = null;
}
return $this;
}
/**
* Merges this query with another one.
*
* The merging is done according to the following rules:
*
* - [[select]]: the union of both queries' [[select]] property values.
* - [[selectOption]], [[distinct]], [[limit]], [[offset]]: the new query
* takes precedence over this query.
* - [[where]], [[having]]: the new query's corresponding property value
* will be 'AND' together with the existing one.
* - [[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.
* @param array|Query $query the new query to be merged with this query.
* @return Query the query object itself
*/
public function mergeWith($query)
{
if (is_array($query)) {
$class = '\\' . get_class($this);
$query = $class::newInstance($query);
}
if ($this->select !== $query->select) {
if (empty($this->select)) {
$this->select = $query->select;
} elseif (!empty($query->select)) {
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($query->select) ? preg_split('/\s*,\s*/', trim($query->select), -1, PREG_SPLIT_NO_EMPTY) : $query->select;
$this->select = array_merge($select1, array_diff($select2, $select1));
}
}
if ($query->selectOption !== null) {
$this->selectOption = $query->selectOption;
}
if ($query->distinct !== null) {
$this->distinct = $query->distinct;
}
if ($query->limit !== null) {
$this->limit = $query->limit;
}
if ($query->offset !== null) {
$this->offset = $query->offset;
}
if ($query->where !== null) {
$this->andWhere($query->where);
}
if ($query->having !== null) {
$this->andHaving($query->having);
}
if ($query->params !== null) {
$this->addParams($query->params);
}
if ($query->orderBy !== null) {
$this->addOrderBy($query->orderBy);
}
if ($query->groupBy !== null) {
$this->addGroupBy($query->groupBy);
}
if ($query->join !== null) {
if (empty($this->join)) {
$this->join = $query->join;
} else {
if (!is_array($this->join)) {
$this->join = array($this->join);
}
if (is_array($query->join)) {
$this->join = array_merge($this->join, $query->join);
} else {
$this->join[] = $query->join;
}
}
}
if ($query->union !== null) {
if (empty($this->union)) {
$this->union = $query->union;
} else {
if (!is_array($this->union)) {
$this->union = array($this->union);
}
if (is_array($query->union)) {
$this->union = array_merge($this->union, $query->union);
} else {
$this->union[] = $query->union;
}
}
}
return $this;
}
} }

23
framework/util/Text.php

@ -44,8 +44,27 @@ class Text
return $name . 's'; return $name . 's';
} }
public static function dd($value) /**
* Converts a class name into space-separated words.
* For example, 'PostTag' will be converted as 'Post Tag'.
* @param string $name the string to be converted
* @param boolean $ucwords whether to capitalize the first letter in each word
* @return string the resulting words
*/
public static function name2words($name, $ucwords = true)
{
$label = trim(strtolower(str_replace(array('-', '_', '.'), ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name))));
return $ucwords ? ucwords($label) : $label;
}
/**
* Converts a class name into a HTML ID.
* For example, 'PostTag' will be converted as 'post-tag'.
* @param string $name the string to be converted
* @return string the resulting ID
*/
public static function name2id($name)
{ {
return trim(strtolower(str_replace(array('-', '_', '.'), ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $value)))); return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $name))),'-');
} }
} }

23
tests/unit/framework/base/BehaviorTest.php

@ -7,6 +7,16 @@ class BarClass extends \yii\base\Component
} }
class FooClass extends \yii\base\Component
{
public function behaviors()
{
return array(
'foo' => __NAMESPACE__ . '\BarBehavior',
);
}
}
class BarBehavior extends \yii\base\Behavior class BarBehavior extends \yii\base\Behavior
{ {
public $behaviorProperty = 'behavior property'; public $behaviorProperty = 'behavior property';
@ -21,12 +31,19 @@ class BehaviorTest extends \yiiunit\TestCase
{ {
public function testAttachAndAccessing() public function testAttachAndAccessing()
{ {
$bar = BarClass::newInstance(); $bar = new BarClass();
$behavior = new BarBehavior(); $behavior = new BarBehavior();
$bar->attachBehavior('bar', $behavior); $bar->attachBehavior('bar', $behavior);
$this->assertEquals('behavior property', $bar->behaviorProperty); $this->assertEquals('behavior property', $bar->behaviorProperty);
$this->assertEquals('behavior method', $bar->behaviorMethod()); $this->assertEquals('behavior method', $bar->behaviorMethod());
$this->assertEquals('behavior property', $bar->bar->behaviorProperty); $this->assertEquals('behavior property', $bar->asa('bar')->behaviorProperty);
$this->assertEquals('behavior method', $bar->bar->behaviorMethod()); $this->assertEquals('behavior method', $bar->asa('bar')->behaviorMethod());
}
public function testAutomaticAttach()
{
$foo = new FooClass();
$this->assertEquals('behavior property', $foo->behaviorProperty);
$this->assertEquals('behavior method', $foo->behaviorMethod());
} }
} }

Loading…
Cancel
Save