├── ActiveDataProvider.php ├── ActiveQuery.php ├── ActiveRecord.php ├── Cache.php ├── Connection.php ├── Exception.php ├── LICENSE ├── README.md ├── Response.php ├── SsdbSession.php ├── composer.json └── docs └── custom-sorting.md /ActiveDataProvider.php: -------------------------------------------------------------------------------- 1 | Post::find(), 29 | * 'pagination' => [ 30 | * 'pageSize' => 20, 31 | * ], 32 | * ]); 33 | * 34 | * // get the posts in the current page 35 | * $posts = $provider->getModels(); 36 | * ~~~ 37 | * 38 | * And the following example shows how to use ActiveDataProvider without ActiveRecord: 39 | * 40 | * ~~~ 41 | * $query = new Query; 42 | * $provider = new ActiveDataProvider([ 43 | * 'query' => $query->from('post'), 44 | * 'pagination' => [ 45 | * 'pageSize' => 20, 46 | * ], 47 | * ]); 48 | * 49 | * // get the posts in the current page 50 | * $posts = $provider->getModels(); 51 | * ~~~ 52 | * 53 | * @author Qiang Xue 54 | * @since 2.0 55 | */ 56 | class ActiveDataProvider extends BaseDataProvider 57 | { 58 | /** 59 | * @var QueryInterface the query that is used to fetch data models and [[totalCount]] 60 | * if it is not explicitly set. 61 | */ 62 | public $query; 63 | /** 64 | * @var string|callable the column that is used as the key of the data models. 65 | * This can be either a column name, or a callable that returns the key value of a given data model. 66 | * 67 | * If this is not set, the following rules will be used to determine the keys of the data models: 68 | * 69 | * - If [[query]] is an [[\yii\db\ActiveQuery]] instance, the primary keys of [[\yii\db\ActiveQuery::modelClass]] will be used. 70 | * - Otherwise, the keys of the [[models]] array will be used. 71 | * 72 | * @see getKeys() 73 | */ 74 | public $key; 75 | /** 76 | * @var Connection|array|string the DB connection object or the application component ID of the DB connection. 77 | * If not set, the default DB connection will be used. 78 | * Starting from version 2.0.2, this can also be a configuration array for creating the object. 79 | */ 80 | public $db; 81 | 82 | 83 | /** 84 | * Initializes the DB connection component. 85 | * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. 86 | * @throws InvalidConfigException if [[db]] is invalid. 87 | */ 88 | public function init() 89 | { 90 | parent::init(); 91 | if (is_string($this->db)) { 92 | $this->db = Instance::ensure($this->db, Connection::className()); 93 | } 94 | } 95 | 96 | /** 97 | * @inheritdoc 98 | */ 99 | protected function prepareModels() 100 | { 101 | if (!$this->query instanceof QueryInterface) { 102 | throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); 103 | } 104 | $query = clone $this->query; 105 | if (($pagination = $this->getPagination()) !== false) { 106 | $pagination->totalCount = $this->getTotalCount(); 107 | $query->limit($pagination->getLimit())->offset($pagination->getOffset()); 108 | } 109 | // @todo 110 | // if (($sort = $this->getSort()) !== false) { 111 | // $query->addOrderBy($sort->getOrders()); 112 | // } 113 | 114 | return $query->all($this->db); 115 | } 116 | 117 | /** 118 | * @inheritdoc 119 | */ 120 | protected function prepareKeys($models) 121 | { 122 | $keys = []; 123 | if ($this->key !== null) { 124 | foreach ($models as $model) { 125 | if (is_string($this->key)) { 126 | $keys[] = $model[$this->key]; 127 | } else { 128 | $keys[] = call_user_func($this->key, $model); 129 | } 130 | } 131 | 132 | return $keys; 133 | } elseif ($this->query instanceof ActiveQueryInterface) { 134 | /* @var $class \yii\db\ActiveRecord */ 135 | $class = $this->query->modelClass; 136 | $pks = $class::primaryKey(); 137 | if (count($pks) === 1) { 138 | $pk = $pks[0]; 139 | foreach ($models as $model) { 140 | $keys[] = $model[$pk]; 141 | } 142 | } else { 143 | foreach ($models as $model) { 144 | $kk = []; 145 | foreach ($pks as $pk) { 146 | $kk[$pk] = $model[$pk]; 147 | } 148 | $keys[] = $kk; 149 | } 150 | } 151 | 152 | return $keys; 153 | } else { 154 | return array_keys($models); 155 | } 156 | } 157 | 158 | /** 159 | * @inheritdoc 160 | */ 161 | protected function prepareTotalCount() 162 | { 163 | if (!$this->query instanceof QueryInterface) { 164 | throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); 165 | } 166 | $query = clone $this->query; 167 | return (int)$query->limit(-1)->offset(-1)->count('*', $this->db); 168 | } 169 | 170 | /** 171 | * @inheritdoc 172 | */ 173 | public function setSort($value) 174 | { 175 | parent::setSort($value); 176 | if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) { 177 | /* @var $model Model */ 178 | $model = new $this->query->modelClass; 179 | if (empty($sort->attributes)) { 180 | foreach ($model->attributes() as $attribute) { 181 | $sort->attributes[$attribute] = [ 182 | 'asc' => [$attribute => SORT_ASC], 183 | 'desc' => [$attribute => SORT_DESC], 184 | 'label' => $model->getAttributeLabel($attribute), 185 | ]; 186 | } 187 | } else { 188 | foreach ($sort->attributes as $attribute => $config) { 189 | if (!isset($config['label'])) { 190 | $sort->attributes[$attribute]['label'] = $model->getAttributeLabel($attribute); 191 | } 192 | } 193 | } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /ActiveQuery.php: -------------------------------------------------------------------------------- 1 | modelClass = $modelClass; 42 | parent::__construct($config); 43 | } 44 | 45 | /** 46 | * Initializes the object. 47 | * This method is called at the end of the constructor. The default implementation will trigger 48 | * an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end 49 | * to ensure triggering of the event. 50 | */ 51 | public function init() 52 | { 53 | parent::init(); 54 | $this->trigger(self::EVENT_INIT); 55 | } 56 | 57 | /** 58 | * Sets the [[asArray]] property. 59 | * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. 60 | * @return $this the query object itself 61 | */ 62 | public function asArray($value = true) 63 | { 64 | // TODO: Implement asArray() method. 65 | } 66 | 67 | /** 68 | * Sets the [[indexBy]] property. 69 | * @param string|callable $column the name of the column by which the query results should be indexed by. 70 | * This can also be a callable (e.g. anonymous function) that returns the index value based on the given 71 | * row or model data. The signature of the callable should be: 72 | * 73 | * ~~~ 74 | * // $model is an AR instance when `asArray` is false, 75 | * // or an array of column values when `asArray` is true. 76 | * function ($model) 77 | * { 78 | * // return the index value corresponding to $model 79 | * } 80 | * ~~~ 81 | * 82 | * @return $this the query object itself 83 | */ 84 | public function indexBy($column) 85 | { 86 | // TODO: Implement indexBy() method. 87 | } 88 | 89 | /** 90 | * Specifies the relations with which this query should be performed. 91 | * 92 | * The parameters to this method can be either one or multiple strings, or a single array 93 | * of relation names and the optional callbacks to customize the relations. 94 | * 95 | * A relation name can refer to a relation defined in [[ActiveQueryTrait::modelClass|modelClass]] 96 | * or a sub-relation that stands for a relation of a related record. 97 | * For example, `orders.address` means the `address` relation defined 98 | * in the model class corresponding to the `orders` relation. 99 | * 100 | * The following are some usage examples: 101 | * 102 | * ~~~ 103 | * // find customers together with their orders and country 104 | * Customer::find()->with('orders', 'country')->all(); 105 | * // find customers together with their orders and the orders' shipping address 106 | * Customer::find()->with('orders.address')->all(); 107 | * // find customers together with their country and orders of status 1 108 | * Customer::find()->with([ 109 | * 'orders' => function ($query) { 110 | * $query->andWhere('status = 1'); 111 | * }, 112 | * 'country', 113 | * ])->all(); 114 | * ~~~ 115 | * 116 | * @return $this the query object itself 117 | */ 118 | public function with() 119 | { 120 | // TODO: Implement with() method. 121 | } 122 | 123 | /** 124 | * Specifies the relation associated with the junction table for use in relational query. 125 | * @param string $relationName the relation name. This refers to a relation declared in the [[ActiveRelationTrait::primaryModel|primaryModel]] of the relation. 126 | * @param callable $callable a PHP callback for customizing the relation associated with the junction table. 127 | * Its signature should be `function($query)`, where `$query` is the query to be customized. 128 | * @return $this the relation object itself. 129 | */ 130 | public function via($relationName, callable $callable = null) 131 | { 132 | // TODO: Implement via() method. 133 | } 134 | 135 | /** 136 | * Finds the related records for the specified primary record. 137 | * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion. 138 | * @param string $name the relation name 139 | * @param ActiveRecordInterface $model the primary model 140 | * @return mixed the related record(s) 141 | */ 142 | public function findFor($name, $model) 143 | { 144 | // TODO: Implement findFor() method. 145 | } 146 | 147 | /** 148 | * Executes the query and returns all results as an array. 149 | * @param Connection $db the database connection used to execute the query. 150 | * If this parameter is not given, the `db` application component will be used. 151 | * @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned. 152 | */ 153 | public function all($db = null) 154 | { 155 | // TODO add support for orderBy 156 | $data = $this->executeScript($db, 'All'); 157 | $rows = []; 158 | foreach ($data as $dataRow) { 159 | $rows[] = $dataRow; 160 | } 161 | if (!empty($rows)) { 162 | $models = $this->createModels($rows); 163 | if (!empty($this->with)) { 164 | $this->findWith($this->with, $models); 165 | } 166 | if (!$this->asArray) { 167 | foreach ($models as $model) { 168 | $model->afterFind(); 169 | } 170 | } 171 | return $models; 172 | } else { 173 | return []; 174 | } 175 | } 176 | 177 | /** 178 | * Executes the query and returns a single row of result. 179 | * @param Connection $db the database connection used to execute the query. 180 | * If this parameter is not given, the `db` application component will be used. 181 | * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query 182 | * results in nothing. 183 | */ 184 | public function one($db = null) 185 | { 186 | // TODO add support for orderBy 187 | $row = $this->executeScript($db, 'One'); 188 | if (empty($row)) { 189 | return null; 190 | } 191 | if ($this->asArray) { 192 | $model = $row; 193 | } else { 194 | /* @var $class ActiveRecord */ 195 | $class = $this->modelClass; 196 | $model = $class::instantiate($row); 197 | $class = get_class($model); 198 | $class::populateRecord($model, $row); 199 | } 200 | if (!empty($this->with)) { 201 | $models = [$model]; 202 | $this->findWith($this->with, $models); 203 | $model = $models[0]; 204 | } 205 | if (!$this->asArray) { 206 | $model->afterFind(); 207 | } 208 | return $model; 209 | } 210 | 211 | /** 212 | * Returns the number of records. 213 | * @param string $q the COUNT expression. Defaults to '*'. 214 | * @param Connection $db the database connection used to execute the query. 215 | * If this parameter is not given, the `db` application component will be used. 216 | * @return integer number of records. 217 | */ 218 | public function count($q = '*', $db = null) 219 | { 220 | if (null === $this->orderBy) { 221 | /* @var $modelClass ActiveRecord */ 222 | $modelClass = $this->modelClass; 223 | if (null === $db) { 224 | $db = $modelClass::getDb(); 225 | } 226 | return $db->zsize($modelClass::keyPrefix()); 227 | } else { 228 | return $this->executeScript($db, 'Count'); 229 | } 230 | } 231 | 232 | /** 233 | * Returns a value indicating whether the query result contains any row of data. 234 | * @param Connection $db the database connection used to execute the query. 235 | * If this parameter is not given, the `db` application component will be used. 236 | * @return boolean whether the query result contains any row of data. 237 | */ 238 | public function exists($db = null) 239 | { 240 | return $this->one($db) !== null; 241 | } 242 | 243 | /** 244 | * Executes a script created by [[LuaScriptBuilder]] 245 | * @param Connection|null $db the database connection used to execute the query. 246 | * If this parameter is not given, the `db` application component will be used. 247 | * @param string $type the type of the script to generate 248 | * @param string $columnName 249 | * @throws NotSupportedException 250 | * @return array|bool|null|string 251 | */ 252 | protected function executeScript($db, $type, $columnName = null) 253 | { 254 | if ($this->primaryModel !== null) { 255 | // lazy loading 256 | if ($this->via instanceof self) { 257 | // via junction table 258 | $viaModels = $this->via->findJunctionRows([$this->primaryModel]); 259 | $this->filterByModels($viaModels); 260 | } elseif (is_array($this->via)) { 261 | // via relation 262 | /* @var $viaQuery ActiveQuery */ 263 | list($viaName, $viaQuery) = $this->via; 264 | if ($viaQuery->multiple) { 265 | $viaModels = $viaQuery->all(); 266 | $this->primaryModel->populateRelation($viaName, $viaModels); 267 | } else { 268 | $model = $viaQuery->one(); 269 | $this->primaryModel->populateRelation($viaName, $model); 270 | $viaModels = $model === null ? [] : [$model]; 271 | } 272 | $this->filterByModels($viaModels); 273 | } else { 274 | $this->filterByModels([$this->primaryModel]); 275 | } 276 | } 277 | /* @var $modelClass ActiveRecord */ 278 | $modelClass = $this->modelClass; 279 | if ($db === null) { 280 | $db = $modelClass::getDb(); 281 | } 282 | 283 | // find by primary key if possible. This is much faster than scanning all records 284 | if (is_array($this->where) && !isset($this->where[0]) && $modelClass::isPrimaryKey(array_keys($this->where))) { 285 | return $this->findByPk($db, $type, $columnName); 286 | } 287 | 288 | $method = 'build' . $type; 289 | return $this->$method($modelClass, $db); 290 | } 291 | 292 | /** 293 | * 所有数据 294 | * 295 | * @param ActiveRecord $modelClass 296 | * @param \wsl\ssdb\Connection $db 297 | * @return mixed 298 | */ 299 | protected function buildAll($modelClass, $db) 300 | { 301 | $rows = []; 302 | $pks = []; 303 | $keyPrefix = $modelClass::keyPrefix(); 304 | $offset = is_null($this->offset) ? 0 : $this->offset; 305 | $limit = is_null($this->limit) ? $db->zsize($keyPrefix) : $this->limit; 306 | if ($this->orderBy) { 307 | foreach ($this->orderBy as $field => $sort) { 308 | if (SORT_ASC === $sort) { 309 | $pks = $db->zrange($keyPrefix . ':f:' . $field, $offset, $limit); 310 | } elseif (SORT_DESC === $sort) { 311 | $pks = $db->zrrange($keyPrefix . ':f:' . $field, $offset, $limit); 312 | } 313 | break; 314 | } 315 | } else { 316 | $pks = $db->zrange($keyPrefix, $offset, $limit); 317 | } 318 | if ($pks) { 319 | foreach ($pks as $pk => $scope) { 320 | $rows[] = $db->hgetall($pk); 321 | } 322 | } 323 | 324 | return $rows; 325 | } 326 | 327 | /** 328 | * 单条数据 329 | * 330 | * @param ActiveRecord $modelClass 331 | * @param \wsl\ssdb\Connection $db 332 | * @return mixed 333 | */ 334 | protected function buildOne($modelClass, $db) 335 | { 336 | $row = []; 337 | $pks = []; 338 | $keyPrefix = $modelClass::keyPrefix(); 339 | if ($this->orderBy) { 340 | foreach ($this->orderBy as $field => $sort) { 341 | if (SORT_ASC === $sort) { 342 | $pks = $db->zrange($keyPrefix, 0, 1); 343 | } elseif (SORT_DESC === $sort) { 344 | $pks = $db->zrrange($keyPrefix, 0, 1); 345 | } 346 | break; 347 | } 348 | } else { 349 | $pks = $db->zrange($keyPrefix, 0, 1); 350 | } 351 | // @todo 352 | if ($pks) { 353 | foreach ($pks as $pkv => $scope) { 354 | $key = $modelClass::keyPrefix() . ':a:' . $modelClass::buildKey($pkv); 355 | $row = $db->hgetall($key); 356 | break; 357 | } 358 | } 359 | 360 | return $row; 361 | } 362 | 363 | /** 364 | * 所有数据 365 | * 366 | * @param ActiveRecord $modelClass 367 | * @param \wsl\ssdb\Connection $db 368 | * @return mixed 369 | */ 370 | protected function buildCount($modelClass, $db) 371 | { 372 | $count = 0; 373 | $keyPrefix = $modelClass::keyPrefix(); 374 | if ($this->orderBy) { 375 | foreach ($this->orderBy as $field => $sort) { 376 | if (SORT_ASC === $sort) { 377 | $count = $db->zsize($keyPrefix . ':f:' . $field); 378 | } elseif (SORT_DESC === $sort) { 379 | $count = $db->zsize($keyPrefix . ':f:' . $field); 380 | } 381 | break; 382 | } 383 | } else { 384 | $count = $db->zsize($keyPrefix); 385 | } 386 | 387 | return $count; 388 | } 389 | 390 | /** 391 | * Fetch by pk if possible as this is much faster 392 | * @param Connection $db the database connection used to execute the query. 393 | * If this parameter is not given, the `db` application component will be used. 394 | * @param string $type the type of the script to generate 395 | * @param string $columnName 396 | * @return array|bool|null|string 397 | * @throws \yii\base\InvalidParamException 398 | * @throws \yii\base\NotSupportedException 399 | */ 400 | private function findByPk($db, $type, $columnName = null) 401 | { 402 | if (count($this->where) == 1) { 403 | $pks = (array)reset($this->where); 404 | } else { 405 | foreach ($this->where as $values) { 406 | if (is_array($values)) { 407 | // TODO support composite IN for composite PK 408 | throw new NotSupportedException('Find by composite PK is not supported by redis ActiveRecord.'); 409 | } 410 | } 411 | $pks = [$this->where]; 412 | } 413 | /* @var $modelClass ActiveRecord */ 414 | $modelClass = $this->modelClass; 415 | if ($type == 'Count') { 416 | $start = 0; 417 | $limit = null; 418 | } else { 419 | $start = $this->offset === null ? 0 : $this->offset; 420 | $limit = $this->limit; 421 | } 422 | $i = 0; 423 | $data = []; 424 | foreach ($pks as $pk) { 425 | if (++$i > $start && ($limit === null || $i <= $start + $limit)) { 426 | $key = $modelClass::keyPrefix() . ':a:' . $modelClass::buildKey($pk); 427 | $result = $db->hgetall($key); 428 | if (!empty($result)) { 429 | $data[] = $result; 430 | if ($type === 'One' && $this->orderBy === null) { 431 | break; 432 | } 433 | } 434 | } 435 | } 436 | // TODO support orderBy 437 | switch ($type) { 438 | case 'All': 439 | return $data; 440 | case 'One': 441 | return reset($data); 442 | case 'Count': 443 | return count($data); 444 | case 'Column': 445 | $column = []; 446 | foreach ($data as $dataRow) { 447 | $row = []; 448 | $c = count($dataRow); 449 | for ($i = 0; $i < $c;) { 450 | $row[$dataRow[$i++]] = $dataRow[$i++]; 451 | } 452 | $column[] = $row[$columnName]; 453 | } 454 | return $column; 455 | case 'Sum': 456 | $sum = 0; 457 | foreach ($data as $dataRow) { 458 | $c = count($dataRow); 459 | for ($i = 0; $i < $c;) { 460 | if ($dataRow[$i++] == $columnName) { 461 | $sum += $dataRow[$i]; 462 | break; 463 | } 464 | } 465 | } 466 | return $sum; 467 | case 'Average': 468 | $sum = 0; 469 | $count = 0; 470 | foreach ($data as $dataRow) { 471 | $count++; 472 | $c = count($dataRow); 473 | for ($i = 0; $i < $c;) { 474 | if ($dataRow[$i++] == $columnName) { 475 | $sum += $dataRow[$i]; 476 | break; 477 | } 478 | } 479 | } 480 | return $sum / $count; 481 | case 'Min': 482 | $min = null; 483 | foreach ($data as $dataRow) { 484 | $c = count($dataRow); 485 | for ($i = 0; $i < $c;) { 486 | if ($dataRow[$i++] == $columnName && ($min == null || $dataRow[$i] < $min)) { 487 | $min = $dataRow[$i]; 488 | break; 489 | } 490 | } 491 | } 492 | return $min; 493 | case 'Max': 494 | $max = null; 495 | foreach ($data as $dataRow) { 496 | $c = count($dataRow); 497 | for ($i = 0; $i < $c;) { 498 | if ($dataRow[$i++] == $columnName && ($max == null || $dataRow[$i] > $max)) { 499 | $max = $dataRow[$i]; 500 | break; 501 | } 502 | } 503 | } 504 | return $max; 505 | } 506 | throw new InvalidParamException('Unknown fetch type: ' . $type); 507 | } 508 | 509 | /** 510 | * 排序参数 511 | * 512 | * @param string $fieldName 排序字段名 513 | * @param array $params 参数 514 | * @param int $sort 排序类型 515 | * @return $this 516 | */ 517 | public function orderParams($fieldName, $params, $sort = SORT_ASC) 518 | { 519 | /* @var $modelClass ActiveRecord */ 520 | $modelClass = $this->modelClass; 521 | $fields = ArrayHelper::getValue($modelClass::$sortFields, $fieldName); 522 | if (is_null($fields)) { 523 | throw new InvalidParamException('Unknown sort field name: ' . $fieldName); 524 | } 525 | foreach ($fields as $field) { 526 | if (!isset($params[$field])) { 527 | $params[$field] = 'all'; 528 | } 529 | } 530 | $this->orderBy(strtr(join('_', $fields), $params) . ' ' . $sort); 531 | 532 | return $this; 533 | } 534 | 535 | } -------------------------------------------------------------------------------- /ActiveRecord.php: -------------------------------------------------------------------------------- 1 | loadDefaultValues(); 41 | * ``` 42 | * 43 | * @param boolean $skipIfSet whether existing value should be preserved. 44 | * This will only set defaults for attributes that are `null`. 45 | * @return $this the model instance itself. 46 | */ 47 | public function loadDefaultValues($skipIfSet = true) 48 | { 49 | foreach ($this->getTableSchema()->columns as $column) { 50 | if ($column->defaultValue !== null && (!$skipIfSet || $this->{$column->name} === null)) { 51 | $this->{$column->name} = $column->defaultValue; 52 | } 53 | } 54 | return $this; 55 | } 56 | 57 | /** 58 | * Returns the connection used by this AR class. 59 | * @return Connection the database connection used by this AR class. 60 | */ 61 | public static function getDb() 62 | { 63 | return Yii::$app->get('ssdb'); 64 | } 65 | 66 | /** 67 | * Declares the name of the database table associated with this AR class. 68 | * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]] 69 | * with prefix [[Connection::tablePrefix]]. For example if [[Connection::tablePrefix]] is 'tbl_', 70 | * 'Customer' becomes 'tbl_customer', and 'OrderItem' becomes 'tbl_order_item'. You may override this method 71 | * if the table is not named after this convention. 72 | * @return string the table name 73 | * @throws NotSupportedException 74 | */ 75 | public static function tableName() 76 | { 77 | /** @var \yii\db\ActiveRecord $modelClass */ 78 | $modelClass = static::$modelClass; 79 | 80 | if ($modelClass) { 81 | return $modelClass::tableName(); 82 | } else { 83 | throw new NotSupportedException(__CLASS__); 84 | } 85 | } 86 | 87 | /** 88 | * Declares prefix of the key that represents the keys that store this records in redis. 89 | * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]. 90 | * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes 91 | * 'order_item'. You may override this method if you want different key naming. 92 | * @return string the prefix to apply to all AR keys 93 | */ 94 | public static function keyPrefix() 95 | { 96 | return Inflector::camel2id(StringHelper::basename(get_called_class()), '_'); 97 | } 98 | 99 | /** 100 | * Builds a normalized key from a given primary key value. 101 | * 102 | * @param mixed $key the key to be normalized 103 | * @return string the generated key 104 | */ 105 | public static function buildKey($key) 106 | { 107 | if (is_numeric($key)) { 108 | return $key; 109 | } elseif (is_string($key)) { 110 | return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key); 111 | } elseif (is_array($key)) { 112 | if (count($key) == 1) { 113 | return self::buildKey(reset($key)); 114 | } 115 | ksort($key); // ensure order is always the same 116 | $isNumeric = true; 117 | foreach ($key as $value) { 118 | if (!is_numeric($value)) { 119 | $isNumeric = false; 120 | } 121 | } 122 | if ($isNumeric) { 123 | return implode('-', $key); 124 | } 125 | } 126 | return md5(json_encode($key)); 127 | } 128 | 129 | /** 130 | * Returns the schema information of the DB table associated with this AR class. 131 | * @return TableSchema the schema information of the DB table associated with this AR class. 132 | * @throws InvalidConfigException if the table for the AR class does not exist. 133 | */ 134 | public static function getTableSchema() 135 | { 136 | /** @var \yii\db\ActiveRecord $modelClass */ 137 | $modelClass = static::$modelClass; 138 | 139 | if ($modelClass) { 140 | return $modelClass::getTableSchema(); 141 | } else { 142 | throw new InvalidConfigException("The table does not exist: " . static::tableName()); 143 | } 144 | } 145 | 146 | /** 147 | * @inheritDoc 148 | */ 149 | public function attributes() 150 | { 151 | return array_keys(static::getTableSchema()->columns); 152 | } 153 | 154 | /** 155 | * Returns the primary key **name(s)** for this AR class. 156 | * 157 | * Note that an array should be returned even when the record only has a single primary key. 158 | * 159 | * For the primary key **value** see [[getPrimaryKey()]] instead. 160 | * 161 | * @return string[] the primary key name(s) for this AR class. 162 | */ 163 | public static function primaryKey() 164 | { 165 | return static::getTableSchema()->primaryKey; 166 | } 167 | 168 | /** 169 | * @inheritdoc 170 | * @return ActiveQuery the newly created [[ActiveQuery]] instance. 171 | */ 172 | public static function find() 173 | { 174 | return Yii::createObject(ActiveQuery::className(), [get_called_class()]); 175 | } 176 | 177 | /** 178 | * @inheritdoc 179 | */ 180 | public static function populateRecord($record, $row) 181 | { 182 | $columns = static::getTableSchema()->columns; 183 | foreach ($row as $name => $value) { 184 | if (isset($columns[$name])) { 185 | $row[$name] = $columns[$name]->phpTypecast($value); 186 | } 187 | } 188 | parent::populateRecord($record, $row); 189 | } 190 | 191 | /** 192 | * Inserts the record into the database using the attribute values of this record. 193 | * 194 | * Usage example: 195 | * 196 | * ```php 197 | * $customer = new Customer; 198 | * $customer->name = $name; 199 | * $customer->email = $email; 200 | * $customer->insert(); 201 | * ``` 202 | * 203 | * @param boolean $runValidation whether to perform validation (calling [[validate()]]) 204 | * before saving the record. Defaults to `true`. If the validation fails, the record 205 | * will not be saved to the database and this method will return `false`. 206 | * @param array $attributes list of attributes that need to be saved. Defaults to null, 207 | * meaning all attributes that are loaded from DB will be saved. 208 | * @return boolean whether the attributes are valid and the record is inserted successfully. 209 | */ 210 | // public function insert($runValidation = true, $attributes = null) 211 | // { 212 | // if ($runValidation && !$this->validate($attributes)) { 213 | // Yii::info('Model not inserted due to validation error.', __METHOD__); 214 | // return false; 215 | // } 216 | // 217 | // return $this->insertInternal($attributes); 218 | // } 219 | 220 | /** 221 | * 排序规则 222 | * 223 | * @return array 224 | */ 225 | public function sortRules() 226 | { 227 | return []; 228 | } 229 | 230 | /** 231 | * Save data key 232 | * 233 | * @return string 234 | */ 235 | public function getKey() 236 | { 237 | return static::keyPrefix() . ':a:' . static::buildKey($this->primaryKey); 238 | } 239 | 240 | /** 241 | * Get index names 242 | * 243 | * @return array 244 | */ 245 | public function getIndexs() 246 | { 247 | $indexes = []; 248 | $keyPrefix = static::keyPrefix(); 249 | foreach ($this->sortRules() as $rule) { 250 | $index = ArrayHelper::getValue($rule, 'index'); 251 | $weight = ArrayHelper::getValue($rule, 'weight', time()); 252 | $isValid = ArrayHelper::getValue($rule, 'isValid', true); 253 | if (is_callable($isValid)) { 254 | $isValid = call_user_func($isValid); 255 | } 256 | if ($isValid) { 257 | if (is_callable($index)) { 258 | $index = call_user_func($index); 259 | } 260 | if (is_callable($weight)) { 261 | $weight = call_user_func($weight); 262 | } 263 | $names = is_array($index) ? $index : [$index]; 264 | foreach ($names as &$indexName) { 265 | $indexes[] = [ 266 | 'index' => $keyPrefix . ':f:' . $indexName, 267 | 'weight' => $weight, 268 | 'isValid' => $isValid, 269 | ]; 270 | } 271 | } 272 | } 273 | 274 | return $indexes; 275 | } 276 | 277 | /** 278 | * @inheritdoc 279 | */ 280 | public function insert($runValidation = true, $attributes = null) 281 | { 282 | if ($runValidation && !$this->validate($attributes)) { 283 | return false; 284 | } 285 | if (!$this->beforeSave(true)) { 286 | return false; 287 | } 288 | $db = static::getDb(); 289 | $values = $this->getDirtyAttributes($attributes); 290 | $keyPrefix = static::keyPrefix(); 291 | $pk = []; 292 | foreach ($this->primaryKey() as $key) { 293 | $pk[$key] = $values[$key] = $this->getAttribute($key); 294 | if ($pk[$key] === null) { 295 | // use auto increment if pk is null 296 | $pk[$key] = $values[$key] = $db->incr($keyPrefix . ':s:' . $key); 297 | $this->setAttribute($key, $values[$key]); 298 | } elseif (is_numeric($pk[$key])) { 299 | // if pk is numeric update auto increment value 300 | $currentPk = $db->get($keyPrefix . ':s:' . $key); 301 | if ($pk[$key] > $currentPk) { 302 | $db->set($keyPrefix . ':s:' . $key, $pk[$key]); 303 | } 304 | } 305 | } 306 | // save pk in a find all pool 307 | $key = $this->getKey(); 308 | $db->zset($keyPrefix, $key, ArrayHelper::getValue(array_values($pk), 0)); 309 | 310 | foreach ($this->getIndexs() as $indexData) { 311 | $db->zset($indexData['index'], $key, $indexData['weight']); 312 | $db->hset($keyPrefix . ':index', $indexData['index'], time()); 313 | } 314 | 315 | // save attributes 316 | foreach ($values as $attribute => $value) { 317 | if (is_bool($value)) { 318 | $value = (int)$value; 319 | } 320 | $db->hset($key, $attribute, $value); 321 | } 322 | $changedAttributes = array_fill_keys(array_keys($values), null); 323 | $this->setOldAttributes($values); 324 | $this->afterSave(true, $changedAttributes); 325 | return true; 326 | } 327 | 328 | /** 329 | * Deletes rows in the table using the provided conditions. 330 | * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. 331 | * 332 | * For example, to delete all customers whose status is 3: 333 | * 334 | * ~~~ 335 | * Customer::deleteAll(['status' => 3]); 336 | * ~~~ 337 | * 338 | * @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL. 339 | * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. 340 | * @return integer the number of rows deleted 341 | */ 342 | public static function deleteAll($condition = null) 343 | { 344 | $records = self::fetchPks($condition); 345 | if (empty($records)) { 346 | return 0; 347 | } 348 | $db = static::getDb(); 349 | $keyPrefix = static::keyPrefix(); 350 | 351 | if (is_null($condition)) { 352 | $attributeKeys = []; 353 | $indexKey = $keyPrefix . ':index'; 354 | $indexes = $db->hgetall($indexKey); 355 | foreach ($indexes as $key => $timestamp) { 356 | $db->zclear($key); 357 | } 358 | $db->hclear($indexKey); 359 | 360 | foreach ($records as $record) { 361 | $pkv = $keyPrefix . ':a:' . static::buildKey($record->primaryKey); 362 | $attributeKeys[] = $pkv; 363 | if (count($attributeKeys) > 1000) { 364 | $db->hclear($attributeKeys); 365 | $attributeKeys = []; 366 | } 367 | } 368 | $db->hclear($attributeKeys); 369 | $db->zclear($keyPrefix); 370 | } else { 371 | // 删除数据对应的索引列表数据 372 | foreach ($records as $record) { 373 | if (!is_null($condition)) { 374 | foreach ($record->getIndexs() as $indexData) { 375 | $db->zdel($indexData['index'], $record->getKey()); 376 | } 377 | } 378 | } 379 | } 380 | 381 | return true; 382 | } 383 | 384 | private static function fetchPks($condition) 385 | { 386 | $query = static::find(); 387 | $query->where($condition); 388 | return $query->all(); 389 | } 390 | 391 | /** 392 | * 获取字符的各种组合 393 | * 二位数组为限制自身数组下标允许改变的字符 394 | * 395 | * 示例: 396 | * ```php 397 | * $array = [1, 2, 3]; 398 | * print_r(comb($array)); 399 | * 400 | * // 输出内容: 401 | * // Array 402 | * // ( 403 | * // [0] => 1_2_3 404 | * // [1] => 1_2_all 405 | * // [2] => 1_all_3 406 | * // [3] => 1_all_all 407 | * // [4] => all_2_3 408 | * // [5] => all_2_all 409 | * // [6] => all_all_3 410 | * // [7] => all_all_all 411 | * // ) 412 | * ``` 413 | * 414 | * @param array $array 需要组合的数组 415 | * @param bool $isAddAll 添加all字符 416 | * @param string|null $delimiter 分隔符 默认不分割返回数组 417 | * @param string|null $prefix 前缀 418 | * @return array 419 | */ 420 | public static function comb(array $array, $isAddAll = true, $delimiter = '_', $prefix = null) 421 | { 422 | $count = 1; 423 | if ($isAddAll) { 424 | foreach ($array as &$_item) { 425 | if (!is_array($_item)) { 426 | $_item = ['all', $_item]; 427 | } 428 | } 429 | } 430 | foreach ($array as $item) { 431 | $count *= count($item); 432 | } 433 | 434 | $data = []; 435 | $repeatCount = $count; 436 | foreach ($array as $i => $values) { 437 | $data[$i] = []; 438 | $startIndex = 0; 439 | $fillCount = 0; 440 | $repeatCount = $repeatCount / count($values); 441 | do { 442 | foreach ($values as $j => $item) { 443 | $data[$i] = array_merge($data[$i], array_fill($startIndex, $repeatCount, $item)); 444 | $startIndex += $repeatCount; 445 | $fillCount += $repeatCount; 446 | } 447 | } while ($fillCount < $count); 448 | } 449 | 450 | $newData = []; 451 | foreach ($data as $i => $item) { 452 | foreach ($item as $k => $value) { 453 | if (is_null($delimiter) && !is_null($prefix) && 0 == $i) { 454 | $newData[$k][] = $prefix . $value; 455 | continue; 456 | } 457 | $newData[$k][] = $value; 458 | } 459 | } 460 | 461 | if (is_null($delimiter)) { 462 | return $newData; 463 | } else { 464 | $strings = []; 465 | foreach ($newData as $indexName) { 466 | if (is_null($prefix)) { 467 | $strings[] = join($delimiter, $indexName); 468 | } else { 469 | $strings[] = $prefix . join($delimiter, $indexName); 470 | } 471 | } 472 | 473 | return $strings; 474 | } 475 | } 476 | } -------------------------------------------------------------------------------- /Cache.php: -------------------------------------------------------------------------------- 1 | 6 | * @create_time 2015-06-14 12:24 7 | * 8 | * * To use Ssdb Cache as the cache application component, configure the application as follows, 9 | * 10 | * ~~~ 11 | * [ 12 | * 'components' => [ 13 | * 'cache' => [ 14 | * 'class' => 'wsl\ssdb\Cache', 15 | * 'redis' => [ 16 | * 'host' => 'localhost', 17 | * 'port' => 8888, 18 | * ] 19 | * ], 20 | * ], 21 | * ] 22 | * ~~~ 23 | * 24 | * Or if you have configured the Ssdb [[Connection]] as an application component, the following is sufficient: 25 | * 26 | * ~~~ 27 | * [ 28 | * 'components' => [ 29 | * 'cache' => [ 30 | * 'class' => 'wsl\ssdb\Cache', 31 | * // 'ssdb' => 'ssdb' // id of the connection application component 32 | * ], 33 | * ], 34 | * ] 35 | * ~~~ 36 | */ 37 | 38 | 39 | namespace wsl\ssdb; 40 | 41 | use Yii; 42 | use yii\base\InvalidConfigException; 43 | 44 | class Cache extends \yii\caching\Cache 45 | { 46 | /** 47 | * 48 | * @var Connection 49 | */ 50 | public $ssdb = 'ssdb'; 51 | 52 | /** 53 | * @var string 54 | */ 55 | public $cache_keys_hash = '_ssdb_cache_key_hash'; 56 | 57 | /** 58 | * @var bool 59 | */ 60 | public $is_unserialize = false; 61 | 62 | 63 | public function init() { 64 | parent::init(); 65 | 66 | if (is_string($this->ssdb)) { 67 | $this->ssdb = \Yii::$app->get($this->ssdb); 68 | } elseif (is_array($this->ssdb)) { 69 | if (!isset($this->ssdb['class'])) { 70 | $this->ssdb['class'] = Connection::className(); 71 | } 72 | $this->ssdb = \Yii::createObject($this->ssdb); 73 | } 74 | 75 | if (!$this->ssdb instanceof Connection) { 76 | throw new InvalidConfigException("Cache::ssdb must be either a Ssdb Connection instance or the application component ID of a ssdb Connection."); 77 | } 78 | 79 | if ($this->cache_keys_hash === "") { 80 | $this->$cache_keys_hash = substr(md5(Yii::$app->id), 0, 5) . "___"; 81 | } 82 | 83 | } 84 | 85 | public function getkeys() { 86 | return $this->ssdb->hkeys($this->cache_keys_hash, "", "", $this->ssdb->hsize($this->cache_keys_hash)); 87 | } 88 | 89 | /** 90 | * Retrieves a value from cache with a specified key. 91 | * This is the implementation of the method declared in the parent class. 92 | * @param string $key a unique key identifying the cached value 93 | * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. 94 | */ 95 | protected function getValue($key) { 96 | $data = $this->ssdb->get($key); 97 | return $this->is_unserialize ? unserialize($data) : $data; 98 | } 99 | 100 | /** 101 | * Stores a value identified by a key in cache. 102 | * This is the implementation of the method declared in the parent class. 103 | * @param string $key the key identifying the value to be cached 104 | * @param string $value the value to be cached 105 | * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. 106 | * @return boolean true if the value is successfully stored into cache, false otherwise 107 | */ 108 | public function setValue($key, $value, $expire) { 109 | $this->ssdb->hset($this->cache_keys_hash, $key, 1); 110 | $data = $this->is_unserialize ? serialize($value) : $value; 111 | if ($expire > 0) { 112 | //$expire += time(); 113 | return $this->ssdb->setx($key, $data, (int) $expire); 114 | } 115 | else { 116 | return $this->ssdb->set($key, $data); 117 | } 118 | } 119 | /** 120 | * Stores a value identified by a key into cache if the cache does not contain this key. 121 | * This is the implementation of the method declared in the parent class. 122 | * @param string $key the key identifying the value to be cached 123 | * @param string $value the value to be cached 124 | * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. 125 | * @return boolean true if the value is successfully stored into cache, false otherwise 126 | */ 127 | protected function addValue($key, $value, $expire) { 128 | return $this->setValue($key, $value, $expire); 129 | } 130 | /** 131 | * Deletes a value with the specified key from cache 132 | * This is the implementation of the method declared in the parent class. 133 | * @param string $key the key of the value to be deleted 134 | * @return boolean if no error happens during deletion 135 | */ 136 | public function deleteValue($key) { 137 | $this->ssdb->hdel($this->cache_keys_hash, $key); 138 | return $this->ssdb->del($key); 139 | } 140 | /** 141 | * @return boolean whether the flush operation was successful. 142 | */ 143 | protected function flushValues() { 144 | $this->ssdb->multi_del($this->getkeys()); 145 | return $this->ssdb->hclear($this->cache_keys_hash); 146 | } 147 | } -------------------------------------------------------------------------------- /Connection.php: -------------------------------------------------------------------------------- 1 | 6 | * @create_time 2015-06-02 14:29 7 | * 8 | * 9 | * 10 | */ 11 | 12 | namespace wsl\ssdb; 13 | 14 | use yii\base\Component; 15 | 16 | /** 17 | * Class Connection 18 | * 19 | * @package yii\SSDB 20 | * @method info() 21 | * @method dbsize() 22 | * @method ping() 23 | * @method qset(String $name, $index, $val) 24 | * @method getbit(String $key, Int $offset) 25 | * @method setbit(String $key, Int $offset, Int $val) 26 | * @method countbit(String $key, Int $start, Int $size) 27 | * @method strlen(String $key) 28 | * @method set(String $key, String $value) 29 | * @method setx(String $key, String $value, Int $ttl) 30 | * @method setnx(String $key, String $value) 31 | * @method zset(String $name, String $key, Int $score) 32 | * @method hset(String $name, String $key, String $value) 33 | * @method qpush(String $name, String $item) 34 | * @method qpush_front(String $name, String $item) 35 | * @method qpush_back(String $name, String $item) 36 | * @method qtrim_front(String $name, Int $size) 37 | * @method qtrim_back(String $name, Int $size) 38 | * @method del(String $key) 39 | * @method zdel(String $name, String $key) 40 | * @method hdel(String $name, String $key) 41 | * @method hsize(String $name) 42 | * @method zsize(String $name) 43 | * @method qsize(String $name) 44 | * @method hclear(String $name) 45 | * @method zclear(String $name) 46 | * @method qclear(String $name) 47 | * @method multi_del(Array $keys) 48 | * @method multi_hdel(String $name, Array $keys) 49 | * @method multi_zdel(String $name, Array $keys) 50 | * @method zget(String $name, String $key) 51 | * @method zrank(String $name, String $key) 52 | * @method zrrank(String $name, String $key) 53 | * @method zcount(String $name, Int $score_start, Int $score_end) 54 | * @method zsum(String $name, Int $score_start, Int $score_end) 55 | * @method zremrangebyrank(String $name, Int $start, Int $end) 56 | * @method zremrangebyscore(String $name, Int $start, Int $end) 57 | * @method ttl(String $key) 58 | * @method zavg(String $name, Int $score_start, Int $score_end) 59 | * @method get(String $key) 60 | * @method substr(String $key, Int $start, Int $size) 61 | * @method getset(String $key, String $value) 62 | * @method hget(String $name, String $key) 63 | * @method qget(String $name, Int $index) 64 | * @method qfront(String $name) 65 | * @method qback(String $name) 66 | * @method qpop(String $name, Int $size) 67 | * @method qpop_front(String $name, Int $size) 68 | * @method qpop_back(String $name, Int $size) 69 | * @method keys(String $key_start, String $key_end, Int $limit) 70 | * @method zkeys(String $name, String $key_start, Int $score_start, Int $score_end, Int $limit) 71 | * @method hkeys(String $name, String $key_start, String $key_end, Int $limit) 72 | * @method hlist(Int $name_start, Int $name_end, Int $limit) 73 | * @method zlist(Int $name_start, Int $name_end, Int $limit) 74 | * @method zrlist(Int $name_start, Int $name_end, Int $limit) 75 | * @method qslice(String $name, Int $begin, Int $end) 76 | * @method exists(String $key) 77 | * @method hexists(String $name, String $key) 78 | * @method zexists(String $name, String $key) 79 | * @method multi_exists 80 | * @method multi_hexists 81 | * @method multi_zexists 82 | * @method scan(String $key_start, String $key_end, Int $limit) 83 | * @method rscan(String $key_start, String $key_end, Int $limit) 84 | * @method zscan(String $name, String $key_start, Int $score_start, Int $score_end, Int $limit) 85 | * @method zrscan(String $name, String $key_start, Int $score_start, Int $score_end, Int $limit) 86 | * @method zrange(String $name, Int $offset, Int $limit) 87 | * @method zrrange(String $name, Int $offset, Int $limit) 88 | * @method hscan(String $name, String $key_start, String $key_end, Int $limit) 89 | * @method hrscan(String $name, String $key_start, String $key_end, Int $limit) 90 | * @method hgetall(String $name) 91 | * @method multi_hsize 92 | * @method multi_zsize 93 | * @method multi_get(Array $keys) 94 | * @method multi_hget(String $name, Array $keys) 95 | * @method multi_zget(String $name, Array $keys) 96 | * @method zpop_front(string $name, integer $limit) 97 | * @method zpop_back(string $name, integer $limit) 98 | */ 99 | class Connection extends Component 100 | { 101 | private $debug = false; 102 | public $sock = null; 103 | private $_closed = false; 104 | private $recv_buf = ''; 105 | public $last_resp = null; 106 | 107 | public $host = '127.0.0.1'; 108 | public $port = 8888; 109 | public $timeout_ms=2000; 110 | public $easy = true; 111 | 112 | 113 | public function init() 114 | { 115 | parent::init(); 116 | 117 | $timeout_f = (float)$this->timeout_ms/1000; 118 | $this->sock = @stream_socket_client("$this->host:$this->port", $errno, $errstr, $timeout_f); 119 | if(!$this->sock){ 120 | throw new Exception("$errno: $errstr"); 121 | } 122 | $timeout_sec = intval($this->timeout_ms/1000); 123 | $timeout_usec = ($this->timeout_ms - $timeout_sec * 1000) * 1000; 124 | @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec); 125 | if(function_exists('stream_set_chunk_size')){ 126 | @stream_set_chunk_size($this->sock, 1024 * 1024); 127 | } 128 | 129 | if (true === $this->easy) { 130 | $this->easy(); 131 | } 132 | } 133 | 134 | /** 135 | * After this method invoked with yesno=true, all requesting methods 136 | * will not return a Response object. 137 | * And some certain methods like get/zget will return false 138 | * when response is not ok(not_found, etc) 139 | */ 140 | public function easy(){ 141 | $this->easy = true; 142 | } 143 | 144 | public function close(){ 145 | if(!$this->_closed){ 146 | @fclose($this->sock); 147 | $this->_closed = true; 148 | $this->sock = null; 149 | } 150 | } 151 | 152 | public function closed(){ 153 | return $this->_closed; 154 | } 155 | 156 | private $batch_mode = false; 157 | private $batch_cmds = array(); 158 | 159 | public function batch(){ 160 | $this->batch_mode = true; 161 | $this->batch_cmds = array(); 162 | return $this; 163 | } 164 | 165 | public function multi(){ 166 | return $this->batch(); 167 | } 168 | 169 | public function exec(){ 170 | $ret = array(); 171 | foreach($this->batch_cmds as $op){ 172 | list($cmd, $params) = $op; 173 | $this->send_req($cmd, $params); 174 | } 175 | foreach($this->batch_cmds as $op){ 176 | list($cmd, $params) = $op; 177 | $resp = $this->recv_resp($cmd, $params); 178 | $resp = $this->check_easy_resp($cmd, $resp); 179 | $ret[] = $resp; 180 | } 181 | $this->batch_mode = false; 182 | $this->batch_cmds = array(); 183 | return $ret; 184 | } 185 | 186 | public function request(){ 187 | $args = func_get_args(); 188 | $cmd = array_shift($args); 189 | return $this->__call($cmd, $args); 190 | } 191 | 192 | private $async_auth_password = null; 193 | 194 | public function auth($password){ 195 | $this->async_auth_password = $password; 196 | return null; 197 | } 198 | 199 | public function __call($cmd, $params=array()){ 200 | $cmd = strtolower($cmd); 201 | if($this->async_auth_password !== null){ 202 | $pass = $this->async_auth_password; 203 | $this->async_auth_password = null; 204 | $auth = $this->__call('auth', array($pass)); 205 | if($auth->data !== true){ 206 | throw new \Exception("Authentication failed"); 207 | } 208 | } 209 | 210 | if($this->batch_mode){ 211 | $this->batch_cmds[] = array($cmd, $params); 212 | return $this; 213 | } 214 | 215 | try{ 216 | if($this->send_req($cmd, $params) === false){ 217 | $resp = new Response('error', 'send error'); 218 | }else{ 219 | $resp = $this->recv_resp($cmd, $params); 220 | } 221 | }catch(Exception $e){ 222 | if($this->easy){ 223 | throw $e; 224 | }else{ 225 | $resp = new Response('error', $e->getMessage()); 226 | } 227 | } 228 | 229 | if($resp->code == 'noauth'){ 230 | $msg = $resp->message; 231 | throw new \Exception($msg); 232 | } 233 | 234 | $resp = $this->check_easy_resp($cmd, $resp); 235 | return $resp; 236 | } 237 | 238 | private function check_easy_resp($cmd, $resp){ 239 | $this->last_resp = $resp; 240 | if($this->easy){ 241 | if($resp->not_found()){ 242 | return NULL; 243 | }else if(!$resp->ok() && !is_array($resp->data)){ 244 | return false; 245 | }else{ 246 | return $resp->data; 247 | } 248 | }else{ 249 | $resp->cmd = $cmd; 250 | return $resp; 251 | } 252 | } 253 | 254 | public function multi_set($kvs=array()){ 255 | $args = array(); 256 | foreach($kvs as $k=>$v){ 257 | $args[] = $k; 258 | $args[] = $v; 259 | } 260 | return $this->__call(__FUNCTION__, $args); 261 | } 262 | 263 | public function multi_hset($name, $kvs=array()){ 264 | $args = array($name); 265 | foreach($kvs as $k=>$v){ 266 | $args[] = $k; 267 | $args[] = $v; 268 | } 269 | return $this->__call(__FUNCTION__, $args); 270 | } 271 | 272 | public function multi_zset($name, $kvs=array()){ 273 | $args = array($name); 274 | foreach($kvs as $k=>$v){ 275 | $args[] = $k; 276 | $args[] = $v; 277 | } 278 | return $this->__call(__FUNCTION__, $args); 279 | } 280 | 281 | public function incr($key, $val=1){ 282 | $args = func_get_args(); 283 | return $this->__call(__FUNCTION__, $args); 284 | } 285 | 286 | public function decr($key, $val=1){ 287 | $args = func_get_args(); 288 | return $this->__call(__FUNCTION__, $args); 289 | } 290 | 291 | public function zincr($name, $key, $score=1){ 292 | $args = func_get_args(); 293 | return $this->__call(__FUNCTION__, $args); 294 | } 295 | 296 | public function zdecr($name, $key, $score=1){ 297 | $args = func_get_args(); 298 | return $this->__call(__FUNCTION__, $args); 299 | } 300 | 301 | public function zadd($key, $score, $value){ 302 | $args = array($key, $value, $score); 303 | return $this->__call('zset', $args); 304 | } 305 | 306 | public function zRevRank($name, $key){ 307 | $args = func_get_args(); 308 | return $this->__call("zrrank", $args); 309 | } 310 | 311 | public function zRevRange($name, $offset, $limit){ 312 | $args = func_get_args(); 313 | return $this->__call("zrrange", $args); 314 | } 315 | 316 | public function hincr($name, $key, $val=1){ 317 | $args = func_get_args(); 318 | return $this->__call(__FUNCTION__, $args); 319 | } 320 | 321 | public function hdecr($name, $key, $val=1){ 322 | $args = func_get_args(); 323 | return $this->__call(__FUNCTION__, $args); 324 | } 325 | 326 | private function send_req($cmd, $params){ 327 | $req = array($cmd); 328 | foreach($params as $p){ 329 | if(is_array($p)){ 330 | $req = array_merge($req, $p); 331 | }else{ 332 | $req[] = $p; 333 | } 334 | } 335 | return $this->send($req); 336 | } 337 | 338 | private function recv_resp($cmd, $params){ 339 | $resp = $this->recv(); 340 | if($resp === false){ 341 | return new Response('error', 'Unknown error'); 342 | }else if(!$resp){ 343 | return new Response('disconnected', 'Connection closed'); 344 | } 345 | if($resp[0] == 'noauth'){ 346 | $errmsg = isset($resp[1])? $resp[1] : ''; 347 | return new Response($resp[0], $errmsg); 348 | } 349 | switch($cmd){ 350 | case 'ping': 351 | case 'qset': 352 | case 'getbit': 353 | case 'setbit': 354 | case 'countbit': 355 | case 'strlen': 356 | case 'set': 357 | case 'setx': 358 | case 'setnx': 359 | case 'zset': 360 | case 'hset': 361 | case 'qpush': 362 | case 'qpush_front': 363 | case 'qpush_back': 364 | case 'qtrim_front': 365 | case 'qtrim_back': 366 | case 'del': 367 | case 'zdel': 368 | case 'hdel': 369 | case 'hsize': 370 | case 'zsize': 371 | case 'qsize': 372 | case 'hclear': 373 | case 'zclear': 374 | case 'qclear': 375 | case 'multi_set': 376 | case 'multi_del': 377 | case 'multi_hset': 378 | case 'multi_hdel': 379 | case 'multi_zset': 380 | case 'multi_zdel': 381 | case 'incr': 382 | case 'decr': 383 | case 'zincr': 384 | case 'zdecr': 385 | case 'hincr': 386 | case 'hdecr': 387 | case 'zget': 388 | case 'zrank': 389 | case 'zrrank': 390 | case 'zcount': 391 | case 'zsum': 392 | case 'zremrangebyrank': 393 | case 'zremrangebyscore': 394 | if($resp[0] == 'ok'){ 395 | $val = isset($resp[1])? intval($resp[1]) : 0; 396 | return new Response($resp[0], $val); 397 | }else{ 398 | $errmsg = isset($resp[1])? $resp[1] : ''; 399 | return new Response($resp[0], $errmsg); 400 | } 401 | case 'zavg': 402 | if($resp[0] == 'ok'){ 403 | $val = isset($resp[1])? floatval($resp[1]) : (float)0; 404 | return new Response($resp[0], $val); 405 | }else{ 406 | $errmsg = isset($resp[1])? $resp[1] : ''; 407 | return new Response($resp[0], $errmsg); 408 | } 409 | case 'get': 410 | case 'substr': 411 | case 'getset': 412 | case 'hget': 413 | case 'qget': 414 | case 'qfront': 415 | case 'qback': 416 | if($resp[0] == 'ok'){ 417 | if(count($resp) == 2){ 418 | return new Response('ok', $resp[1]); 419 | }else{ 420 | return new Response('server_error', 'Invalid response'); 421 | } 422 | }else{ 423 | $errmsg = isset($resp[1])? $resp[1] : ''; 424 | return new Response($resp[0], $errmsg); 425 | } 426 | break; 427 | case 'qpop': 428 | case 'qpop_front': 429 | case 'qpop_back': 430 | if($resp[0] == 'ok'){ 431 | $size = 1; 432 | if(isset($params[1])){ 433 | $size = intval($params[1]); 434 | } 435 | if($size <= 1){ 436 | if(count($resp) == 2){ 437 | return new Response('ok', $resp[1]); 438 | }else{ 439 | return new Response('server_error', 'Invalid response'); 440 | } 441 | }else{ 442 | $data = array_slice($resp, 1); 443 | return new Response('ok', $data); 444 | } 445 | }else{ 446 | $errmsg = isset($resp[1])? $resp[1] : ''; 447 | return new Response($resp[0], $errmsg); 448 | } 449 | break; 450 | case 'keys': 451 | case 'zkeys': 452 | case 'hkeys': 453 | case 'hlist': 454 | case 'zlist': 455 | case 'qslice': 456 | if($resp[0] == 'ok'){ 457 | $data = array(); 458 | if($resp[0] == 'ok'){ 459 | $data = array_slice($resp, 1); 460 | } 461 | return new Response($resp[0], $data); 462 | }else{ 463 | $errmsg = isset($resp[1])? $resp[1] : ''; 464 | return new Response($resp[0], $errmsg); 465 | } 466 | case 'auth': 467 | case 'exists': 468 | case 'hexists': 469 | case 'zexists': 470 | if($resp[0] == 'ok'){ 471 | if(count($resp) == 2){ 472 | return new Response('ok', (bool)$resp[1]); 473 | }else{ 474 | return new Response('server_error', 'Invalid response'); 475 | } 476 | }else{ 477 | $errmsg = isset($resp[1])? $resp[1] : ''; 478 | return new Response($resp[0], $errmsg); 479 | } 480 | break; 481 | case 'multi_exists': 482 | case 'multi_hexists': 483 | case 'multi_zexists': 484 | if($resp[0] == 'ok'){ 485 | if(count($resp) % 2 == 1){ 486 | $data = array(); 487 | for($i=1; $idebug){ 546 | echo '> ' . str_replace(array("\r", "\n"), array('\r', '\n'), $s) . "\n"; 547 | } 548 | try{ 549 | while(true){ 550 | $ret = @fwrite($this->sock, $s); 551 | if($ret == false){ 552 | $this->close(); 553 | throw new Exception('Connection lost'); 554 | } 555 | $s = substr($s, $ret); 556 | if(strlen($s) == 0){ 557 | break; 558 | } 559 | @fflush($this->sock); 560 | } 561 | }catch(\Exception $e){ 562 | $this->close(); 563 | throw new Exception($e->getMessage()); 564 | } 565 | return $ret; 566 | } 567 | 568 | private function recv(){ 569 | $this->step = self::STEP_SIZE; 570 | while(true){ 571 | $ret = $this->parse(); 572 | if($ret === null){ 573 | try{ 574 | $data = @fread($this->sock, 1024 * 1024); 575 | if($this->debug){ 576 | echo '< ' . str_replace(array("\r", "\n"), array('\r', '\n'), $data) . "\n"; 577 | } 578 | }catch(\Exception $e){ 579 | $data = ''; 580 | } 581 | if($data === false || $data === ''){ 582 | $this->close(); 583 | throw new Exception('Connection lost'); 584 | } 585 | $this->recv_buf .= $data; 586 | # echo "read " . strlen($data) . " total: " . strlen($this->recv_buf) . "\n"; 587 | }else{ 588 | return $ret; 589 | } 590 | } 591 | } 592 | 593 | const STEP_SIZE = 0; 594 | const STEP_DATA = 1; 595 | public $resp = array(); 596 | public $step; 597 | public $block_size; 598 | 599 | private function parse(){ 600 | $spos = 0; 601 | $epos = 0; 602 | $buf_size = strlen($this->recv_buf); 603 | // performance issue for large reponse 604 | //$this->recv_buf = ltrim($this->recv_buf); 605 | while(true){ 606 | $spos = $epos; 607 | if($this->step === self::STEP_SIZE){ 608 | $epos = strpos($this->recv_buf, "\n", $spos); 609 | if($epos === false){ 610 | break; 611 | } 612 | $epos += 1; 613 | $line = substr($this->recv_buf, $spos, $epos - $spos); 614 | $spos = $epos; 615 | 616 | $line = trim($line); 617 | if(strlen($line) == 0){ // head end 618 | $this->recv_buf = substr($this->recv_buf, $spos); 619 | $ret = $this->resp; 620 | $this->resp = array(); 621 | return $ret; 622 | } 623 | $this->block_size = intval($line); 624 | $this->step = self::STEP_DATA; 625 | } 626 | if($this->step === self::STEP_DATA){ 627 | $epos = $spos + $this->block_size; 628 | if($epos <= $buf_size){ 629 | $n = strpos($this->recv_buf, "\n", $epos); 630 | if($n !== false){ 631 | $data = substr($this->recv_buf, $spos, $epos - $spos); 632 | $this->resp[] = $data; 633 | $epos = $n + 1; 634 | $this->step = self::STEP_SIZE; 635 | continue; 636 | } 637 | } 638 | break; 639 | } 640 | } 641 | 642 | // packet not ready 643 | if($spos > 0){ 644 | $this->recv_buf = substr($this->recv_buf, $spos); 645 | } 646 | return null; 647 | } 648 | } 649 | -------------------------------------------------------------------------------- /Exception.php: -------------------------------------------------------------------------------- 1 | 6 | * @create_time 2015-06-02 14:29 7 | */ 8 | 9 | namespace wsl\ssdb; 10 | 11 | class Exception extends \Exception 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Shanli Wei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 SSDB 2 | ========= 3 | 4 | 实现了 Active Record、Active Query 5 | 6 | > Yii2 SSDB GII 扩展开发中... 7 | 8 | github: https://github.com/myweishanli/yii2-ssdb 9 | 10 | [![Latest Stable Version](https://poser.pugx.org/myweishanli/yii2-ssdb/v/stable.png)](https://packagist.org/packages/myweishanli/yii2-ssdb) 11 | [![Total Downloads](https://poser.pugx.org/myweishanli/yii2-ssdb/downloads.png)](https://packagist.org/packages/myweishanli/yii2-ssdb) 12 | 13 | > 注: 功能正在开发中... 14 | 15 | > 更详细的配置说明文档正在编写中... 16 | 17 | > QQ群: 137158108 验证信息: github 18 | 19 | > 有任何疑问可以发邮件到 myweishanli@gmail.com 20 | 21 | 22 | 安装 23 | ------------ 24 | 25 | 安装这个扩展的首选方式是通过 [composer](http://getcomposer.org/download/). 26 | 27 | 执行 28 | 29 | ``` 30 | composer require myweishanli/yii2-ssdb:dev-master 31 | ``` 32 | 或添加 33 | 34 | ``` 35 | "myweishanli/yii2-ssdb": "dev-master" 36 | ``` 37 | 38 | 39 | 配置 40 | ------------ 41 | 42 | 高级版是`common/config/main-local.php` 43 | 44 | 基础版是`config/web.php` 45 | 46 | ```php 47 | 'components' => [ 48 | // ... 49 | 'ssdb' => [ 50 | 'class' => 'wsl\ssdb\Connection', 51 | 'host' => 'localhost', 52 | 'port' => 8888, 53 | ], 54 | ], 55 | ``` 56 | 57 | 创建数据模型 58 | ------------ 59 | 60 | `common/models/ssdb/User.php` 61 | ```php 62 | /** 63 | * This is the ActiveRecord class for [[\common\models\User]]. 64 | * 65 | * @property string $user_id 66 | * @property string $name 67 | * @property integer $age 68 | * @property integer $status 69 | */ 70 | class User extends \wsl\ssdb\ActiveRecord 71 | { 72 | public static $modelClass = '\common\models\User'; 73 | } 74 | ``` 75 | 76 | 77 | Active Record、Active Query使用说明 78 | ------------ 79 | 80 | > 默认只能使用单个主键排序 更多排序查询[自定义排序规则](docs/custom-sorting.md) 81 | 82 | > 实际项目可能需求非常复杂,如果下方例子不能满足你的要求可以加QQ群探讨 83 | 84 | ### 新增或者替换数据 85 | 86 | ```php 87 | $userModel = new User(); 88 | $userModel->user_id = 1000000; 89 | $userModel->name = '张三'; 90 | $userModel->age = 19; 91 | $userModel->status = 0; 92 | $userModel->save(); 93 | ``` 94 | 95 | ### 获取一条数据 96 | 97 | ```php 98 | $model = User::find()->one(); 99 | ``` 100 | 101 | ### 获取一条数据 排序 102 | 103 | ```php 104 | $model = User::find()->orderBy('user_id asc')->one(); 105 | ``` 106 | 107 | ### 删除全部 108 | 109 | ```php 110 | User::deleteAll(); 111 | ``` 112 | 113 | ### 获取一条数据 条件查询 114 | 115 | ```php 116 | $model = User::find()->andWhere(['user_id' => 1000000])->one(); 117 | ``` 118 | 119 | ### 获取一条数据 多条件查询 120 | 121 | ```php 122 | $model = User::find()->andWhere(['user_id' => 1000000, 'age' => 19])->one(); 123 | ``` 124 | 125 | ### 获取所有数据列表 126 | 127 | ```php 128 | $models = User::find()->all(); 129 | ``` 130 | 131 | ### 获取所有数据列表 排序 132 | 133 | ```php 134 | $models = User::find()->orderBy('age desc')->all(); 135 | ``` 136 | 137 | ### 获取所有数据列表 条件查询 138 | 139 | ```php 140 | $models = User::find()->andWhere(['user_id' => 1000000])->all(); 141 | ``` 142 | 143 | ### 偏移数据和限定数据返回条数 144 | 145 | ```php 146 | $models = User::find()->offset(1)->limit(1)->all(); 147 | ``` 148 | 149 | ### 使用`DataProvider` 150 | 151 | ```php 152 | $dataProvider = new ActiveDataProvider([ 153 | 'query' => User::find(), 154 | 'pagination' => [ 155 | 'pageSize' => 20, 156 | ], 157 | ]); 158 | foreach ($dataProvider->getModels() as $itemModel) { 159 | // code... 160 | } 161 | ``` 162 | 163 | 更多应用 164 | ------------ 165 | 166 | - [自定义排序规则](docs/custom-sorting.md) 167 | -------------------------------------------------------------------------------- /Response.php: -------------------------------------------------------------------------------- 1 | 6 | * @create_time 2015-06-02 14:29 7 | */ 8 | 9 | namespace wsl\ssdb; 10 | 11 | /** 12 | * Class Response 13 | * @package SSDB 14 | */ 15 | class Response 16 | { 17 | public $cmd; 18 | public $code; 19 | public $data = null; 20 | public $message; 21 | 22 | public function __construct($code='ok', $data_or_message=null){ 23 | $this->code = $code; 24 | if($code == 'ok'){ 25 | $this->data = $data_or_message; 26 | }else{ 27 | $this->message = $data_or_message; 28 | } 29 | } 30 | 31 | public function __toString(){ 32 | if($this->code == 'ok'){ 33 | $s = $this->data === null? '' : json_encode($this->data); 34 | }else{ 35 | $s = $this->message; 36 | } 37 | return sprintf('%-13s %12s %s', $this->cmd, $this->code, $s); 38 | } 39 | 40 | public function ok(){ 41 | return $this->code == 'ok'; 42 | } 43 | 44 | public function not_found(){ 45 | return $this->code == 'not_found'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /SsdbSession.php: -------------------------------------------------------------------------------- 1 | 6 | * @create_time 2015-06-02 14:29 7 | */ 8 | 9 | namespace wsl\ssdb; 10 | 11 | use Yii; 12 | use yii\base\InvalidConfigException; 13 | use yii\web\Session; 14 | 15 | /* 16 | * * To use ssdb Session as the session application component, configure the application as follows, 17 | * 18 | * ~~~ 19 | * [ 20 | * 'components' => [ 21 | * 'session' => [ 22 | * 'class' => 'wsl\ssdb\SsdbSession', 23 | * 'ssdb' => [ 24 | * 'host' => 'localhost', 25 | * 'port' => 8888, 26 | * 'easy' => true 27 | * ] 28 | * ], 29 | * ], 30 | * ] 31 | * ~~~ 32 | * 33 | * Or if you have configured the ssdb [[Connect]] as an application component, the following is sufficient: 34 | * 35 | * ~~~ 36 | * [ 37 | * 'components' => [ 38 | * 'session' => [ 39 | * 'class' => 'wsl\ssdb\SsdbSession', 40 | * // 'ssdb' => 'ssdb' // id of the connect application component 41 | * ], 42 | * ], 43 | * ] 44 | * ~~~ 45 | */ 46 | 47 | class SsdbSession extends Session { 48 | 49 | /** 50 | * @var string 51 | */ 52 | public $key_prefix = 'ssdb_session_'; 53 | 54 | /** 55 | * 56 | * @var Connection 57 | */ 58 | public $ssdb = 'ssdb'; 59 | 60 | 61 | public function init() 62 | { 63 | parent::init(); 64 | if (is_string($this->ssdb)) { 65 | $this->ssdb = \Yii::$app->get($this->ssdb); 66 | } elseif (is_array($this->ssdb)) { 67 | if (!isset($this->ssdb['class'])) { 68 | $this->ssdb['class'] = Connection::className(); 69 | } 70 | $this->ssdb = \Yii::createObject($this->ssdb); 71 | } 72 | 73 | if (!$this->ssdb instanceof Connection) { 74 | throw new InvalidConfigException("Session::ssdb must be either a Ssdb Connection instance or the application component ID of a ssdb Connection."); 75 | } 76 | if ($this->key_prefix === null) { 77 | $this->key_prefix = substr(md5(Yii::$app->id), 0, 5); 78 | } 79 | } 80 | 81 | /** 82 | * Returns a value indicating whether to use custom session storage. 83 | * This method overrides the parent implementation and always returns true. 84 | * @return boolean whether to use custom storage. 85 | */ 86 | public function getUseCustomStorage() 87 | { 88 | return true; 89 | } 90 | /** 91 | * Session read handler. 92 | * Do not call this method directly. 93 | * @param string $id session ID 94 | * @return string the session data 95 | */ 96 | public function readSession($id) 97 | { 98 | $data = $this->ssdb->get($this->calculateKey($id)); 99 | return $data === false ? '' : $data; 100 | } 101 | /** 102 | * Session write handler. 103 | * Do not call this method directly. 104 | * @param string $id session ID 105 | * @param string $data session data 106 | * @return boolean whether session write is successful 107 | */ 108 | public function writeSession($id, $data) 109 | { 110 | return (bool) $this->ssdb->setx($this->calculateKey($id), $data, $this->getTimeout()); 111 | } 112 | /** 113 | * Session destroy handler. 114 | * Do not call this method directly. 115 | * @param string $id session ID 116 | * @return boolean whether session is destroyed successfully 117 | */ 118 | public function destroySession($id) 119 | { 120 | return (bool) $this->ssdb->del($this->calculateKey($id)); 121 | } 122 | /** 123 | * Generates a unique key used for storing session data in cache. 124 | * @param string $id session variable name 125 | * @return string a safe cache key associated with the session variable name 126 | */ 127 | protected function calculateKey($id) 128 | { 129 | return $this->key_prefix . $id; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myweishanli/yii2-ssdb", 3 | "description": "Yii2 SSDB Active Record Active Query", 4 | "type": "yii2-extension", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Shanli Wei", 9 | "email": "myweishanli@gmail.com" 10 | } 11 | ], 12 | "minimum-stability": "stable", 13 | "require": { 14 | "php": ">=5.4.0", 15 | "yiisoft/yii2": ">=2.0.6" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "wsl\\ssdb\\": "" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/custom-sorting.md: -------------------------------------------------------------------------------- 1 | 自定义字段排序 2 | ========= 3 | 4 | 创建数据模型 5 | ------------ 6 | 7 | `common/models/ssdb/User.php` 8 | ```php 9 | /** 10 | * This is the ActiveRecord class for [[\common\models\User]]. 11 | * 12 | * @property string $user_id 13 | * @property string $name 14 | * @property integer $age 15 | * @property integer $status 16 | */ 17 | class User extends \wsl\ssdb\ActiveRecord 18 | { 19 | /** 20 | * @var string ModelClass 完整类名 21 | */ 22 | public static $modelClass = '\console\models\SnsTestUser'; 23 | 24 | /** 25 | * @var array 自定义排序字段顺序 26 | */ 27 | public static $sortFields = [ 28 | 'filter' => ['age', 'status'], 29 | ]; 30 | 31 | /** 32 | * 排序规则 33 | * 34 | * @return array 35 | */ 36 | public function sortRules() 37 | { 38 | return [ 39 | /** 40 | * 自定义名称,也可以使用数组默认的key 41 | */ 42 | 'filter' => [ 43 | /** 44 | * 索引名称 45 | * 46 | * @return mixed 可以是一个字符、数组、回调函数(返回字符串)、回调函数(返回数组) 47 | */ 48 | 'index' => function () { 49 | return $this->comb([ 50 | 'age' => $this->age, 51 | 'status' => $this->status, 52 | ]); 53 | }, 54 | /** 55 | * 权重 56 | * 57 | * @return mixed integer或者个回调函数(返回integer) 58 | */ 59 | 'weight' => function () { 60 | return $this->age; 61 | }, 62 | /** 63 | * 默认值为true 64 | * 65 | * @return mixed boolean或者回调函数(返回boolean) 66 | */ 67 | // 'isValid' => true 68 | ], 69 | ]; 70 | } 71 | 72 | } 73 | ``` 74 | 75 | 查询示例 76 | ------------ 77 | 78 | ```php 79 | $models = SnsTestUser::find() 80 | ->orderParams('filter', [ 81 | 'status' => 1, 82 | ], 'desc') 83 | ->all(); 84 | foreach ($models as $itemModel) { 85 | print_r($itemModel->toArray()); 86 | } 87 | ``` --------------------------------------------------------------------------------