Commit 8542448f by Carsten Brandt

refactored redis AR to relect the latest changes

- make use of traits - short array - better implementation of query findByPk
parent bc4324c0
......@@ -6,7 +6,11 @@
*/
namespace yii\redis;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\QueryTrait;
/**
* ActiveQuery represents a query associated with an Active Record class.
......@@ -43,91 +47,24 @@ use yii\base\NotSupportedException;
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveQuery extends \yii\base\Component
class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
{
/**
* Sort ascending
* @see orderBy
*/
const SORT_ASC = false;
/**
* Sort descending
* @see orderBy
*/
const SORT_DESC = true;
/**
* @var string the name of the ActiveRecord class.
*/
public $modelClass;
/**
* @var array list of relations that this query should be performed with
*/
public $with;
/**
* @var string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row or model data. For more details, see [[indexBy()]].
*/
public $indexBy;
/**
* @var boolean whether to return each record as an array. If false (default), an object
* of [[modelClass]] will be created to represent each record.
*/
public $asArray;
/**
* @var array the query condition.
* @see where()
*/
public $where;
/**
* @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
*/
public $limit;
/**
* @var integer zero-based offset from where the records are to be returned.
* If not set, it means starting from the beginning.
* If less than zero it means starting n elements from the end.
*/
public $offset;
/**
* @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
* The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
* can be either [[ActiveQuery::SORT_ASC]] or [[ActiveQuery::SORT_DESC]]. The array may also contain [[Expression]] objects.
* If that is the case, the expressions will be converted into strings without any change.
*/
public $orderBy;
use QueryTrait;
use ActiveQueryTrait;
/**
* PHP magic method.
* This method allows calling static method defined in [[modelClass]] via this query object.
* It is mainly implemented for supporting the feature of scope.
* @param string $name the method name to be called
* @param array $params the parameters passed to the method
* @return mixed the method return result
* Executes the query and returns all results as an array.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
*/
public function __call($name, $params)
{
if (method_exists($this->modelClass, $name)) {
array_unshift($params, $this);
call_user_func_array(array($this->modelClass, $name), $params);
return $this;
} else {
return parent::__call($name, $params);
}
}
/**
* Executes query and returns all results as an array.
* @return array the query results. If the query results in nothing, an empty array will be returned.
*/
public function all()
public function all($db = null)
{
// TODO add support for orderBy
$data = $this->executeScript('All');
$rows = array();
$data = $this->executeScript($db, 'All');
$rows = [];
foreach($data as $dataRow) {
$row = array();
$row = [];
$c = count($dataRow);
for($i = 0; $i < $c; ) {
$row[$dataRow[$i++]] = $dataRow[$i++];
......@@ -137,28 +74,30 @@ class ActiveQuery extends \yii\base\Component
if (!empty($rows)) {
$models = $this->createModels($rows);
if (!empty($this->with)) {
$this->populateRelations($models, $this->with);
$this->findWith($this->with, $models);
}
return $models;
} else {
return array();
return [];
}
}
/**
* Executes query and returns a single row of result.
* Executes the query and returns a single row of result.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. Null will be returned
* if the query results in nothing.
*/
public function one()
public function one($db = null)
{
// TODO add support for orderBy
$data = $this->executeScript('One');
if ($data === array()) {
$data = $this->executeScript($db, 'One');
if (empty($data)) {
return null;
}
$row = array();
$row = [];
$c = count($data);
for($i = 0; $i < $c; ) {
$row[$data[$i++]] = $data[$i++];
......@@ -166,584 +105,273 @@ class ActiveQuery extends \yii\base\Component
if ($this->asArray) {
$model = $row;
} else {
/** @var $class ActiveRecord */
/** @var ActiveRecord $class */
$class = $this->modelClass;
$model = $class::create($row);
}
if (!empty($this->with)) {
$models = array($model);
$this->populateRelations($models, $this->with);
$models = [$model];
$this->findWith($this->with, $models);
$model = $models[0];
}
return $model;
}
/**
* Executes the query and returns the first column of the result.
* @param string $column name of the column to select
* @return array the first column of the query result. An empty array is returned if the query results in nothing.
*/
public function column($column)
{
// TODO add support for indexBy and orderBy
return $this->executeScript('Column', $column);
}
/**
* Returns the number of records.
* @param string $q the COUNT expression. Defaults to '*'.
* Make sure you properly quote column names.
* @param string $q the COUNT expression. This parameter is ignored by this implementation.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return integer number of records
*/
public function count()
public function count($q = '*', $db = null)
{
if ($this->offset === null && $this->limit === null && $this->where === null) {
$modelClass = $this->modelClass;
/** @var Connection $db */
$db = $modelClass::getDb();
return $db->executeCommand('LLEN', array($modelClass::tableName()));
if ($db === null) {
$db = $modelClass::getDb();
}
return $db->executeCommand('LLEN', [$modelClass::tableName()]);
} else {
return $this->executeScript('Count');
return $this->executeScript($db, 'Count');
}
}
/**
* Returns a value indicating whether the query result contains any row of data.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return boolean whether the query result contains any row of data.
*/
public function exists($db = null)
{
return $this->one($db) !== null;
}
/**
* Executes the query and returns the first column of the result.
* @param string $column name of the column to select
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return array the first column of the query result. An empty array is returned if the query results in nothing.
*/
public function column($column, $db = null)
{
// TODO add support for indexBy and orderBy
return $this->executeScript($db, 'Column', $column);
}
/**
* Returns the number of records.
* @param string $column the column to sum up
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return integer number of records
*/
public function sum($column)
public function sum($column, $db = null)
{
return $this->executeScript('Sum', $column);
return $this->executeScript($db, 'Sum', $column);
}
/**
* Returns the average of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return integer the average of the specified column values.
*/
public function average($column)
public function average($column, $db = null)
{
return $this->executeScript('Average', $column);
return $this->executeScript($db, 'Average', $column);
}
/**
* Returns the minimum of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return integer the minimum of the specified column values.
*/
public function min($column)
public function min($column, $db = null)
{
return $this->executeScript('Min', $column);
return $this->executeScript($db, 'Min', $column);
}
/**
* Returns the maximum of the specified column values.
* @param string $column the column name or expression.
* Make sure you properly quote column names in the expression.
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return integer the maximum of the specified column values.
*/
public function max($column)
public function max($column, $db = null)
{
return $this->executeScript('Max', $column);
return $this->executeScript($db, 'Max', $column);
}
/**
* Returns the query result as a scalar value.
* The value returned will be the first column in the first row of the query results.
* @param string $column name of the column to select
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @return string|boolean the value of the first column in the first row of the query result.
* False is returned if the query result is empty.
*/
public function scalar($column)
public function scalar($column, $db = null)
{
$record = $this->one();
return $record->$column;
$record = $this->one($db);
if ($record === null) {
return false;
} else {
return $record->$column;
}
}
/**
* Returns a value indicating whether the query result contains any row of data.
* @return boolean whether the query result contains any row of data.
*/
public function exists()
{
return $this->one() !== null;
}
/**
* Executes a script created by [[LuaScriptBuilder]]
* @param string $type
* @param null $column
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @param string $type the type of the script to generate
* @param string $columnName
* @return array|bool|null|string
*/
protected function executeScript($type, $columnName=null)
protected function executeScript($db, $type, $columnName = null)
{
if (($data = $this->findByPk($type)) === false) {
$modelClass = $this->modelClass;
/** @var Connection $db */
/** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass;
if ($db === null) {
$db = $modelClass::getDb();
}
$method = 'build' . $type;
$script = $db->getLuaScriptBuilder()->$method($this, $columnName);
return $db->executeCommand('EVAL', array($script, 0));
// find by primary key if possible. This is much faster than scanning all records
if (is_array($this->where) && !isset($this->where[0]) && $modelClass::isPrimaryKey(array_keys($this->where))) {
return $this->findByPk($db, $type, $columnName);
}
return $data;
$method = 'build' . $type;
$script = $db->getLuaScriptBuilder()->$method($this, $columnName);
return $db->executeCommand('EVAL', [$script, 0]);
}
/**
* Fetch by pk if possible as this is much faster
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @param string $type the type of the script to generate
* @param string $columnName
* @return array|bool|null|string
* @throws \yii\base\NotSupportedException
*/
private function findByPk($type, $columnName = null)
private function findByPk($db, $type, $columnName = null)
{
$modelClass = $this->modelClass;
if (is_array($this->where) && !isset($this->where[0]) && $modelClass::isPrimaryKey(array_keys($this->where))) {
/** @var Connection $db */
$db = $modelClass::getDb();
if (count($this->where) == 1) {
$pks = (array) reset($this->where);
} else {
// TODO support IN for composite PK
return false;
if (count($this->where) == 1) {
$pks = (array) reset($this->where);
} else {
foreach($this->where as $column => $values) {
if (is_array($values)) {
// TODO support composite IN for composite PK
throw new NotSupportedException('find by composite PK is not yet implemented.');
}
}
$pks = [$this->where];
}
$start = $this->offset === null ? 0 : $this->offset;
$i = 0;
$data = array();
foreach($pks as $pk) {
if (++$i > $start && ($this->limit === null || $i <= $start + $this->limit)) {
$key = $modelClass::tableName() . ':a:' . $modelClass::buildKey($pk);
$result = $db->executeCommand('HGETALL', array($key));
if (!empty($result)) {
$data[] = $result;
if ($type === 'One' && $this->orderBy === null) {
break;
}
/** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass;
$start = $this->offset === null ? 0 : $this->offset;
$i = 0;
$data = [];
foreach($pks as $pk) {
if (++$i > $start && ($this->limit === null || $i <= $start + $this->limit)) {
$key = $modelClass::tableName() . ':a:' . $modelClass::buildKey($pk);
$result = $db->executeCommand('HGETALL', [$key]);
if (!empty($result)) {
$data[] = $result;
if ($type === 'One' && $this->orderBy === null) {
break;
}
}
}
// TODO support orderBy
switch($type) {
case 'All':
return $data;
case 'One':
return reset($data);
case 'Column':
// TODO support indexBy
$column = array();
foreach($data as $dataRow) {
$row = array();
$c = count($dataRow);
for($i = 0; $i < $c; ) {
$row[$dataRow[$i++]] = $dataRow[$i++];
}
$column[] = $row[$columnName];
}
return $column;
case 'Count':
return count($data);
case 'Sum':
$sum = 0;
foreach($data as $dataRow) {
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName) {
$sum += $dataRow[$i];
break;
}
}
}
// TODO support orderBy
switch($type) {
case 'All':
return $data;
case 'One':
return reset($data);
case 'Count':
return count($data);
case 'Column':
// TODO support indexBy
$column = [];
foreach($data as $dataRow) {
$row = [];
$c = count($dataRow);
for($i = 0; $i < $c; ) {
$row[$dataRow[$i++]] = $dataRow[$i++];
}
return $sum;
case 'Average':
$sum = 0;
$count = 0;
foreach($data as $dataRow) {
$count++;
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName) {
$sum += $dataRow[$i];
break;
}
$column[] = $row[$columnName];
}
return $column;
case 'Sum':
$sum = 0;
foreach($data as $dataRow) {
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName) {
$sum += $dataRow[$i];
break;
}
}
return $sum / $count;
case 'Min':
$min = null;
foreach($data as $dataRow) {
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName && ($min == null || $dataRow[$i] < $min)) {
$min = $dataRow[$i];
break;
}
}
return $sum;
case 'Average':
$sum = 0;
$count = 0;
foreach($data as $dataRow) {
$count++;
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName) {
$sum += $dataRow[$i];
break;
}
}
return $min;
case 'Max':
$max = null;
foreach($data as $dataRow) {
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName && ($max == null || $dataRow[$i] > $max)) {
$max = $dataRow[$i];
break;
}
}
return $sum / $count;
case 'Min':
$min = null;
foreach($data as $dataRow) {
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName && ($min == null || $dataRow[$i] < $min)) {
$min = $dataRow[$i];
break;
}
}
return $max;
}
}
return false;
}
// TODO: refactor. code below here is all duplicated from yii/db/ActiveQuery and yii/db/Query
/**
* Sets the [[asArray]] property.
* @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
* @return ActiveQuery the query object itself
*/
public function asArray($value = true)
{
$this->asArray = $value;
return $this;
}
/**
* Sets the ORDER BY part of the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return ActiveQuery the query object itself
* @see addOrderBy()
*/
public function orderBy($columns)
{
$this->orderBy = $this->normalizeOrderBy($columns);
return $this;
}
/**
* Adds additional ORDER BY columns to the query.
* @param string|array $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
* (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return ActiveQuery the query object itself
* @see orderBy()
*/
public function addOrderBy($columns)
{
$columns = $this->normalizeOrderBy($columns);
if ($this->orderBy === null) {
$this->orderBy = $columns;
} else {
$this->orderBy = array_merge($this->orderBy, $columns);
}
return $this;
}
protected function normalizeOrderBy($columns)
{
throw new NotSupportedException('orderBy is currently not supported');
if (is_array($columns)) {
return $columns;
} else {
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
$result = array();
foreach ($columns as $column) {
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? self::SORT_ASC : self::SORT_DESC;
} else {
$result[$column] = self::SORT_ASC;
}
}
return $result;
}
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit
* @return ActiveQuery the query object itself
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset
* @return ActiveQuery the query object itself
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* Specifies the relations with which this query should be performed.
*
* The parameters to this method can be either one or multiple strings, or a single array
* of relation names and the optional callbacks to customize the relations.
*
* The followings are some usage examples:
*
* ~~~
* // find customers together with their orders and country
* Customer::find()->with('orders', 'country')->all();
* // find customers together with their country and orders of status 1
* Customer::find()->with(array(
* 'orders' => function($query) {
* $query->andWhere('status = 1');
* },
* 'country',
* ))->all();
* ~~~
*
* @return ActiveQuery the query object itself
*/
public function with()
{
$this->with = func_get_args();
if (isset($this->with[0]) && is_array($this->with[0])) {
// the parameter is given as an array
$this->with = $this->with[0];
}
return $this;
}
/**
* Sets the [[indexBy]] property.
* @param string|callable $column the name of the column by which the query results should be indexed by.
* This can also be a callable (e.g. anonymous function) that returns the index value based on the given
* row or model data. The signature of the callable should be:
*
* ~~~
* // $model is an AR instance when `asArray` is false,
* // or an array of column values when `asArray` is true.
* function ($model)
* {
* // return the index value corresponding to $model
* }
* ~~~
*
* @return ActiveQuery the query object itself
*/
public function indexBy($column)
{
$this->indexBy = $column;
return $this;
}
/**
* Sets the WHERE part of the query.
*
* The method requires a $condition parameter, and optionally a $params parameter
* specifying the values to be bound to the query.
*
* The $condition parameter should be either a string (e.g. 'id=1') or an array.
* If the latter, it must be in one of the following two formats:
*
* - hash format: `array('column1' => value1, 'column2' => value2, ...)`
* - operator format: `array(operator, operand1, operand2, ...)`
*
* A condition in hash format represents the following SQL expression in general:
* `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
* an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
* in the generated expression. Below are some examples:
*
* - `array('type' => 1, 'status' => 2)` generates `(type = 1) AND (status = 2)`.
* - `array('id' => array(1, 2, 3), 'status' => 2)` generates `(id IN (1, 2, 3)) AND (status = 2)`.
* - `array('status' => null) generates `status IS NULL`.
*
* A condition in operator format generates the SQL expression according to the specified operator, which
* can be one of the followings:
*
* - `and`: the operands should be concatenated together using `AND`. For example,
* `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array,
* it will be converted into a string using the rules described here. For example,
* `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`.
* The method will NOT do any quoting or escaping.
*
* - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
*
* - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
* starting and ending values of the range that the column is in.
* For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`.
*
* - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
* in the generated condition.
*
* - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
* the range of the values that the column or DB expression should be in. For example,
* `array('in', 'id', array(1, 2, 3))` will generate `id IN (1, 2, 3)`.
* The method will properly quote the column name and escape values in the range.
*
* - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
*
* - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
* the values that the column or DB expression should be like.
* For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`.
* When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
* using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate
* `name LIKE '%test%' AND name LIKE '%sample%'`.
* The method will properly quote the column name and escape values in the range.
*
* - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* predicates when operand 2 is an array.
*
* - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
* in the generated condition.
*
* - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
* the `NOT LIKE` predicates.
*
* @param string|array $condition the conditions that should be put in the WHERE part.
* @return ActiveQuery the query object itself
* @see andWhere()
* @see orWhere()
*/
public function where($condition)
{
$this->where = $condition;
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return ActiveQuery the query object itself
* @see where()
* @see orWhere()
*/
public function andWhere($condition)
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = array('and', $this->where, $condition);
}
return $this;
}
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* @param string|array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @return ActiveQuery the query object itself
* @see where()
* @see andWhere()
*/
public function orWhere($condition)
{
if ($this->where === null) {
$this->where = $condition;
} else {
$this->where = array('or', $this->where, $condition);
}
return $this;
}
private function createModels($rows)
{
$models = array();
if ($this->asArray) {
if ($this->indexBy === null) {
return $rows;
}
foreach ($rows as $row) {
if (is_string($this->indexBy)) {
$key = $row[$this->indexBy];
} else {
$key = call_user_func($this->indexBy, $row);
}
$models[$key] = $row;
}
} else {
/** @var $class ActiveRecord */
$class = $this->modelClass;
if ($this->indexBy === null) {
foreach ($rows as $row) {
$models[] = $class::create($row);
}
} else {
foreach ($rows as $row) {
$model = $class::create($row);
if (is_string($this->indexBy)) {
$key = $model->{$this->indexBy};
} else {
$key = call_user_func($this->indexBy, $model);
return $min;
case 'Max':
$max = null;
foreach($data as $dataRow) {
$c = count($dataRow);
for($i = 0; $i < $c; ) {
if ($dataRow[$i++] == $columnName && ($max == null || $dataRow[$i] > $max)) {
$max = $dataRow[$i];
break;
}
}
$models[$key] = $model;
}
}
}
return $models;
}
private function populateRelations(&$models, $with)
{
$primaryModel = new $this->modelClass;
$relations = $this->normalizeRelations($primaryModel, $with);
foreach ($relations as $name => $relation) {
if ($relation->asArray === null) {
// inherit asArray from primary query
$relation->asArray = $this->asArray;
}
$relation->findWith($name, $models);
}
}
/**
* @param ActiveRecord $model
* @param array $with
* @return ActiveRelation[]
*/
private function normalizeRelations($model, $with)
{
$relations = array();
foreach ($with as $name => $callback) {
if (is_integer($name)) {
$name = $callback;
$callback = null;
}
if (($pos = strpos($name, '.')) !== false) {
// with sub-relations
$childName = substr($name, $pos + 1);
$name = substr($name, 0, $pos);
} else {
$childName = null;
}
$t = strtolower($name);
if (!isset($relations[$t])) {
$relation = $model->getRelation($name);
$relation->primaryModel = null;
$relations[$t] = $relation;
} else {
$relation = $relations[$t];
}
if (isset($childName)) {
$relation->with[$childName] = $callback;
} elseif ($callback !== null) {
call_user_func($callback, $relation);
}
return $max;
}
return $relations;
throw new InvalidParamException('Unknown fetch type: ' . $type);
}
}
......@@ -23,7 +23,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
/**
* @var array cache for TableSchema instances
*/
private static $_tables = array();
private static $_tables = [];
/**
* Returns the database connection used by this AR class.
......@@ -33,23 +33,111 @@ class ActiveRecord extends \yii\db\ActiveRecord
*/
public static function getDb()
{
return \Yii::$app->redis;
return \Yii::$app->getComponent('redis');
}
/**
* @inheritdoc
*/
public static function findBySql($sql, $params = array())
public static function findBySql($sql, $params = [])
{
throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord');
}
/**
* @inheritDoc
*/
public static function createQuery()
{
return new ActiveQuery(['modelClass' => get_called_class()]);
}
/**
* @inheritDoc
*/
protected function createActiveRelation($config = [])
{
return new ActiveRelation($config);
}
/**
* 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();
}
/**
* @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 = [];
// if ($values === []) {
foreach ($this->primaryKey() as $key) {
$pk[$key] = $values[$key] = $this->getAttribute($key);
if ($pk[$key] === null) {
$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::tableName() . ':s:' . $key]);
$this->setAttribute($key, $values[$key]);
}
}
// }
// save pk in a findall pool
$db->executeCommand('RPUSH', [static::tableName(), static::buildKey($pk)]);
$key = static::tableName() . ':a:' . static::buildKey($pk);
// save attributes
$args = [$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:
*
* ~~~
* Customer::updateAll(array('status' => 1), array('id' => 2));
* Customer::updateAll(['status' => 1], ['id' => 2]);
* ~~~
*
* @param array $attributes attribute values (name-value pairs) to be saved into the table
......@@ -58,7 +146,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
* @param array $params this parameter is ignored in redis implementation.
* @return integer the number of rows updated
*/
public static function updateAll($attributes, $condition = null, $params = array())
public static function updateAll($attributes, $condition = null, $params = [])
{
if (empty($attributes)) {
return 0;
......@@ -70,7 +158,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
$pk = static::buildKey($pk);
$key = static::tableName() . ':a:' . $pk;
// save attributes
$args = array($key);
$args = [$key];
foreach($attributes as $attribute => $value) {
if (isset($newPk[$attribute])) {
$newPk[$attribute] = $value;
......@@ -84,9 +172,9 @@ class ActiveRecord extends \yii\db\ActiveRecord
if ($newPk != $pk) {
$db->executeCommand('MULTI');
$db->executeCommand('HMSET', $args);
$db->executeCommand('LINSERT', array(static::tableName(), 'AFTER', $pk, $newPk));
$db->executeCommand('LREM', array(static::tableName(), 0, $pk));
$db->executeCommand('RENAME', array($key, $newKey));
$db->executeCommand('LINSERT', [static::tableName(), 'AFTER', $pk, $newPk]);
$db->executeCommand('LREM', [static::tableName(), 0, $pk]);
$db->executeCommand('RENAME', [$key, $newKey]);
$db->executeCommand('EXEC');
} else {
$db->executeCommand('HMSET', $args);
......@@ -101,7 +189,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
* For example, to increment all customers' age by 1,
*
* ~~~
* Customer::updateAllCounters(array('age' => 1));
* Customer::updateAllCounters(['age' => 1]);
* ~~~
*
* @param array $counters the counters to be updated (attribute name => increment value).
......@@ -111,7 +199,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
* @param array $params this parameter is ignored in redis implementation.
* @return integer the number of rows updated
*/
public static function updateAllCounters($counters, $condition = null, $params = array())
public static function updateAllCounters($counters, $condition = null, $params = [])
{
if (empty($counters)) {
return 0;
......@@ -121,7 +209,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
foreach(static::fetchPks($condition) as $pk) {
$key = static::tableName() . ':a:' . static::buildKey($pk);
foreach($counters as $attribute => $value) {
$db->executeCommand('HINCRBY', array($key, $attribute, $value));
$db->executeCommand('HINCRBY', [$key, $attribute, $value]);
}
$n++;
}
......@@ -135,7 +223,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
* For example, to delete all customers whose status is 3:
*
* ~~~
* Customer::deleteAll('status = 3');
* Customer::deleteAll(['status' => 3]);
* ~~~
*
* @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
......@@ -143,15 +231,15 @@ class ActiveRecord extends \yii\db\ActiveRecord
* @param array $params this parameter is ignored in redis implementation.
* @return integer the number of rows deleted
*/
public static function deleteAll($condition = null, $params = array())
public static function deleteAll($condition = null, $params = [])
{
$db = static::getDb();
$attributeKeys = array();
$attributeKeys = [];
$pks = static::fetchPks($condition);
$db->executeCommand('MULTI');
foreach($pks as $pk) {
$pk = static::buildKey($pk);
$db->executeCommand('LREM', array(static::tableName(), 0, $pk));
$db->executeCommand('LREM', [static::tableName(), 0, $pk]);
$attributeKeys[] = static::tableName() . ':a:' . $pk;
}
if (empty($attributeKeys)) {
......@@ -170,9 +258,9 @@ class ActiveRecord extends \yii\db\ActiveRecord
$records = $query->asArray()->all(); // TODO limit fetched columns to pk
$primaryKey = static::primaryKey();
$pks = array();
$pks = [];
foreach($records as $record) {
$pk = array();
$pk = [];
foreach($primaryKey as $key) {
$pk[$key] = $record[$key];
}
......@@ -197,6 +285,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
if (count($key) == 1) {
return self::buildKey(reset($key));
}
ksort($key); // ensure order is always the same
$isNumeric = true;
foreach($key as $value) {
if (!is_numeric($value)) {
......@@ -211,171 +300,6 @@ 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.
*
* A `has-one` relation means that there is at most one related record matching
* the criteria set by this relation, e.g., a customer has one country.
*
* For example, to declare the `country` relation for `Customer` class, we can write
* the following code in the `Customer` class:
*
* ~~~
* public function getCountry()
* {
* return $this->hasOne('Country', array('id' => 'country_id'));
* }
* ~~~
*
* Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
* in the related class `Country`, while the 'country_id' value refers to an attribute name
* in the current AR class.
*
* Call methods declared in [[ActiveRelation]] to further customize the relation.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the columns in the table associated with the `$class` model, while the values of the
* array refer to the corresponding columns in the table associated with this AR class.
* @return ActiveRelation the relation object.
*/
public function hasOne($class, $link)
{
return new ActiveRelation(array(
'modelClass' => $this->getNamespacedClass($class),
'primaryModel' => $this,
'link' => $link,
'multiple' => false,
));
}
/**
* Declares a `has-many` relation.
* The declaration is returned in terms of an [[ActiveRelation]] instance
* through which the related record can be queried and retrieved back.
*
* A `has-many` relation means that there are multiple related records matching
* the criteria set by this relation, e.g., a customer has many orders.
*
* For example, to declare the `orders` relation for `Customer` class, we can write
* the following code in the `Customer` class:
*
* ~~~
* public function getOrders()
* {
* return $this->hasMany('Order', array('customer_id' => 'id'));
* }
* ~~~
*
* Note that in the above, the 'customer_id' key in the `$link` parameter refers to
* an attribute name in the related class `Order`, while the 'id' value refers to
* an attribute name in the current AR class.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the columns in the table associated with the `$class` model, while the values of the
* array refer to the corresponding columns in the table associated with this AR class.
* @return ActiveRelation the relation object.
*/
public function hasMany($class, $link)
{
return new ActiveRelation(array(
'modelClass' => $this->getNamespacedClass($class),
'primaryModel' => $this,
'link' => $link,
'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;
}
/**
* 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.
* @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
......
......@@ -7,9 +7,8 @@
namespace yii\redis;
use yii\base\InvalidConfigException;
// TODO this class is nearly completely duplicated from yii\db\ActiveRelation
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation between two Active Record classes.
......@@ -26,76 +25,29 @@ use yii\base\InvalidConfigException;
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
/**
* @var boolean whether this relation should populate all query results into AR instances.
* If false, only the first row of the results will be retrieved.
*/
public $multiple;
/**
* @var ActiveRecord the primary model that this relation is associated with.
* This is used only in lazy loading with dynamic query options.
*/
public $primaryModel;
/**
* @var array the columns of the primary and foreign tables that establish the relation.
* The array keys must be columns of the table for this relation, and the array values
* must be the corresponding columns from the primary table.
* Do not prefix or quote the column names as this will be done automatically by Yii.
*/
public $link;
/**
* @var array|ActiveRelation the query associated with the pivot table. Please call [[via()]]
* or [[viaTable()]] to set this property instead of directly setting it.
*/
public $via;
/**
* Clones internal objects.
*/
public function __clone()
{
if (is_object($this->via)) {
// make a clone of "via" object so that the same query object can be reused multiple times
$this->via = clone $this->via;
}
}
use ActiveRelationTrait;
/**
* Specifies the relation associated with the pivot table.
* @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return ActiveRelation the relation object itself.
* Executes a script created by [[LuaScriptBuilder]]
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @param string $type the type of the script to generate
* @param null $column
* @return array|bool|null|string
*/
public function via($relationName, $callable = null)
{
$relation = $this->primaryModel->getRelation($relationName);
$this->via = array($relationName, $relation);
if ($callable !== null) {
call_user_func($callable, $relation);
}
return $this;
}
/**
* Creates a DB command that can be used to execute this query.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @return Command the created DB command instance.
*/
protected function executeScript($type, $column=null)
protected function executeScript($db, $type, $column=null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot table
$viaModels = $this->via->findPivotRows(array($this->primaryModel));
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var $viaQuery ActiveRelation */
/** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
......@@ -103,187 +55,13 @@ class ActiveRelation extends ActiveQuery
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? array() : array($model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels(array($this->primaryModel));
}
}
return parent::executeScript($type, $column);
}
/**
* Finds the related records and populates them into the primary models.
* This method is internally used by [[ActiveQuery]]. Do not call it directly.
* @param string $name the relation name
* @param array $primaryModels primary models
* @return array the related models
* @throws InvalidConfigException
*/
public function findWith($name, &$primaryModels)
{
if (!is_array($this->link)) {
throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
}
if ($this->via instanceof self) {
// via pivot table
/** @var $viaQuery ActiveRelation */
$viaQuery = $this->via;
$viaModels = $viaQuery->findPivotRows($primaryModels);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var $viaQuery ActiveRelation */
list($viaName, $viaQuery) = $this->via;
$viaQuery->primaryModel = null;
$viaModels = $viaQuery->findWith($viaName, $primaryModels);
$this->filterByModels($viaModels);
} else {
$this->filterByModels($primaryModels);
}
if (count($primaryModels) === 1 && !$this->multiple) {
$model = $this->one();
foreach ($primaryModels as $i => $primaryModel) {
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $model);
} else {
$primaryModels[$i][$name] = $model;
}
}
return array($model);
} else {
$models = $this->all();
if (isset($viaModels, $viaQuery)) {
$buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
} else {
$buckets = $this->buildBuckets($models, $this->link);
}
$link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
foreach ($primaryModels as $i => $primaryModel) {
$key = $this->getModelKey($primaryModel, $link);
$value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? array() : null);
if ($primaryModel instanceof ActiveRecord) {
$primaryModel->populateRelation($name, $value);
} else {
$primaryModels[$i][$name] = $value;
}
}
return $models;
}
}
/**
* @param array $models
* @param array $link
* @param array $viaModels
* @param array $viaLink
* @return array
*/
private function buildBuckets($models, $link, $viaModels = null, $viaLink = null)
{
$buckets = array();
$linkKeys = array_keys($link);
foreach ($models as $i => $model) {
$key = $this->getModelKey($model, $linkKeys);
if ($this->indexBy !== null) {
$buckets[$key][$i] = $model;
} else {
$buckets[$key][] = $model;
}
}
if ($viaModels !== null) {
$viaBuckets = array();
$viaLinkKeys = array_keys($viaLink);
$linkValues = array_values($link);
foreach ($viaModels as $viaModel) {
$key1 = $this->getModelKey($viaModel, $viaLinkKeys);
$key2 = $this->getModelKey($viaModel, $linkValues);
if (isset($buckets[$key2])) {
foreach ($buckets[$key2] as $i => $bucket) {
if ($this->indexBy !== null) {
$viaBuckets[$key1][$i] = $bucket;
} else {
$viaBuckets[$key1][] = $bucket;
}
}
}
}
$buckets = $viaBuckets;
}
if (!$this->multiple) {
foreach ($buckets as $i => $bucket) {
$buckets[$i] = reset($bucket);
$this->filterByModels([$this->primaryModel]);
}
}
return $buckets;
}
/**
* @param ActiveRecord|array $model
* @param array $attributes
* @return string
*/
private function getModelKey($model, $attributes)
{
if (count($attributes) > 1) {
$key = array();
foreach ($attributes as $attribute) {
$key[] = $model[$attribute];
}
return serialize($key);
} else {
$attribute = reset($attributes);
return $model[$attribute];
}
}
/**
* @param array $models
*/
private function filterByModels($models)
{
$attributes = array_keys($this->link);
$values = array();
if (count($attributes) === 1) {
// single key
$attribute = reset($this->link);
foreach ($models as $model) {
if (($value = $model[$attribute]) !== null) {
$values[] = $value;
}
}
} else {
// composite keys
foreach ($models as $model) {
$v = array();
foreach ($this->link as $attribute => $link) {
$v[$attribute] = $model[$link];
}
$values[] = $v;
}
}
$this->andWhere(array('in', $attributes, array_unique($values, SORT_REGULAR)));
}
/**
* @param ActiveRecord[] $primaryModels
* @return array
*/
private function findPivotRows($primaryModels)
{
if (empty($primaryModels)) {
return array();
}
$this->filterByModels($primaryModels);
/** @var $primaryModel ActiveRecord */
$primaryModel = reset($primaryModels);
$db = $primaryModel->getDb(); // TODO use different db in db overlapping relations
return $this->all();
return parent::executeScript($db, $type, $column);
}
}
......@@ -129,7 +129,7 @@ class LuaScriptBuilder extends \yii\base\Object
*/
private function build($query, $buildResult, $return)
{
$columns = array();
$columns = [];
if ($query->where !== null) {
$condition = $this->buildCondition($query->where, $columns);
} else {
......@@ -206,7 +206,7 @@ EOF;
*/
public function buildCondition($condition, &$columns)
{
static $builders = array(
static $builders = [
'and' => 'buildAndCondition',
'or' => 'buildAndCondition',
'between' => 'buildBetweenCondition',
......@@ -217,7 +217,7 @@ EOF;
'not like' => 'buildLikeCondition',
'or like' => 'buildLikeCondition',
'or not like' => 'buildLikeCondition',
);
];
if (!is_array($condition)) {
throw new NotSupportedException('Where must be an array.');
......@@ -238,10 +238,10 @@ EOF;
private function buildHashCondition($condition, &$columns)
{
$parts = array();
$parts = [];
foreach ($condition as $column => $value) {
if (is_array($value)) { // IN condition
$parts[] = $this->buildInCondition('in', array($column, $value), $columns);
$parts[] = $this->buildInCondition('in', [$column, $value], $columns);
} else {
$column = $this->addColumn($column, $columns);
if ($value === null) {
......@@ -259,7 +259,7 @@ EOF;
private function buildAndCondition($operator, $operands, &$columns)
{
$parts = array();
$parts = [];
foreach ($operands as $operand) {
if (is_array($operand)) {
$operand = $this->buildCondition($operand, $columns);
......@@ -299,7 +299,7 @@ EOF;
$values = (array)$values;
if (empty($values) || $column === array()) {
if (empty($values) || $column === []) {
return $operator === 'in' ? 'false' : 'true';
}
......@@ -309,7 +309,7 @@ EOF;
$column = reset($column);
}
$columnAlias = $this->addColumn($column, $columns);
$parts = array();
$parts = [];
foreach ($values as $i => $value) {
if (is_array($value)) {
$value = isset($value[$column]) ? $value[$column] : null;
......@@ -329,9 +329,9 @@ EOF;
protected function buildCompositeInCondition($operator, $inColumns, $values, &$columns)
{
$vss = array();
$vss = [];
foreach ($values as $value) {
$vs = array();
$vs = [];
foreach ($inColumns as $column) {
$column = $this->addColumn($column, $columns);
if (isset($value[$column])) {
......@@ -370,7 +370,7 @@ EOF;
$column = $this->addColumn($column, $columns);
$parts = array();
$parts = [];
foreach ($values as $value) {
// TODO implement matching here correctly
$value = $this->quoteValue($value);
......
......@@ -41,7 +41,7 @@ class RecordSchema extends TableSchema
throw new InvalidConfigException('primaryKey of RecordSchema must not be empty.');
}
if (!is_array($this->primaryKey)) {
$this->primaryKey = array($this->primaryKey);
$this->primaryKey = [$this->primaryKey];
}
foreach($this->primaryKey as $pk) {
if (!isset($this->columns[$pk])) {
......
......@@ -16,12 +16,12 @@ class Customer extends ActiveRecord
*/
public function getOrders()
{
return $this->hasMany('Order', array('customer_id' => 'id'));
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
}
public static function active($query)
{
$query->andWhere(array('status' => 1));
$query->andWhere(['status' => 1]);
}
public static function getRecordSchema()
......
......@@ -8,15 +8,15 @@ class Item extends ActiveRecord
{
public static function getRecordSchema()
{
return new RecordSchema(array(
return new RecordSchema([
'name' => 'item',
'primaryKey' => array('id'),
'primaryKey' => ['id'],
'sequenceName' => 'id',
'columns' => array(
'columns' => [
'id' => 'integer',
'name' => 'string',
'category_id' => 'integer'
)
));
]
]);
}
}
\ No newline at end of file
......@@ -8,17 +8,17 @@ class Order extends ActiveRecord
{
public function getCustomer()
{
return $this->hasOne('Customer', array('id' => 'customer_id'));
return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
}
public function getOrderItems()
{
return $this->hasMany('OrderItem', array('order_id' => 'id'));
return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
}
public function getItems()
{
return $this->hasMany('Item', array('id' => 'item_id'))
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItems', function($q) {
// additional query configuration
});
......@@ -26,9 +26,9 @@ class Order extends ActiveRecord
public function getBooks()
{
return $this->hasMany('Item', array('id' => 'item_id'))
->via('orderItems', array('order_id' => 'id'));
//->where(array('category_id' => 1));
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->via('orderItems', ['order_id' => 'id']);
//->where(['category_id' => 1]);
}
public function beforeSave($insert)
......@@ -46,7 +46,7 @@ class Order extends ActiveRecord
{
return new RecordSchema(array(
'name' => 'orders',
'primaryKey' => array('id'),
'primaryKey' => ['id'],
'columns' => array(
'id' => 'integer',
'customer_id' => 'integer',
......
......@@ -8,19 +8,19 @@ class OrderItem extends ActiveRecord
{
public function getOrder()
{
return $this->hasOne('Order', array('id' => 'order_id'));
return $this->hasOne(Order::className(), ['id' => 'order_id']);
}
public function getItem()
{
return $this->hasOne('Item', array('id' => 'item_id'));
return $this->hasOne(Item::className(), ['id' => 'item_id']);
}
public static function getRecordSchema()
{
return new RecordSchema(array(
'name' => 'order_item',
'primaryKey' => array('order_id', 'item_id'),
'primaryKey' => ['order_id', 'item_id'],
'columns' => array(
'order_id' => 'integer',
'item_id' => 'integer',
......
......@@ -10,6 +10,9 @@ use yiiunit\data\ar\redis\OrderItem;
use yiiunit\data\ar\redis\Order;
use yiiunit\data\ar\redis\Item;
/**
* @group redis
*/
class ActiveRecordTest extends RedisTestCase
{
public function setUp()
......@@ -18,61 +21,61 @@ class ActiveRecordTest extends RedisTestCase
ActiveRecord::$db = $this->getConnection();
$customer = new Customer();
$customer->setAttributes(array('email' => 'user1@example.com', 'name' => 'user1', 'address' => 'address1', 'status' => 1), false);
$customer->setAttributes(['email' => 'user1@example.com', 'name' => 'user1', 'address' => 'address1', 'status' => 1], false);
$customer->save(false);
$customer = new Customer();
$customer->setAttributes(array('email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => 1), false);
$customer->setAttributes(['email' => 'user2@example.com', 'name' => 'user2', 'address' => 'address2', 'status' => 1], false);
$customer->save(false);
$customer = new Customer();
$customer->setAttributes(array('email' => 'user3@example.com', 'name' => 'user3', 'address' => 'address3', 'status' => 2), false);
$customer->setAttributes(['email' => 'user3@example.com', 'name' => 'user3', 'address' => 'address3', 'status' => 2], false);
$customer->save(false);
// INSERT INTO tbl_category (name) VALUES ('Books');
// INSERT INTO tbl_category (name) VALUES ('Movies');
$item = new Item();
$item->setAttributes(array('name' => 'Agile Web Application Development with Yii1.1 and PHP5', 'category_id' => 1), false);
$item->setAttributes(['name' => 'Agile Web Application Development with Yii1.1 and PHP5', 'category_id' => 1], false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Yii 1.1 Application Development Cookbook', 'category_id' => 1), false);
$item->setAttributes(['name' => 'Yii 1.1 Application Development Cookbook', 'category_id' => 1], false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Ice Age', 'category_id' => 2), false);
$item->setAttributes(['name' => 'Ice Age', 'category_id' => 2], false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Toy Story', 'category_id' => 2), false);
$item->setAttributes(['name' => 'Toy Story', 'category_id' => 2], false);
$item->save(false);
$item = new Item();
$item->setAttributes(array('name' => 'Cars', 'category_id' => 2), false);
$item->setAttributes(['name' => 'Cars', 'category_id' => 2], false);
$item->save(false);
$order = new Order();
$order->setAttributes(array('customer_id' => 1, 'create_time' => 1325282384, 'total' => 110.0), false);
$order->setAttributes(['customer_id' => 1, 'create_time' => 1325282384, 'total' => 110.0], false);
$order->save(false);
$order = new Order();
$order->setAttributes(array('customer_id' => 2, 'create_time' => 1325334482, 'total' => 33.0), false);
$order->setAttributes(['customer_id' => 2, 'create_time' => 1325334482, 'total' => 33.0], false);
$order->save(false);
$order = new Order();
$order->setAttributes(array('customer_id' => 2, 'create_time' => 1325502201, 'total' => 40.0), false);
$order->setAttributes(['customer_id' => 2, 'create_time' => 1325502201, 'total' => 40.0], false);
$order->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0), false);
$orderItem->setAttributes(['order_id' => 1, 'item_id' => 1, 'quantity' => 1, 'subtotal' => 30.0], false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0), false);
$orderItem->setAttributes(['order_id' => 1, 'item_id' => 2, 'quantity' => 2, 'subtotal' => 40.0], false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0), false);
$orderItem->setAttributes(['order_id' => 2, 'item_id' => 4, 'quantity' => 1, 'subtotal' => 10.0], false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0), false);
$orderItem->setAttributes(['order_id' => 2, 'item_id' => 5, 'quantity' => 1, 'subtotal' => 15.0], false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0), false);
$orderItem->setAttributes(['order_id' => 2, 'item_id' => 3, 'quantity' => 1, 'subtotal' => 8.0], false);
$orderItem->save(false);
$orderItem = new OrderItem();
$orderItem->setAttributes(array('order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0), false);
$orderItem->setAttributes(['order_id' => 3, 'item_id' => 2, 'quantity' => 1, 'subtotal' => 40.0], false);
$orderItem->save(false);
}
......@@ -97,26 +100,26 @@ class ActiveRecordTest extends RedisTestCase
$this->assertEquals('user2', $customer->name);
$customer = Customer::find(5);
$this->assertNull($customer);
$customer = Customer::find(array('id' => array(5, 6, 1)));
$customer = Customer::find(['id' => [5, 6, 1]]);
$this->assertEquals(1, count($customer));
$customer = Customer::find()->where(array('id' => array(5, 6, 1)))->one();
$customer = Customer::find()->where(['id' => [5, 6, 1]])->one();
$this->assertNotNull($customer);
// query scalar
$customerName = Customer::find()->where(array('id' => 2))->scalar('name');
$customerName = Customer::find()->where(['id' => 2])->scalar('name');
$this->assertEquals('user2', $customerName);
// find by column values
$customer = Customer::find(array('id' => 2, 'name' => 'user2'));
$customer = Customer::find(['id' => 2, 'name' => 'user2']);
$this->assertTrue($customer instanceof Customer);
$this->assertEquals('user2', $customer->name);
$customer = Customer::find(array('id' => 2, 'name' => 'user1'));
$customer = Customer::find(['id' => 2, 'name' => 'user1']);
$this->assertNull($customer);
$customer = Customer::find(array('id' => 5));
$customer = Customer::find(['id' => 5]);
$this->assertNull($customer);
// find by attributes
$customer = Customer::find()->where(array('name' => 'user2'))->one();
$customer = Customer::find()->where(['name' => 'user2'])->one();
$this->assertTrue($customer instanceof Customer);
$this->assertEquals(2, $customer->id);
......@@ -131,7 +134,7 @@ class ActiveRecordTest extends RedisTestCase
$this->assertEquals(2, Customer::find()->active()->count());
// asArray
$customer = Customer::find()->where(array('id' => 2))->asArray()->one();
$customer = Customer::find()->where(['id' => 2])->asArray()->one();
$this->assertEquals(array(
'id' => '2',
'email' => 'user2@example.com',
......@@ -212,14 +215,14 @@ class ActiveRecordTest extends RedisTestCase
public function testFindComplexCondition()
{
$this->assertEquals(2, Customer::find()->where(array('OR', array('id' => 1), array('id' => 2)))->count());
$this->assertEquals(2, count(Customer::find()->where(array('OR', array('id' => 1), array('id' => 2)))->all()));
$this->assertEquals(2, Customer::find()->where(['OR', ['id' => 1], ['id' => 2]])->count());
$this->assertEquals(2, count(Customer::find()->where(['OR', ['id' => 1], ['id' => 2]])->all()));
$this->assertEquals(2, Customer::find()->where(array('id' => array(1,2)))->count());
$this->assertEquals(2, count(Customer::find()->where(array('id' => array(1,2)))->all()));
$this->assertEquals(2, Customer::find()->where(['id' => [1,2]])->count());
$this->assertEquals(2, count(Customer::find()->where(['id' => [1,2]])->all()));
$this->assertEquals(1, Customer::find()->where(array('AND', array('id' => array(2,3)), array('BETWEEN', 'status', 2, 4)))->count());
$this->assertEquals(1, count(Customer::find()->where(array('AND', array('id' => array(2,3)), array('BETWEEN', 'status', 2, 4)))->all()));
$this->assertEquals(1, Customer::find()->where(['AND', ['id' => [2,3]], ['BETWEEN', 'status', 2, 4]])->count());
$this->assertEquals(1, count(Customer::find()->where(['AND', ['id' => [2,3]], ['BETWEEN', 'status', 2, 4]])->all()));
}
public function testSum()
......@@ -230,14 +233,14 @@ class ActiveRecordTest extends RedisTestCase
public function testFindColumn()
{
$this->assertEquals(array('user1', 'user2', 'user3'), Customer::find()->column('name'));
// TODO $this->assertEquals(array('user3', 'user2', 'user1'), Customer::find()->orderBy(array('name' => Query::SORT_DESC))->column('name'));
$this->assertEquals(['user1', 'user2', 'user3'], Customer::find()->column('name'));
// TODO $this->assertEquals(['user3', 'user2', 'user1'], Customer::find()->orderBy(['name' => SORT_DESC])->column('name'));
}
public function testExists()
{
$this->assertTrue(Customer::find()->where(array('id' => 2))->exists());
$this->assertFalse(Customer::find()->where(array('id' => 5))->exists());
$this->assertTrue(Customer::find()->where(['id' => 2])->exists());
$this->assertFalse(Customer::find()->where(['id' => 5])->exists());
}
public function testFindLazy()
......@@ -247,7 +250,7 @@ class ActiveRecordTest extends RedisTestCase
$orders = $customer->orders;
$this->assertEquals(2, count($orders));
$orders = $customer->getOrders()->where(array('id' => 3))->all();
$orders = $customer->getOrders()->where(['id' => 3])->all();
$this->assertEquals(1, count($orders));
$this->assertEquals(3, $orders[0]->id);
}
......@@ -271,7 +274,7 @@ class ActiveRecordTest extends RedisTestCase
$order = Order::find(1);
$order->id = 100;
$this->assertEquals(array(), $order->items);
$this->assertEquals([], $order->items);
}
public function testFindEagerViaRelation()
......@@ -327,13 +330,13 @@ class ActiveRecordTest extends RedisTestCase
$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));
$orderItem = OrderItem::find(['order_id' => 1, 'item_id' => 3]);
$this->assertNull($orderItem);
$item = Item::find(3);
$order->link('items', $item, array('quantity' => 10, 'subtotal' => 100));
$order->link('items', $item, ['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));
$orderItem = OrderItem::find(['order_id' => 1, 'item_id' => 3]);
$this->assertTrue($orderItem instanceof OrderItem);
$this->assertEquals(10, $orderItem->quantity);
$this->assertEquals(100, $orderItem->subtotal);
......@@ -394,7 +397,7 @@ class ActiveRecordTest extends RedisTestCase
$this->assertEquals('user3', $customer->name);
$ret = Customer::updateAll(array(
'name' => 'temp',
), array('id' => 3));
), ['id' => 3]);
$this->assertEquals(1, $ret);
$customer = Customer::find(3);
$this->assertEquals('temp', $customer->name);
......@@ -403,17 +406,17 @@ class ActiveRecordTest extends RedisTestCase
public function testUpdateCounters()
{
// updateCounters
$pk = array('order_id' => 2, 'item_id' => 4);
$pk = ['order_id' => 2, 'item_id' => 4];
$orderItem = OrderItem::find($pk);
$this->assertEquals(1, $orderItem->quantity);
$ret = $orderItem->updateCounters(array('quantity' => -1));
$ret = $orderItem->updateCounters(['quantity' => -1]);
$this->assertTrue($ret);
$this->assertEquals(0, $orderItem->quantity);
$orderItem = OrderItem::find($pk);
$this->assertEquals(0, $orderItem->quantity);
// updateAllCounters
$pk = array('order_id' => 1, 'item_id' => 2);
$pk = ['order_id' => 1, 'item_id' => 2];
$orderItem = OrderItem::find($pk);
$this->assertEquals(2, $orderItem->quantity);
$ret = OrderItem::updateAllCounters(array(
......@@ -429,7 +432,7 @@ class ActiveRecordTest extends RedisTestCase
public function testUpdatePk()
{
// updateCounters
$pk = array('order_id' => 2, 'item_id' => 4);
$pk = ['order_id' => 2, 'item_id' => 4];
$orderItem = OrderItem::find($pk);
$this->assertEquals(2, $orderItem->order_id);
$this->assertEquals(4, $orderItem->item_id);
......@@ -439,7 +442,7 @@ class ActiveRecordTest extends RedisTestCase
$orderItem->save();
$this->assertNull(OrderItem::find($pk));
$this->assertNotNull(OrderItem::find(array('order_id' => 2, 'item_id' => 10)));
$this->assertNotNull(OrderItem::find(['order_id' => 2, 'item_id' => 10]));
}
public function testDelete()
......
......@@ -4,6 +4,9 @@ namespace yiiunit\framework\redis;
use yii\redis\Connection;
/**
* @group redis
*/
class RedisConnectionTest extends RedisTestCase
{
/**
......
......@@ -8,7 +8,7 @@ use yiiunit\TestCase;
/**
* RedisTestCase is the base class for all redis related test cases
*/
class RedisTestCase extends TestCase
abstract class RedisTestCase extends TestCase
{
protected function setUp()
{
......
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