From 3e75c117210bfbebbd73a595c9a280413a8a42da Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 24 Sep 2013 20:16:39 +0200 Subject: [PATCH] cleanup and reorder methods in redis ar + added link+unlink --- framework/yii/redis/ActiveQuery.php | 5 +- framework/yii/redis/ActiveRecord.php | 294 ++++++++++++++---------- framework/yii/redis/ActiveRelation.php | 21 +- framework/yii/redis/Connection.php | 4 +- framework/yii/redis/RecordSchema.php | 7 +- framework/yii/redis/Transaction.php | 93 -------- tests/unit/framework/redis/ActiveRecordTest.php | 44 ++-- 7 files changed, 209 insertions(+), 259 deletions(-) delete mode 100644 framework/yii/redis/Transaction.php diff --git a/framework/yii/redis/ActiveQuery.php b/framework/yii/redis/ActiveQuery.php index 59026f5..fff25cb 100644 --- a/framework/yii/redis/ActiveQuery.php +++ b/framework/yii/redis/ActiveQuery.php @@ -1,10 +1,7 @@ * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008 Yii Software LLC + * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ diff --git a/framework/yii/redis/ActiveRecord.php b/framework/yii/redis/ActiveRecord.php index 7b91b14..0850eb6 100644 --- a/framework/yii/redis/ActiveRecord.php +++ b/framework/yii/redis/ActiveRecord.php @@ -1,10 +1,7 @@ * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008 Yii Software LLC + * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ @@ -12,9 +9,7 @@ namespace yii\redis; use yii\base\InvalidCallException; use yii\base\InvalidConfigException; -use yii\base\InvalidParamException; use yii\base\NotSupportedException; -use yii\base\UnknownMethodException; use yii\db\TableSchema; use yii\helpers\StringHelper; @@ -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. * 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 } /** + * 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. * The declaration is returned in terms of an [[ActiveRelation]] instance * through which the related record can be queried and retrieved back. @@ -404,4 +326,124 @@ abstract class ActiveRecord extends \yii\db\ActiveRecord '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); + } + } } diff --git a/framework/yii/redis/ActiveRelation.php b/framework/yii/redis/ActiveRelation.php index d890720..ab4fd37 100644 --- a/framework/yii/redis/ActiveRelation.php +++ b/framework/yii/redis/ActiveRelation.php @@ -1,23 +1,32 @@ * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008 Yii Software LLC + * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace yii\redis; + 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 * @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. diff --git a/framework/yii/redis/Connection.php b/framework/yii/redis/Connection.php index 0b45659..1a09f7a 100644 --- a/framework/yii/redis/Connection.php +++ b/framework/yii/redis/Connection.php @@ -1,9 +1,7 @@ + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ */ namespace yii\redis; - use yii\base\InvalidConfigException; use yii\db\TableSchema; diff --git a/framework/yii/redis/Transaction.php b/framework/yii/redis/Transaction.php deleted file mode 100644 index 94cff7a..0000000 --- a/framework/yii/redis/Transaction.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @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.'); - } - } -} diff --git a/tests/unit/framework/redis/ActiveRecordTest.php b/tests/unit/framework/redis/ActiveRecordTest.php index 55286a8..a6ac3ce 100644 --- a/tests/unit/framework/redis/ActiveRecordTest.php +++ b/tests/unit/framework/redis/ActiveRecordTest.php @@ -319,21 +319,20 @@ class ActiveRecordTest extends RedisTestCase $this->assertEquals(1, $order->customer_id); $this->assertEquals(1, $order->customer->id); - // TODO support via -// // via model -// $order = Order::find(1); -// $this->assertEquals(2, count($order->items)); -// $this->assertEquals(2, count($order->orderItems)); -// $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); -// $this->assertNull($orderItem); -// $item = Item::find(3); -// $order->link('items', $item, array('quantity' => 10, 'subtotal' => 100)); -// $this->assertEquals(3, count($order->items)); -// $this->assertEquals(3, count($order->orderItems)); -// $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); -// $this->assertTrue($orderItem instanceof OrderItem); -// $this->assertEquals(10, $orderItem->quantity); -// $this->assertEquals(100, $orderItem->subtotal); + // via model + $order = Order::find(1); + $this->assertEquals(2, count($order->items)); + $this->assertEquals(2, count($order->orderItems)); + $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); + $this->assertNull($orderItem); + $item = Item::find(3); + $order->link('items', $item, array('quantity' => 10, 'subtotal' => 100)); + $this->assertEquals(3, count($order->items)); + $this->assertEquals(3, count($order->orderItems)); + $orderItem = OrderItem::find(array('order_id' => 1, 'item_id' => 3)); + $this->assertTrue($orderItem instanceof OrderItem); + $this->assertEquals(10, $orderItem->quantity); + $this->assertEquals(100, $orderItem->subtotal); } public function testUnlink() @@ -345,14 +344,13 @@ class ActiveRecordTest extends RedisTestCase $this->assertEquals(1, count($customer->orders)); $this->assertNull(Order::find(3)); - // TODO support via -// // via model -// $order = Order::find(2); -// $this->assertEquals(3, count($order->items)); -// $this->assertEquals(3, count($order->orderItems)); -// $order->unlink('items', $order->items[2], true); -// $this->assertEquals(2, count($order->items)); -// $this->assertEquals(2, count($order->orderItems)); + // via model + $order = Order::find(2); + $this->assertEquals(3, count($order->items)); + $this->assertEquals(3, count($order->orderItems)); + $order->unlink('items', $order->items[2], true); + $this->assertEquals(2, count($order->items)); + $this->assertEquals(2, count($order->orderItems)); } public function testInsert()