refactored Model and redis AR to allow drop of RecordSchema

This commit is contained in:
Carsten Brandt
2013-11-22 18:17:31 +01:00
parent 8542448f20
commit cb4504a10f
11 changed files with 85 additions and 190 deletions

View File

@@ -229,14 +229,13 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* You may override this method to change the default behavior.
* @return array list of attribute names.
*/
public function attributes()
public static function attributes()
{
$class = new ReflectionClass($this);
$class = new ReflectionClass(get_called_class());
$names = [];
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$name = $property->getName();
if (!$property->isStatic()) {
$names[] = $name;
$names[] = $property->getName();
}
}
return $names;

View File

@@ -568,9 +568,9 @@ class ActiveRecord extends Model
* The default implementation will return all column names of the table associated with this AR class.
* @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
*/
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
public static function create($row)
{
$record = static::instantiate($row);
$columns = static::getTableSchema()->columns;
$columns = array_flip(static::attributes());
foreach ($row as $name => $value) {
if (isset($columns[$name])) {
$record->_attributes[$name] = $value;

View File

@@ -9,22 +9,33 @@ namespace yii\redis;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\db\TableSchema;
use yii\helpers\StringHelper;
/**
* 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>
* @since 2.0
*/
class ActiveRecord extends \yii\db\ActiveRecord
{
/**
* @var array cache for TableSchema instances
*/
private static $_tables = [];
/**
* Returns the database connection used by this AR class.
* By default, the "redis" application component is used as the database connection.
@@ -36,14 +47,6 @@ class ActiveRecord extends \yii\db\ActiveRecord
return \Yii::$app->getComponent('redis');
}
/**
* @inheritdoc
*/
public static function findBySql($sql, $params = [])
{
throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord');
}
/**
* @inheritDoc
*/
@@ -61,35 +64,26 @@ class ActiveRecord extends \yii\db\ActiveRecord
}
/**
* Declares the name of the database table associated with this AR class.
* @return string the table name
* Returns the primary key name(s) for this AR class.
* This method should be overridden by child classes to define the primary key.
*
* Note that an array should be returned even when it is a single primary key.
*
* @return string[] the primary keys of this record.
*/
public static function tableName()
public static function primaryKey()
{
return static::getTableSchema()->name;
return ['id'];
}
/**
* This method is ment to be overridden in redis ActiveRecord subclasses to return a [[RecordSchema]] instance.
* @return RecordSchema
* @throws \yii\base\InvalidConfigException
* Returns the list of all attribute names of the model.
* This method must be overridden by child classes to define available attributes.
* @return array list of attribute names.
*/
public static function getRecordSchema()
public static function attributes()
{
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();
throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.');
}
/**
@@ -299,6 +293,22 @@ class ActiveRecord extends \yii\db\ActiveRecord
return md5(json_encode($key));
}
/**
* @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]].
* This method will always return false as transactional operations are not supported by redis.

View File

@@ -21,8 +21,6 @@ use yii\helpers\Inflector;
* @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 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>
* @since 2.0
@@ -201,10 +199,6 @@ class Connection extends Component
'ZSCORE', // key member Get the score associated with the given member in a sorted set
'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
*/
@@ -296,27 +290,6 @@ class Connection extends Component
$this->trigger(self::EVENT_AFTER_OPEN);
}
/**
* 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]].
* @return string name of the DB driver

View File

@@ -129,6 +129,10 @@ class LuaScriptBuilder extends \yii\base\Object
*/
private function build($query, $buildResult, $return)
{
if (!empty($query->orderBy)) {
throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.');
}
$columns = [];
if ($query->where !== null) {
$condition = $this->buildCondition($query->where, $columns);
@@ -139,6 +143,7 @@ class LuaScriptBuilder extends \yii\base\Object
$start = $query->offset === null ? 0 : $query->offset;
$limitCondition = 'i>' . $start . ($query->limit === null ? '' : ' and i<=' . ($start + $query->limit));
/** @var ActiveRecord $modelClass */
$modelClass = $query->modelClass;
$key = $this->quoteValue($modelClass::tableName());
$loadColumnValues = '';

View File

@@ -1,52 +0,0 @@
<?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.');
}
}
}
}

View File

@@ -64,13 +64,13 @@ class UniqueValidator extends Validator
$className = $this->className === null ? get_class($object) : $this->className;
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
$table = $className::getTableSchema();
if (($column = $table->getColumn($attributeName)) === null) {
throw new InvalidConfigException("Table '{$table->name}' does not have a column named '$attributeName'.");
$attributes = $className::attributes();
if (!in_array($attribute, $attributes)) {
throw new InvalidConfigException("'$className' does not have an attribute named '$attributeName'.");
}
$query = $className::find();
$query->where([$column->name => $value]);
$query->where([$attribute => $value]);
if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) {
// 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
$n = count($objects);
if ($n === 1) {
if ($column->isPrimaryKey) {
if (in_array($attribute, $className::primaryKey())) {
// primary key is modified and not unique
$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
} else {