Commit 3e75c117 by Carsten Brandt

cleanup and reorder methods in redis ar + added link+unlink

parent 28c7acc4
<?php <?php
/** /**
* ActiveRecord class file.
*
* @author Carsten Brandt <mail@cebe.cc>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC * @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
......
<?php <?php
/** /**
* ActiveRecord class file.
*
* @author Carsten Brandt <mail@cebe.cc>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC * @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
...@@ -12,9 +9,7 @@ namespace yii\redis; ...@@ -12,9 +9,7 @@ namespace yii\redis;
use yii\base\InvalidCallException; use yii\base\InvalidCallException;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException; use yii\base\NotSupportedException;
use yii\base\UnknownMethodException;
use yii\db\TableSchema; use yii\db\TableSchema;
use yii\helpers\StringHelper; use yii\helpers\StringHelper;
...@@ -51,126 +46,6 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord ...@@ -51,126 +46,6 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
} }
/** /**
* Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createQuery()
{
return new ActiveQuery(array(
'modelClass' => get_called_class(),
));
}
/**
* Declares the name of the database table associated with this AR class.
* @return string the table name
*/
public static function tableName()
{
return static::getTableSchema()->name;
}
/**
* This method is ment to be overridden in redis ActiveRecord subclasses to return a [[RecordSchema]] instance.
* @return RecordSchema
* @throws \yii\base\InvalidConfigException
*/
public static function getRecordSchema()
{
throw new InvalidConfigException(__CLASS__.'::getRecordSchema() needs to be overridden in subclasses and return a RecordSchema.');
}
/**
* Returns the schema information of the DB table associated with this AR class.
* @return TableSchema the schema information of the DB table associated with this AR class.
*/
public static function getTableSchema()
{
$class = get_called_class();
if (isset(self::$_tables[$class])) {
return self::$_tables[$class];
}
return self::$_tables[$class] = static::getRecordSchema();
}
/**
* Inserts a row into the associated database table using the attribute values of this record.
*
* This method performs the following steps in order:
*
* 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
* fails, it will skip the rest of the steps;
* 2. call [[afterValidate()]] when `$runValidation` is true.
* 3. call [[beforeSave()]]. If the method returns false, it will skip the
* rest of the steps;
* 4. insert the record into database. If this fails, it will skip the rest of the steps;
* 5. call [[afterSave()]];
*
* In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
* [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
* will be raised by the corresponding methods.
*
* Only the [[changedAttributes|changed attribute values]] will be inserted into database.
*
* If the table's primary key is auto-incremental and is null during insertion,
* it will be populated with the actual value after insertion.
*
* For example, to insert a customer record:
*
* ~~~
* $customer = new Customer;
* $customer->name = $name;
* $customer->email = $email;
* $customer->insert();
* ~~~
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be inserted into the database.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the attributes are valid and the record is inserted successfully.
*/
public function insert($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
return false;
}
if ($this->beforeSave(true)) {
$db = static::getDb();
$values = $this->getDirtyAttributes($attributes);
$pk = array();
// if ($values === array()) {
foreach ($this->primaryKey() as $key) {
$pk[$key] = $values[$key] = $this->getAttribute($key);
if ($pk[$key] === null) {
$pk[$key] = $values[$key] = $db->executeCommand('INCR', array(static::tableName() . ':s:' . $key));
$this->setAttribute($key, $values[$key]);
}
}
// }
// save pk in a findall pool
$db->executeCommand('RPUSH', array(static::tableName(), static::buildKey($pk)));
$key = static::tableName() . ':a:' . static::buildKey($pk);
// save attributes
$args = array($key);
foreach($values as $attribute => $value) {
$args[] = $attribute;
$args[] = $value;
}
$db->executeCommand('HMSET', $args);
$this->setOldAttributes($values);
$this->afterSave(true);
return true;
}
return false;
}
/**
* Updates the whole table using the provided attribute values and conditions. * Updates the whole table using the provided attribute values and conditions.
* For example, to change the status to be 1 for all customers whose status is 2: * For example, to change the status to be 1 for all customers whose status is 2:
* *
...@@ -328,6 +203,53 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord ...@@ -328,6 +203,53 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
} }
/** /**
* Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createQuery()
{
return new ActiveQuery(array(
'modelClass' => get_called_class(),
));
}
/**
* Declares the name of the database table associated with this AR class.
* @return string the table name
*/
public static function tableName()
{
return static::getTableSchema()->name;
}
/**
* This method is ment to be overridden in redis ActiveRecord subclasses to return a [[RecordSchema]] instance.
* @return RecordSchema
* @throws \yii\base\InvalidConfigException
*/
public static function getRecordSchema()
{
throw new InvalidConfigException(__CLASS__.'::getRecordSchema() needs to be overridden in subclasses and return a RecordSchema.');
}
/**
* Returns the schema information of the DB table associated with this AR class.
* @return TableSchema the schema information of the DB table associated with this AR class.
*/
public static function getTableSchema()
{
$class = get_called_class();
if (isset(self::$_tables[$class])) {
return self::$_tables[$class];
}
return self::$_tables[$class] = static::getRecordSchema();
}
/**
* Declares a `has-one` relation. * Declares a `has-one` relation.
* The declaration is returned in terms of an [[ActiveRelation]] instance * The declaration is returned in terms of an [[ActiveRelation]] instance
* through which the related record can be queried and retrieved back. * through which the related record can be queried and retrieved back.
...@@ -404,4 +326,124 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord ...@@ -404,4 +326,124 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord
'multiple' => true, 'multiple' => true,
)); ));
} }
/**
* @inheritDocs
*/
public function insert($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
return false;
}
if ($this->beforeSave(true)) {
$db = static::getDb();
$values = $this->getDirtyAttributes($attributes);
$pk = array();
// if ($values === array()) {
foreach ($this->primaryKey() as $key) {
$pk[$key] = $values[$key] = $this->getAttribute($key);
if ($pk[$key] === null) {
$pk[$key] = $values[$key] = $db->executeCommand('INCR', array(static::tableName() . ':s:' . $key));
$this->setAttribute($key, $values[$key]);
}
}
// }
// save pk in a findall pool
$db->executeCommand('RPUSH', array(static::tableName(), static::buildKey($pk)));
$key = static::tableName() . ':a:' . static::buildKey($pk);
// save attributes
$args = array($key);
foreach($values as $attribute => $value) {
$args[] = $attribute;
$args[] = $value;
}
$db->executeCommand('HMSET', $args);
$this->setOldAttributes($values);
$this->afterSave(true);
return true;
}
return false;
}
// TODO port these changes back to AR
/**
* @inheritDocs
*/
public function link($name, $model, $extraColumns = array())
{
$relation = $this->getRelation($name);
if ($relation->via !== null) {
if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
}
if (is_array($relation->via)) {
/** @var $viaRelation ActiveRelation */
list($viaName, $viaRelation) = $relation->via;
/** @var $viaClass ActiveRecord */
$viaClass = $viaRelation->modelClass;
// unset $viaName so that it can be reloaded to reflect the change
// unset($this->_related[strtolower($viaName)]); // TODO this needs private access
} else {
throw new NotSupportedException('redis does not support relations via table.');
}
$columns = array();
foreach ($viaRelation->link as $a => $b) {
$columns[$a] = $this->$b;
}
foreach ($relation->link as $a => $b) {
$columns[$b] = $model->$a;
}
foreach ($extraColumns as $k => $v) {
$columns[$k] = $v;
}
$record = new $viaClass();
foreach($columns as $column => $value) {
$record->$column = $value;
}
$record->insert();
} else {
parent::link($name, $model, $extraColumns);
}
}
/**
* @inheritDocs
*/
public function unlink($name, $model, $delete = false)
{
$relation = $this->getRelation($name);
if ($relation->via !== null) {
if (is_array($relation->via)) {
/** @var $viaRelation ActiveRelation */
list($viaName, $viaRelation) = $relation->via;
/** @var $viaClass ActiveRecord */
$viaClass = $viaRelation->modelClass;
//unset($this->_related[strtolower($viaName)]); // TODO this needs private access
} else {
throw new NotSupportedException('redis does not support relations via table.');
}
$columns = array();
foreach ($viaRelation->link as $a => $b) {
$columns[$a] = $this->$b;
}
foreach ($relation->link as $a => $b) {
$columns[$b] = $model->$a;
}
if ($delete) {
$viaClass::deleteAll($columns);
} else {
$nulls = array();
foreach (array_keys($columns) as $a) {
$nulls[$a] = null;
}
$viaClass::updateAll($nulls, $columns);
}
} else {
parent::unlink($name, $model, $delete);
}
}
} }
<?php <?php
/** /**
* ActiveRecord class file.
*
* @author Carsten Brandt <mail@cebe.cc>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC * @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\redis; namespace yii\redis;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
// TODO this class is nearly completely duplicated from yii\db\ActiveRelation
/** /**
* ActiveRecord is the base class for classes representing relational data in terms of objects. * ActiveRelation represents a relation between two Active Record classes.
*
* ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveRelation object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
* *
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ActiveRelation extends \yii\redis\ActiveQuery class ActiveRelation extends ActiveQuery
{ {
/** /**
* @var boolean whether this relation should populate all query results into AR instances. * @var boolean whether this relation should populate all query results into AR instances.
......
<?php <?php
/** /**
* Connection class file
*
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC * @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
......
<?php <?php
/** /**
* * @link http://www.yiiframework.com/
* * @copyright Copyright (c) 2008 Yii Software LLC
* @author Carsten Brandt <mail@cebe.cc> * @license http://www.yiiframework.com/license/
*/ */
namespace yii\redis; namespace yii\redis;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\db\TableSchema; use yii\db\TableSchema;
......
<?php
/**
* Transaction class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\base\InvalidConfigException;
use yii\db\Exception;
/**
* Transaction represents a DB transaction.
*
* @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]]
* or [[rollBack()]]. This property is read-only.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Transaction extends \yii\base\Object
{
/**
* @var Connection the database connection that this transaction is associated with.
*/
public $db;
/**
* @var boolean whether this transaction is active. Only an active transaction
* can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started.
*/
private $_active = false;
/**
* Returns a value indicating whether this transaction is active.
* @return boolean whether this transaction is active. Only an active transaction
* can [[commit()]] or [[rollBack()]].
*/
public function getIsActive()
{
return $this->_active;
}
/**
* Begins a transaction.
* @throws InvalidConfigException if [[connection]] is null
*/
public function begin()
{
if (!$this->_active) {
if ($this->db === null) {
throw new InvalidConfigException('Transaction::db must be set.');
}
\Yii::trace('Starting transaction', __CLASS__);
$this->db->open();
$this->db->createCommand('MULTI')->execute();
$this->_active = true;
}
}
/**
* Commits a transaction.
* @throws Exception if the transaction or the DB connection is not active.
*/
public function commit()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Committing transaction', __CLASS__);
$this->db->createCommand('EXEC')->execute();
// TODO handle result of EXEC
$this->_active = false;
} else {
throw new Exception('Failed to commit transaction: transaction was inactive.');
}
}
/**
* Rolls back a transaction.
* @throws Exception if the transaction or the DB connection is not active.
*/
public function rollback()
{
if ($this->_active && $this->db && $this->db->isActive) {
\Yii::trace('Rolling back transaction', __CLASS__);
$this->db->pdo->commit();
$this->_active = false;
} else {
throw new Exception('Failed to roll back transaction: transaction was inactive.');
}
}
}
...@@ -319,21 +319,20 @@ class ActiveRecordTest extends RedisTestCase ...@@ -319,21 +319,20 @@ class ActiveRecordTest extends RedisTestCase
$this->assertEquals(1, $order->customer_id); $this->assertEquals(1, $order->customer_id);
$this->assertEquals(1, $order->customer->id); $this->assertEquals(1, $order->customer->id);
// TODO support via // via model
// // via model $order = Order::find(1);
// $order = Order::find(1); $this->assertEquals(2, count($order->items));
// $this->assertEquals(2, count($order->items)); $this->assertEquals(2, count($order->orderItems));
// $this->assertEquals(2, count($order->orderItems)); $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3));
// $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); $this->assertNull($orderItem);
// $this->assertNull($orderItem); $item = Item::find(3);
// $item = Item::find(3); $order->link('items', $item, array('quantity' => 10, 'subtotal' => 100));
// $order->link('items', $item, array('quantity' => 10, 'subtotal' => 100)); $this->assertEquals(3, count($order->items));
// $this->assertEquals(3, count($order->items)); $this->assertEquals(3, count($order->orderItems));
// $this->assertEquals(3, count($order->orderItems)); $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3));
// $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); $this->assertTrue($orderItem instanceof OrderItem);
// $this->assertTrue($orderItem instanceof OrderItem); $this->assertEquals(10, $orderItem->quantity);
// $this->assertEquals(10, $orderItem->quantity); $this->assertEquals(100, $orderItem->subtotal);
// $this->assertEquals(100, $orderItem->subtotal);
} }
public function testUnlink() public function testUnlink()
...@@ -345,14 +344,13 @@ class ActiveRecordTest extends RedisTestCase ...@@ -345,14 +344,13 @@ class ActiveRecordTest extends RedisTestCase
$this->assertEquals(1, count($customer->orders)); $this->assertEquals(1, count($customer->orders));
$this->assertNull(Order::find(3)); $this->assertNull(Order::find(3));
// TODO support via // via model
// // via model $order = Order::find(2);
// $order = Order::find(2); $this->assertEquals(3, count($order->items));
// $this->assertEquals(3, count($order->items)); $this->assertEquals(3, count($order->orderItems));
// $this->assertEquals(3, count($order->orderItems)); $order->unlink('items', $order->items[2], true);
// $order->unlink('items', $order->items[2], true); $this->assertEquals(2, count($order->items));
// $this->assertEquals(2, count($order->items)); $this->assertEquals(2, count($order->orderItems));
// $this->assertEquals(2, count($order->orderItems));
} }
public function testInsert() public function testInsert()
......
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