Commit cb4504a1 by Carsten Brandt

refactored Model and redis AR to allow drop of RecordSchema

parent 8542448f
...@@ -229,14 +229,13 @@ class Model extends Component implements IteratorAggregate, ArrayAccess ...@@ -229,14 +229,13 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* You may override this method to change the default behavior. * You may override this method to change the default behavior.
* @return array list of attribute names. * @return array list of attribute names.
*/ */
public function attributes() public static function attributes()
{ {
$class = new ReflectionClass($this); $class = new ReflectionClass(get_called_class());
$names = []; $names = [];
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$name = $property->getName();
if (!$property->isStatic()) { if (!$property->isStatic()) {
$names[] = $name; $names[] = $property->getName();
} }
} }
return $names; return $names;
......
...@@ -568,9 +568,9 @@ class ActiveRecord extends Model ...@@ -568,9 +568,9 @@ class ActiveRecord extends Model
* The default implementation will return all column names of the table associated with this AR class. * The default implementation will return all column names of the table associated with this AR class.
* @return array list of attribute names. * @return array list of attribute names.
*/ */
public function attributes() public static function attributes()
{ {
return array_keys($this->getTableSchema()->columns); return array_keys(static::getTableSchema()->columns);
} }
/** /**
...@@ -580,7 +580,7 @@ class ActiveRecord extends Model ...@@ -580,7 +580,7 @@ class ActiveRecord extends Model
*/ */
public function hasAttribute($name) public function hasAttribute($name)
{ {
return isset($this->_attributes[$name]) || isset($this->getTableSchema()->columns[$name]); return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
} }
/** /**
...@@ -1244,7 +1244,7 @@ class ActiveRecord extends Model ...@@ -1244,7 +1244,7 @@ class ActiveRecord extends Model
public static function create($row) public static function create($row)
{ {
$record = static::instantiate($row); $record = static::instantiate($row);
$columns = static::getTableSchema()->columns; $columns = array_flip(static::attributes());
foreach ($row as $name => $value) { foreach ($row as $name => $value) {
if (isset($columns[$name])) { if (isset($columns[$name])) {
$record->_attributes[$name] = $value; $record->_attributes[$name] = $value;
......
...@@ -9,23 +9,34 @@ namespace yii\redis; ...@@ -9,23 +9,34 @@ namespace yii\redis;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\NotSupportedException; use yii\base\NotSupportedException;
use yii\db\TableSchema;
use yii\helpers\StringHelper; use yii\helpers\StringHelper;
/** /**
* ActiveRecord is the base class for classes representing relational data in terms of objects. * ActiveRecord is the base class for classes representing relational data in terms of objects.
* *
* This class implements the ActiveRecord pattern for the [redis](http://redis.io/) key-value store.
*
* For defining a record a subclass should at least implement the [[attributes()]] method to define
* attributes. A primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified.
*
* The following is an example model called `Customer`:
*
* ```php
* class Customer extends \yii\redis\ActiveRecord
* {
* public function attributes()
* {
* return ['id', 'name', 'address', 'registration_date'];
* }
* }
* ```
*
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ActiveRecord extends \yii\db\ActiveRecord class ActiveRecord extends \yii\db\ActiveRecord
{ {
/** /**
* @var array cache for TableSchema instances
*/
private static $_tables = [];
/**
* Returns the database connection used by this AR class. * Returns the database connection used by this AR class.
* By default, the "redis" application component is used as the database connection. * By default, the "redis" application component is used as the database connection.
* You may override this method if you want to use a different database connection. * You may override this method if you want to use a different database connection.
...@@ -37,14 +48,6 @@ class ActiveRecord extends \yii\db\ActiveRecord ...@@ -37,14 +48,6 @@ class ActiveRecord extends \yii\db\ActiveRecord
} }
/** /**
* @inheritdoc
*/
public static function findBySql($sql, $params = [])
{
throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord');
}
/**
* @inheritDoc * @inheritDoc
*/ */
public static function createQuery() public static function createQuery()
...@@ -61,35 +64,26 @@ class ActiveRecord extends \yii\db\ActiveRecord ...@@ -61,35 +64,26 @@ class ActiveRecord extends \yii\db\ActiveRecord
} }
/** /**
* Declares the name of the database table associated with this AR class. * Returns the primary key name(s) for this AR class.
* @return string the table name * This method should be overridden by child classes to define the primary key.
*/ *
public static function tableName() * Note that an array should be returned even when it is a single primary key.
{ *
return static::getTableSchema()->name; * @return string[] the primary keys of this record.
}
/**
* 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() public static function primaryKey()
{ {
throw new InvalidConfigException(__CLASS__.'::getRecordSchema() needs to be overridden in subclasses and return a RecordSchema.'); return ['id'];
} }
/** /**
* Returns the schema information of the DB table associated with this AR class. * Returns the list of all attribute names of the model.
* @return TableSchema the schema information of the DB table associated with this AR class. * This method must be overridden by child classes to define available attributes.
* @return array list of attribute names.
*/ */
public static function getTableSchema() public static function attributes()
{ {
$class = get_called_class(); throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.');
if (isset(self::$_tables[$class])) {
return self::$_tables[$class];
}
return self::$_tables[$class] = static::getRecordSchema();
} }
/** /**
...@@ -300,6 +294,22 @@ class ActiveRecord extends \yii\db\ActiveRecord ...@@ -300,6 +294,22 @@ class ActiveRecord extends \yii\db\ActiveRecord
} }
/** /**
* @inheritdoc
*/
public static function getTableSchema()
{
throw new NotSupportedException('getTableSchema() is not supported by redis ActiveRecord');
}
/**
* @inheritdoc
*/
public static function findBySql($sql, $params = [])
{
throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord');
}
/**
* Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
* This method will always return false as transactional operations are not supported by redis. * This method will always return false as transactional operations are not supported by redis.
* @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
......
...@@ -21,8 +21,6 @@ use yii\helpers\Inflector; ...@@ -21,8 +21,6 @@ use yii\helpers\Inflector;
* @property string $driverName Name of the DB driver. This property is read-only. * @property string $driverName Name of the DB driver. This property is read-only.
* @property boolean $isActive Whether the DB connection is established. This property is read-only. * @property boolean $isActive Whether the DB connection is established. This property is read-only.
* @property LuaScriptBuilder $luaScriptBuilder This property is read-only. * @property LuaScriptBuilder $luaScriptBuilder This property is read-only.
* @property Transaction $transaction The currently active transaction. Null if no active transaction. This
* property is read-only.
* *
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
...@@ -202,10 +200,6 @@ class Connection extends Component ...@@ -202,10 +200,6 @@ class Connection extends Component
'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key
]; ];
/** /**
* @var Transaction the currently active transaction
*/
private $_transaction;
/**
* @var resource redis socket connection * @var resource redis socket connection
*/ */
private $_socket; private $_socket;
...@@ -297,27 +291,6 @@ class Connection extends Component ...@@ -297,27 +291,6 @@ class Connection extends Component
} }
/** /**
* Returns the currently active transaction.
* @return Transaction the currently active transaction. Null if no active transaction.
*/
public function getTransaction()
{
return $this->_transaction && $this->_transaction->isActive ? $this->_transaction : null;
}
/**
* Starts a transaction.
* @return Transaction the transaction initiated
*/
public function beginTransaction()
{
$this->open();
$this->_transaction = new Transaction(['db' => $this]);
$this->_transaction->begin();
return $this->_transaction;
}
/**
* Returns the name of the DB driver for the current [[dsn]]. * Returns the name of the DB driver for the current [[dsn]].
* @return string name of the DB driver * @return string name of the DB driver
*/ */
......
...@@ -129,6 +129,10 @@ class LuaScriptBuilder extends \yii\base\Object ...@@ -129,6 +129,10 @@ class LuaScriptBuilder extends \yii\base\Object
*/ */
private function build($query, $buildResult, $return) private function build($query, $buildResult, $return)
{ {
if (!empty($query->orderBy)) {
throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.');
}
$columns = []; $columns = [];
if ($query->where !== null) { if ($query->where !== null) {
$condition = $this->buildCondition($query->where, $columns); $condition = $this->buildCondition($query->where, $columns);
...@@ -139,6 +143,7 @@ class LuaScriptBuilder extends \yii\base\Object ...@@ -139,6 +143,7 @@ class LuaScriptBuilder extends \yii\base\Object
$start = $query->offset === null ? 0 : $query->offset; $start = $query->offset === null ? 0 : $query->offset;
$limitCondition = 'i>' . $start . ($query->limit === null ? '' : ' and i<=' . ($start + $query->limit)); $limitCondition = 'i>' . $start . ($query->limit === null ? '' : ' and i<=' . ($start + $query->limit));
/** @var ActiveRecord $modelClass */
$modelClass = $query->modelClass; $modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::tableName()); $key = $this->quoteValue($modelClass::tableName());
$loadColumnValues = ''; $loadColumnValues = '';
......
<?php
/**
* @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;
/**
* Class RecordSchema defines the data schema for a redis active record
*
* As there is no schema in a redis DB this class is used to define one.
*
* @package yii\db\redis
*/
class RecordSchema extends TableSchema
{
/**
* @var string[] column names.
*/
public $columns = array();
/**
* @return string the column type
*/
public function getColumn($name)
{
parent::getColumn($name);
}
public function init()
{
if (empty($this->name)) {
throw new InvalidConfigException('name of RecordSchema must not be empty.');
}
if (empty($this->primaryKey)) {
throw new InvalidConfigException('primaryKey of RecordSchema must not be empty.');
}
if (!is_array($this->primaryKey)) {
$this->primaryKey = [$this->primaryKey];
}
foreach($this->primaryKey as $pk) {
if (!isset($this->columns[$pk])) {
throw new InvalidConfigException('primaryKey '.$pk.' is not a colum of RecordSchema.');
}
}
}
}
\ No newline at end of file
...@@ -64,13 +64,13 @@ class UniqueValidator extends Validator ...@@ -64,13 +64,13 @@ class UniqueValidator extends Validator
$className = $this->className === null ? get_class($object) : $this->className; $className = $this->className === null ? get_class($object) : $this->className;
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName; $attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
$table = $className::getTableSchema(); $attributes = $className::attributes();
if (($column = $table->getColumn($attributeName)) === null) { if (!in_array($attribute, $attributes)) {
throw new InvalidConfigException("Table '{$table->name}' does not have a column named '$attributeName'."); throw new InvalidConfigException("'$className' does not have an attribute named '$attributeName'.");
} }
$query = $className::find(); $query = $className::find();
$query->where([$column->name => $value]); $query->where([$attribute => $value]);
if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) { if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) {
// if current $object isn't in the database yet then it's OK just to call exists() // if current $object isn't in the database yet then it's OK just to call exists()
...@@ -82,7 +82,7 @@ class UniqueValidator extends Validator ...@@ -82,7 +82,7 @@ class UniqueValidator extends Validator
$n = count($objects); $n = count($objects);
if ($n === 1) { if ($n === 1) {
if ($column->isPrimaryKey) { if (in_array($attribute, $className::primaryKey())) {
// primary key is modified and not unique // primary key is modified and not unique
$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey(); $exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
} else { } else {
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
namespace yiiunit\data\ar\redis; namespace yiiunit\data\ar\redis;
use yii\redis\RecordSchema;
class Customer extends ActiveRecord class Customer extends ActiveRecord
{ {
const STATUS_ACTIVE = 1; const STATUS_ACTIVE = 1;
...@@ -11,6 +9,11 @@ class Customer extends ActiveRecord ...@@ -11,6 +9,11 @@ class Customer extends ActiveRecord
public $status2; public $status2;
public static function attributes()
{
return ['id', 'email', 'name', 'address', 'status'];
}
/** /**
* @return \yii\redis\ActiveRelation * @return \yii\redis\ActiveRelation
*/ */
...@@ -23,19 +26,4 @@ class Customer extends ActiveRecord ...@@ -23,19 +26,4 @@ class Customer extends ActiveRecord
{ {
$query->andWhere(['status' => 1]); $query->andWhere(['status' => 1]);
} }
public static function getRecordSchema()
{
return new RecordSchema(array(
'name' => 'customer',
'primaryKey' => array('id'),
'columns' => array(
'id' => 'integer',
'email' => 'string',
'name' => 'string',
'address' => 'string',
'status' => 'integer'
)
));
}
} }
\ No newline at end of file
...@@ -2,21 +2,10 @@ ...@@ -2,21 +2,10 @@
namespace yiiunit\data\ar\redis; namespace yiiunit\data\ar\redis;
use yii\redis\RecordSchema;
class Item extends ActiveRecord class Item extends ActiveRecord
{ {
public static function getRecordSchema() public static function attributes()
{ {
return new RecordSchema([ return ['id', 'name', 'category_id'];
'name' => 'item',
'primaryKey' => ['id'],
'sequenceName' => 'id',
'columns' => [
'id' => 'integer',
'name' => 'string',
'category_id' => 'integer'
]
]);
} }
} }
\ No newline at end of file
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
namespace yiiunit\data\ar\redis; namespace yiiunit\data\ar\redis;
use yii\redis\RecordSchema;
class Order extends ActiveRecord class Order extends ActiveRecord
{ {
public static function attributes()
{
return ['id', 'customer_id', 'create_time', 'total'];
}
public function getCustomer() public function getCustomer()
{ {
return $this->hasOne(Customer::className(), ['id' => 'customer_id']); return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
...@@ -40,20 +43,4 @@ class Order extends ActiveRecord ...@@ -40,20 +43,4 @@ class Order extends ActiveRecord
return false; return false;
} }
} }
public static function getRecordSchema()
{
return new RecordSchema(array(
'name' => 'orders',
'primaryKey' => ['id'],
'columns' => array(
'id' => 'integer',
'customer_id' => 'integer',
'create_time' => 'integer',
'total' => 'decimal',
)
));
}
} }
\ No newline at end of file
...@@ -6,6 +6,16 @@ use yii\redis\RecordSchema; ...@@ -6,6 +6,16 @@ use yii\redis\RecordSchema;
class OrderItem extends ActiveRecord class OrderItem extends ActiveRecord
{ {
public static function primaryKey()
{
return ['order_id', 'item_id'];
}
public static function attributes()
{
return ['order_id', 'item_id', 'quantity', 'subtotal'];
}
public function getOrder() public function getOrder()
{ {
return $this->hasOne(Order::className(), ['id' => 'order_id']); return $this->hasOne(Order::className(), ['id' => 'order_id']);
...@@ -15,18 +25,4 @@ class OrderItem extends ActiveRecord ...@@ -15,18 +25,4 @@ class OrderItem extends ActiveRecord
{ {
return $this->hasOne(Item::className(), ['id' => 'item_id']); return $this->hasOne(Item::className(), ['id' => 'item_id']);
} }
public static function getRecordSchema()
{
return new RecordSchema(array(
'name' => 'order_item',
'primaryKey' => ['order_id', 'item_id'],
'columns' => array(
'order_id' => 'integer',
'item_id' => 'integer',
'quantity' => 'integer',
'subtotal' => 'decimal',
)
));
}
} }
\ No newline at end of file
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