Commit a92acf65 by Qiang Xue

...

parent 2f37d093
......@@ -29,12 +29,12 @@ namespace yii\base;
* Event names are case-insensitive.
*
* 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.
*
* To attach an event handler to an event, call [[attachEventHandler]]. Alternatively,
* 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'`
* - 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
* attached to a component, its public properties and methods can be accessed via the
* component directly, as if the component owns those properties and methods.
* A behavior is an instance of [[Behavior]] or its child class. A component can be attached
* with one or multiple behaviors. When a behavior is attached to a component, its public
* 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, call [[attachBehavior]]; to detach a behavior
* from the component, call [[detachBehavior]].
* To attach a behavior to a component, declare it in [[behaviors()]], or explicitly call [[attachBehavior]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Component extends Object
{
/**
* @var Vector[] the attached event handlers (event name => handlers)
*/
private $_e;
/**
* @var Behavior[] the attached behaviors (behavior name => behavior)
*/
private $_b;
/**
......@@ -104,12 +108,11 @@ class Component extends Object
$this->_e[$name] = new Vector;
}
return $this->_e[$name];
} elseif (isset($this->_b[$name])) { // behavior
return $this->_b[$name];
} elseif (is_array($this->_b)) { // a behavior property
foreach ($this->_b as $object) {
if ($object->canGetProperty($name)) {
return $object->$name;
} else { // behavior property
$this->ensureBehaviors();
foreach ($this->_b as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name;
}
}
}
......@@ -143,10 +146,11 @@ class Component extends Object
$this->_e[$name] = new Vector;
}
return $this->_e[$name]->add($value);
} elseif (is_array($this->_b)) { // behavior
foreach ($this->_b as $object) {
if ($object->canSetProperty($name)) {
return $object->$name = $value;
} else { // behavior property
$this->ensureBehaviors();
foreach ($this->_b as $behavior) {
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
$name = strtolower($name);
return isset($this->_e[$name]) && $this->_e[$name]->getCount();
} elseif (isset($this->_b[$name])) { // has behavior
return true;
} elseif (is_array($this->_b)) {
foreach ($this->_b as $object) {
if ($object->canGetProperty($name)) {
return $object->$name !== null;
} else { // behavior property
$this->ensureBehaviors();
foreach ($this->_b as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name !== null;
}
}
}
......@@ -207,16 +210,17 @@ class Component extends Object
{
$setter = 'set' . $name;
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
unset($this->_e[strtolower($name)]);
return;
} elseif (isset($this->_b[$name])) { // behavior
return $this->detachBehavior($name);
} elseif (is_array($this->_b)) { // behavior property
foreach ($this->_b as $object) {
if ($object->canSetProperty($name)) {
return $object->$name = null;
} else { // behavior property
$this->ensureBehaviors();
foreach ($this->_b as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = null;
return;
}
}
}
......@@ -247,14 +251,42 @@ class Component extends Object
}
}
if ($this->_b !== null) {
foreach ($this->_b as $object) {
if (method_exists($object, $name)) {
return call_user_func_array(array($object, $name), $params);
}
$this->ensureBehaviors();
foreach ($this->_b as $object) {
if (method_exists($object, $name)) {
return call_user_func_array(array($object, $name), $params);
}
}
throw new Exception('Unknown method: ' . get_class($this) . "::$name()");
throw new Exception('Calling 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
* @return boolean whether there is any handler attached to the event.
*/
public function hasEventHandlers($name)
{
$this->ensureBehaviors();
$name = strtolower($name);
return isset($this->_e[$name]) && $this->_e[$name]->getCount();
}
......@@ -365,6 +398,7 @@ class Component extends Object
*/
public function raiseEvent($name, $event)
{
$this->ensureBehaviors();
$name = strtolower($name);
if ($event instanceof Event) {
$event->name = $name;
......@@ -406,6 +440,7 @@ class Component extends Object
*/
public function asa($behavior)
{
$this->ensureBehaviors();
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
* this component by calling the [[Behavior::attach]] method.
* @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 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
* @see detachBehavior
......@@ -457,10 +492,12 @@ class Component extends Object
public function detachBehavior($name)
{
if (isset($this->_b[$name])) {
$this->_b[$name]->detach($this);
$behavior = $this->_b[$name];
unset($this->_b[$name]);
$behavior->detach($this);
return $behavior;
} else {
return null;
}
}
......@@ -470,10 +507,24 @@ class Component extends Object
public function detachBehaviors()
{
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->_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);
}
}
}
}
<?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");
}
}
}
\ No newline at end of file
<?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]);
}
}
<?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
}
}
......@@ -44,8 +44,27 @@ class Text
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))),'-');
}
}
......@@ -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
{
public $behaviorProperty = 'behavior property';
......@@ -21,12 +31,19 @@ class BehaviorTest extends \yiiunit\TestCase
{
public function testAttachAndAccessing()
{
$bar = BarClass::newInstance();
$bar = new BarClass();
$behavior = new BarBehavior();
$bar->attachBehavior('bar', $behavior);
$this->assertEquals('behavior property', $bar->behaviorProperty);
$this->assertEquals('behavior method', $bar->behaviorMethod());
$this->assertEquals('behavior property', $bar->bar->behaviorProperty);
$this->assertEquals('behavior method', $bar->bar->behaviorMethod());
$this->assertEquals('behavior property', $bar->asa('bar')->behaviorProperty);
$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());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment