diff --git a/framework/yii/redis/ActiveQuery.php b/framework/yii/redis/ActiveQuery.php index d16d944..ae6d9d1 100644 --- a/framework/yii/redis/ActiveQuery.php +++ b/framework/yii/redis/ActiveQuery.php @@ -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 * @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); } } diff --git a/framework/yii/redis/ActiveRecord.php b/framework/yii/redis/ActiveRecord.php index 2e4c1ad..acdb4cd 100644 --- a/framework/yii/redis/ActiveRecord.php +++ b/framework/yii/redis/ActiveRecord.php @@ -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]]. diff --git a/framework/yii/redis/ActiveRelation.php b/framework/yii/redis/ActiveRelation.php index ed730d5..b2f5cea 100644 --- a/framework/yii/redis/ActiveRelation.php +++ b/framework/yii/redis/ActiveRelation.php @@ -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 * @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); } } diff --git a/framework/yii/redis/LuaScriptBuilder.php b/framework/yii/redis/LuaScriptBuilder.php index c850002..7932a76 100644 --- a/framework/yii/redis/LuaScriptBuilder.php +++ b/framework/yii/redis/LuaScriptBuilder.php @@ -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); diff --git a/framework/yii/redis/RecordSchema.php b/framework/yii/redis/RecordSchema.php index de51b5d..e63f8d3 100644 --- a/framework/yii/redis/RecordSchema.php +++ b/framework/yii/redis/RecordSchema.php @@ -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])) { diff --git a/tests/unit/data/ar/redis/Customer.php b/tests/unit/data/ar/redis/Customer.php index 865c7db..9dfd98b 100644 --- a/tests/unit/data/ar/redis/Customer.php +++ b/tests/unit/data/ar/redis/Customer.php @@ -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() diff --git a/tests/unit/data/ar/redis/Item.php b/tests/unit/data/ar/redis/Item.php index 3d82a21..5e16c3c 100644 --- a/tests/unit/data/ar/redis/Item.php +++ b/tests/unit/data/ar/redis/Item.php @@ -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 diff --git a/tests/unit/data/ar/redis/Order.php b/tests/unit/data/ar/redis/Order.php index 4b20208..7ac6763 100644 --- a/tests/unit/data/ar/redis/Order.php +++ b/tests/unit/data/ar/redis/Order.php @@ -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', diff --git a/tests/unit/data/ar/redis/OrderItem.php b/tests/unit/data/ar/redis/OrderItem.php index 25863dc..32830bb 100644 --- a/tests/unit/data/ar/redis/OrderItem.php +++ b/tests/unit/data/ar/redis/OrderItem.php @@ -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', diff --git a/tests/unit/framework/redis/ActiveRecordTest.php b/tests/unit/framework/redis/ActiveRecordTest.php index 6d9efcd..31907f7 100644 --- a/tests/unit/framework/redis/ActiveRecordTest.php +++ b/tests/unit/framework/redis/ActiveRecordTest.php @@ -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() diff --git a/tests/unit/framework/redis/RedisConnectionTest.php b/tests/unit/framework/redis/RedisConnectionTest.php index a218899..af39e0e 100644 --- a/tests/unit/framework/redis/RedisConnectionTest.php +++ b/tests/unit/framework/redis/RedisConnectionTest.php @@ -4,6 +4,9 @@ namespace yiiunit\framework\redis; use yii\redis\Connection; +/** + * @group redis + */ class RedisConnectionTest extends RedisTestCase { /** diff --git a/tests/unit/framework/redis/RedisTestCase.php b/tests/unit/framework/redis/RedisTestCase.php index f8e23b2..12e539d 100644 --- a/tests/unit/framework/redis/RedisTestCase.php +++ b/tests/unit/framework/redis/RedisTestCase.php @@ -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() {