├── .gitignore
├── .travis.yml
├── README.md
├── composer.json
├── doc
└── Pea.svg
├── phpunit.xml
├── src
├── Blueprint.php
├── Cache.php
├── Meta.php
├── Model.php
├── QueryBuilder.php
├── RedisCache.php
├── RedisMeta.php
├── SchemaFacade.php
└── ServiceProvider.php
└── test
├── CacheTest.php
├── MetaTest.php
├── ModelTest.php
└── TestCase.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .phpcd
2 | vendor
3 | composer.lock
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.6
5 | - 7.0
6 | - hhvm
7 |
8 | env:
9 | global:
10 | - setup=basic
11 |
12 | matrix:
13 | include:
14 | - php: 5.6
15 | env: setup=stable
16 |
17 | sudo: false
18 |
19 | install:
20 | - if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-source; fi
21 | - if [[ $setup = 'stable' ]]; then travis_retry composer update --prefer-source --no-interaction --prefer-stable; fi
22 | - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-source --no-interaction --prefer-lowest --prefer-stable; fi
23 |
24 | script: vendor/bin/phpunit
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pea
2 |
3 | [](https://gitter.im/angejia/pea?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 | [](https://travis-ci.org/angejia/pea)
5 |
6 | Laravel Eloquent 的缓存层。
7 |
8 | ## 特色
9 |
10 | - 行级缓存
11 | - 表级缓存
12 | - 自动过期
13 |
14 | 更多细节参考[wiki](../../wiki)。
15 |
16 | ## 安装
17 |
18 | ```
19 | composer require angejia/pea:dev-master
20 | ```
21 |
22 | ## 使用
23 |
24 | 在`config/app.php`中添加`Angejia\Pea\ServiceProvider`,然后使用`Angejia\Pea\Model`替换`Illuminate\Database\Eloquent\Model`。 最后在模型中设置`protected`属性`$needCache`为`true`即可开启缓存支持。
25 |
26 | ```php
27 | class UserModel extends \Angejia\Pea\Model
28 | {
29 | protected $needCache = true;
30 | }
31 | ```
32 |
33 | 如果你有专门的 Redis 缓存实例,可以通过`config/database.php`指定。具体参见[wiki](../../wiki/Laravel-配置)。
34 |
35 | ---
36 | [安个家](http://www.angejia.com/)出品。
37 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angejia/pea",
3 | "description": "Eloquent Cache Layer",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "吕海涛",
8 | "email": "git@lvht.net"
9 | }
10 | ],
11 | "autoload": {
12 | "psr-4": {
13 | "Angejia\\Pea\\": "src"
14 | }
15 | },
16 | "autoload-dev": {
17 | "psr-4": {
18 | "Angejia\\Pea\\": "test"
19 | }
20 | },
21 | "require": {
22 | "illuminate/database": "^5.1",
23 | "illuminate/redis": "^5.1"
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit": "^5.0",
27 | "mockery/mockery": "^0.9.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/doc/Pea.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./test/
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Blueprint.php:
--------------------------------------------------------------------------------
1 | toSql($connection, $grammar);
17 | if (!$statements) {
18 | throw new \RuntimeException('migration must be created with Blueprint');
19 | }
20 |
21 | foreach ($statements as $statement) {
22 | $connection->statement($statement);
23 | }
24 |
25 | $db = $connection->getDatabaseName();
26 | $table = $this->getTable();
27 | $this->meta->flushAll($db, $table);
28 | }
29 |
30 | public function setMeta(Meta $meta)
31 | {
32 | $this->meta = $meta;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Cache.php:
--------------------------------------------------------------------------------
1 | 'value1',
13 | * 'key2' => 'value2',
14 | * ]
15 | */
16 | function get($keys);
17 |
18 | /**
19 | * 批量设置缓存内容
20 | *
21 | * @param array $key_value 待缓存键值对
22 | */
23 | function set($keyValue);
24 |
25 | /**
26 | * 清理缓存内容
27 | *
28 | * @param array $keys 缓存索引列表
29 | */
30 | function del($keys);
31 | }
32 |
--------------------------------------------------------------------------------
/src/Meta.php:
--------------------------------------------------------------------------------
1 | needCache && !self::$disableReadCache;
14 | }
15 |
16 | /**
17 | * 判断更新数据库的时候是否需要更新缓存
18 | */
19 | public function needFlushCache()
20 | {
21 | return $this->needCache;
22 | }
23 |
24 | public function primaryKey()
25 | {
26 | return $this->primaryKey;
27 | }
28 |
29 | public function table()
30 | {
31 | return $this->table;
32 | }
33 |
34 | protected function newBaseQueryBuilder()
35 | {
36 | $conn = $this->getConnection();
37 |
38 | $grammar = $conn->getQueryGrammar();
39 |
40 | $queryBuilder = new QueryBuilder(
41 | $conn, $grammar, $conn->getPostProcessor());
42 | $queryBuilder->setModel($this);
43 |
44 | return $queryBuilder;
45 | }
46 |
47 | public function newEloquentBuilder($query)
48 | {
49 | $builder = new Builder($query);
50 |
51 | $builder->macro('key', function (Builder $builder) {
52 | return $builder->getQuery()->key();
53 | });
54 |
55 | $builder->macro('flush', function (Builder $builder) {
56 | return $builder->getQuery()->flush();
57 | });
58 |
59 | return $builder;
60 | }
61 |
62 | /**
63 | * 关闭查询数据库的时候读取缓存的逻辑(更新数据库的时候还会更新缓存)
64 | */
65 | public static function disableReadCache()
66 | {
67 | self::$disableReadCache = true;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/QueryBuilder.php:
--------------------------------------------------------------------------------
1 | model) {
19 | return false;
20 | }
21 |
22 | return $this->model->needCache();
23 | }
24 |
25 | private function needFlushCache()
26 | {
27 | // TODO 如果没有设置 model,则认为不用处理缓存逻辑
28 | if (!$this->model) {
29 | return false;
30 | }
31 |
32 | return $this->model->needFlushCache();
33 | }
34 |
35 | public function setModel(Model $model)
36 | {
37 | $this->model = $model;
38 | }
39 |
40 | /**
41 | * @return Cache
42 | */
43 | protected function getCache()
44 | {
45 | return Container::getInstance()->make(Cache::class);
46 | }
47 |
48 | /**
49 | * @return Meta
50 | */
51 | protected function getMeta()
52 | {
53 | return Container::getInstance()->make(Meta::class);
54 | }
55 |
56 | public function get($columns = ['*'])
57 | {
58 | if ($this->model) {
59 | $this->fireEvent('get');
60 | }
61 |
62 | if (!$this->needCache())
63 | {
64 | return parent::get($columns);
65 | }
66 |
67 | if (!$this->columns && self::hasRawColumn($columns)) {
68 | $this->columns = $columns;
69 | }
70 |
71 | if ($this->isAwful()) {
72 | return $this->getAwful();
73 | } elseif ($this->isNormal()) {
74 | return $this->getAwful();
75 | } else {
76 | return $this->getSimple();
77 | }
78 | }
79 |
80 | private static function hasRawColumn($columns)
81 | {
82 | if (!$columns) {
83 | return false;
84 | }
85 |
86 | foreach ($columns as $column) {
87 | if ($column instanceof Expression) {
88 | return true;
89 | }
90 | }
91 |
92 | return false;
93 | }
94 |
95 | private function getAwful()
96 | {
97 | $key = $this->buildAwfulCacheKey();
98 | $cache = $this->getCache();
99 | $result = $cache->get([$key]);
100 | if (array_key_exists($key, $result)) {
101 | $this->fireEvent('hit.awful');
102 | return $result[$key];
103 | }
104 |
105 | $this->fireEvent('miss.awful');
106 |
107 | $result = parent::get();
108 | $cache->set([
109 | $key => $result,
110 | ]);
111 |
112 | return $result;
113 | }
114 |
115 | private function buildAwfulCacheKey()
116 | {
117 | return $this->buildTableCacheKey($this->toSql(), $this->getBindings());
118 | }
119 |
120 | /**
121 | * 判断当前查询是否未「复杂查询」,判断标准
122 | * 1. 含有 max, sum 等汇聚函数
123 | * 2. 包含 distinct 指令
124 | * 3. 包含分组
125 | * 4. 包含连表
126 | * 5. 包含联合
127 | * 6. 包含子查询
128 | * 7. 包含原生(raw)语句
129 | * 8. 包含排序 TODO 优化此类情形
130 | *
131 | * 复杂查询使用表级缓存,命中率较低
132 | */
133 | private function isAwful()
134 | {
135 | if (self::hasRawColumn($this->columns)) {
136 | return true;
137 | }
138 |
139 | return $this->aggregate
140 | or $this->distinct
141 | or $this->groups
142 | or $this->joins
143 | or $this->orders
144 | or $this->unions
145 | or !$this->wheres
146 | or array_key_exists('Exists', $this->wheres)
147 | or array_key_exists('InSub', $this->wheres)
148 | or array_key_exists('NotExists', $this->wheres)
149 | or array_key_exists('NotInSub', $this->wheres)
150 | or array_key_exists('Sub', $this->wheres)
151 | or array_key_exists('raw', $this->wheres);
152 | }
153 |
154 | private function getNormal()
155 | {
156 | $primaryKeyName = $this->model->primaryKey();
157 | // 查询主键列表
158 | $rows = $this->getAwful([$primaryKeyName]);
159 | $ids = array_map(function ($row) use($primaryKeyName) {
160 | return $row->$primaryKeyName;
161 | }, $rows);
162 |
163 | // 没查到结果则直接返回空数组
164 | if (!$ids) {
165 | return [];
166 | }
167 |
168 | // 根据主键查询结果
169 | $originWheres = $this->wheres;
170 | $originWhereBindings = $this->bindings['where'];
171 | $originLimit = $this->limit;
172 | $originOffset = $this->offset;
173 |
174 | $this->wheres = [];
175 | $this->bindings['where'] = [];
176 | $this->limit = null;
177 | $this->offset = null;
178 | $this->whereIn($primaryKeyName, $ids);
179 | $rows = $this->getSimple();
180 |
181 | $this->wheres = $originWheres;
182 | $this->bindings['where'] = $originWhereBindings;
183 | $this->limit = $originLimit;
184 | $this->offset = $originOffset;
185 |
186 | return $rows;
187 | }
188 |
189 | /**
190 | * 判断当前查询是否未「普通查询」
191 | *
192 | * 普通查询需要转化成简单查询
193 | */
194 | private function isNormal()
195 | {
196 | return !$this->isAwful() && !$this->isSimple();
197 | }
198 |
199 | /**
200 | * 简单查询,只根据主键过滤结果集
201 | */
202 | private function getSimple()
203 | {
204 | $primaryKeyName = $this->model->primaryKey();
205 | $cacheKeys = $this->buildCacheKeys();
206 | $keyId = array_flip($cacheKeys);
207 |
208 | $cache = $this->getCache();
209 | $cachedRows = $cache->get(array_values($cacheKeys));
210 | foreach ($cachedRows as $key => $row) {
211 | unset($cacheKeys[$keyId[$key]]);
212 | }
213 |
214 | // TODO 如何处理顺序
215 | $cachedRows = array_filter(array_values($cachedRows), function ($row) {
216 | return $row !== [];
217 | });
218 |
219 | $missedIds = array_keys($cacheKeys);
220 | if (!$missedIds) {
221 | $this->fireEvent('hit.simple.1000');
222 | return $cachedRows;
223 | }
224 |
225 | if (count($cachedRows) === 0) {
226 | $this->fireEvent('miss.simple');
227 | } else {
228 | $cachedNum = count($cachedRows);
229 | $missedNum = count($missedIds);
230 | $percent = (int)($cachedNum / ($cachedNum + $missedNum) * 1000);
231 | $this->fireEvent('hit.simple.' . $percent);
232 | }
233 |
234 | $originWheres = $this->wheres;
235 | $originWhereBindings = $this->bindings['where'];
236 | $originColumns = $this->columns;
237 | $this->wheres = [];
238 | $this->bindings['where'] = [];
239 | $this->whereIn($primaryKeyName, $missedIds);
240 | $this->columns = null;
241 |
242 | $missedRows = array_fill_keys($missedIds, []);
243 | foreach (parent::get() as $row) {
244 | $missedRows[$row->$primaryKeyName] = $row;
245 | }
246 |
247 | $this->wheres = $originWheres;
248 | $this->bindings['where'] = $originWhereBindings;
249 | $this->columns = $originColumns;
250 |
251 | $toCachRows = [];
252 | $toCachIds = array_keys($missedRows);
253 | $toCachKeys = $this->buildRowCacheKey($toCachIds);
254 | foreach ($missedRows as $id => $row) {
255 | $toCachRows[$toCachKeys[$id]] = $row;
256 | }
257 | if ($toCachRows) {
258 | $cache->set($toCachRows);
259 | }
260 | $missedRows = array_filter(array_values($missedRows), function ($row) {
261 | return $row !== [];
262 | });
263 |
264 | return array_merge($cachedRows, $missedRows);
265 | }
266 |
267 | private function buildCacheKeys()
268 | {
269 | $where = current($this->wheres);
270 | if ($where['type'] == 'In') {
271 | $ids = $where['values'];
272 | } else {
273 | $ids = [$where['value']];
274 | }
275 |
276 | $cacheKeys = $this->buildRowCacheKey($ids);
277 |
278 | return $cacheKeys;
279 | }
280 |
281 | /**
282 | * 「简单查询」就是只根据主键过滤结果集的查询,有以下两种形式:
283 | * 1. select * from foo where id = 1;
284 | * 2. select * from foo where id in (1, 2, 3);
285 | */
286 | private function isSimple()
287 | {
288 | if ($this->isAwful()) {
289 | return false;
290 | }
291 |
292 | if (!$this->wheres) {
293 | return false;
294 | }
295 |
296 | if (count($this->wheres) > 1) {
297 | return false;
298 | }
299 |
300 | $where = current($this->wheres);
301 |
302 | if ($where['type'] === 'Nested') {
303 | return false;
304 | }
305 |
306 | $id = $this->model->primaryKey();
307 | $tableId = $this->model->table() . '.' . $this->model->primaryKey();
308 | if (!in_array($where['column'], [$id, $tableId])) {
309 | return false;
310 | }
311 |
312 | if ($where['type'] === 'In') {
313 | return true;
314 | }
315 |
316 | if ($where['type'] === 'Basic') {
317 | if ($where['operator'] === '=') {
318 | return true;
319 | }
320 | }
321 |
322 | return false;
323 | }
324 |
325 | public function delete($id = null)
326 | {
327 | if ($this->needFlushCache()) {
328 | // 清空表级缓存
329 | $meta = $this->getMeta();
330 | $meta->flush($this->db(), $this->model->table());
331 | $this->flushAffectingRowCache();
332 | }
333 |
334 | return parent::delete($id);
335 | }
336 |
337 | /**
338 | * 查找受影响的 ID,清空相关行级缓存
339 | */
340 | private function flushAffectingRowCache()
341 | {
342 | $keyName = $this->model->primaryKey();
343 | $toDeleteRows = parent::get();
344 | $ids = [];
345 | foreach ($toDeleteRows as $row) {
346 | $ids[] = $row->$keyName;
347 | }
348 | if ($ids) {
349 | $cacheKeys = $this->buildRowCacheKey($ids);
350 | $cache = $this->getCache();
351 | $cache->del(array_values($cacheKeys));
352 | }
353 | }
354 |
355 | public function update(array $values)
356 | {
357 | if ($this->needFlushCache()) {
358 | // 清空表级缓存
359 | $meta = $this->getMeta();
360 | $meta->flush($this->db(), $this->model->table());
361 |
362 | $this->flushAffectingRowCache();
363 | }
364 | return parent::update($values);
365 | }
366 |
367 | public function insert(array $values)
368 | {
369 | if ($this->needFlushCache()) {
370 | // 清空表级缓存
371 | $meta = $this->getMeta();
372 | $meta->flush($this->db(), $this->model->table());
373 |
374 | if (! is_array(reset($values))) {
375 | $values = [$values];
376 | }
377 | $toClearIds = [];
378 | foreach ($values as $value) {
379 | $toClearIds[] = $value[$this->model->primaryKey()];
380 | }
381 | $toClearKeys = $this->buildRowCacheKey($toClearIds);
382 | $this->getCache()->del(array_values($toClearKeys));
383 | }
384 |
385 | return parent::insert($values);
386 | }
387 |
388 | public function insertGetId(array $values, $sequence = null)
389 | {
390 | if ($this->needFlushCache()) {
391 | // 清空表级缓存
392 | $meta = $this->getMeta();
393 | $meta->flush($this->db(), $this->model->table());
394 | }
395 |
396 | $id = parent::insertGetId($values, $sequence);
397 |
398 | if ($this->needFlushCache()) {
399 | $key = $this->buildRowCacheKey([$id])[$id];
400 | $this->getCache()->del([$key]);
401 | }
402 |
403 | return $id;
404 | }
405 |
406 | /**
407 | * 获取当前查询所用的数据库名称
408 | */
409 | private function db()
410 | {
411 | return $this->connection->getDatabaseName();
412 | }
413 |
414 | /**
415 | * 构造行级缓存索引
416 | */
417 | private function buildRowCacheKey($keyValues)
418 | {
419 | $meta = $this->getMeta();
420 | $prefix = $meta->prefix($this->db(), $this->model->table());
421 |
422 | $keys = [];
423 | foreach ($keyValues as $keyValue) {
424 | $keys[$keyValue] = md5($prefix . ':' . $keyValue);
425 | }
426 |
427 | return $keys;
428 | }
429 |
430 | /**
431 | * 构造表级缓存索引
432 | */
433 | private function buildTableCacheKey($sql, $bindings)
434 | {
435 | $meta = $this->getMeta();
436 | $parts = [
437 | $meta->prefix($this->db(), $this->model->table(), true),
438 | $sql,
439 | json_encode($bindings),
440 | ];
441 |
442 | return md5(implode(':', $parts));
443 | }
444 |
445 | /**
446 | * 返回查询对应的缓存 key
447 | *
448 | * 仅供调试使用!
449 | */
450 | public function key()
451 | {
452 | if ($this->isSimple()) {
453 | return $this->buildCacheKeys();
454 | } elseif ($this->isAwful()) {
455 | return $this->buildAwfulCacheKey();
456 | } else {
457 | return $this->buildAwfulCacheKey();
458 | }
459 | }
460 |
461 | /**
462 | * 过期当前表所有缓存
463 | */
464 | public function flush()
465 | {
466 | $this->getMeta()->flushAll($this->db(), $this->model->table());
467 | }
468 |
469 | private function fireEvent($name, $data = [])
470 | {
471 | /** @var $container Container */
472 | $container = Container::getInstance();
473 | if (!$container->bound(Dispatcher::class)) {
474 | return;
475 | }
476 |
477 | $data['table'] = $this->model->table();
478 | $data['db'] = $this->db();
479 |
480 | $event = 'angejia.pea.' . $name;
481 | Container::getInstance()->make(Dispatcher::class)->fire($event, $data);
482 | }
483 | }
484 |
--------------------------------------------------------------------------------
/src/RedisCache.php:
--------------------------------------------------------------------------------
1 | redis = $redis;
15 | }
16 |
17 | public function get($keys)
18 | {
19 | if (!$keys) {
20 | return [];
21 | }
22 |
23 | $keyValue = array_combine($keys, $this->redis->mget($keys));
24 | array_walk($keyValue, function (&$item) {
25 | $item = json_decode($item);
26 | });
27 | $keyValue = array_filter($keyValue, function ($value) {
28 | return !is_null($value);
29 | });
30 |
31 | return $keyValue;
32 | }
33 |
34 | public function set($keyValue)
35 | {
36 | $pipe = $this->redis->pipeline();
37 |
38 | foreach ($keyValue as $key => $value) {
39 | if (!is_null($value)) {
40 | $value = json_encode($value);
41 | $pipe->setex($key, 86400, $value); // 缓存 1 天
42 | }
43 | }
44 |
45 | return $pipe->execute();
46 | }
47 |
48 | public function del($keys)
49 | {
50 | return $this->redis->del($keys);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/RedisMeta.php:
--------------------------------------------------------------------------------
1 | redis = $redis;
20 | }
21 |
22 | public function prefix($db, $table, $isForTable = false)
23 | {
24 | $version = $this->getSchemaVersion($db, $table);
25 | if ($isForTable) {
26 | $version = $version . ':' . $this->getUpdateVersion($db, $table);
27 | }
28 |
29 | return implode(':', [
30 | $this->prefix,
31 | $db,
32 | $table,
33 | $version,
34 | ]);
35 | }
36 |
37 | private function getSchemaVersion($db, $table)
38 | {
39 | $key = implode(':', [
40 | $this->prefix,
41 | self::KEY_SCHEMA_VERSION,
42 | $db,
43 | $table,
44 | ]);
45 |
46 | $key = md5($key);
47 |
48 | return $this->redis->get($key) ?: 0;
49 | }
50 |
51 | private function getUpdateVersion($db, $table)
52 | {
53 | $key = implode(':', [
54 | $this->prefix,
55 | self::KEY_UPDATE_VERSION,
56 | $db,
57 | $table,
58 | ]);
59 |
60 | $key = md5($key);
61 |
62 | return $this->redis->get($key) ?: 0;
63 | }
64 |
65 | public function flush($db, $table)
66 | {
67 | $key = implode(':', [
68 | $this->prefix,
69 | self::KEY_UPDATE_VERSION,
70 | $db,
71 | $table,
72 | ]);
73 |
74 | $key = md5($key);
75 |
76 | $this->redis->incr($key);
77 | }
78 |
79 | public function flushAll($db, $table)
80 | {
81 | $key = implode(':', [
82 | $this->prefix,
83 | self::KEY_SCHEMA_VERSION,
84 | $db,
85 | $table,
86 | ]);
87 |
88 | $key = md5($key);
89 |
90 | $this->redis->incr($key);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/SchemaFacade.php:
--------------------------------------------------------------------------------
1 | connection($name)->getSchemaBuilder();
31 |
32 | $builder->blueprintResolver(function ($table, $callback) {
33 | $blueprint = new Blueprint($table, $callback);
34 | $blueprint->setMeta(static::$app[Meta::class]);
35 |
36 | return $blueprint;
37 | });
38 |
39 | return $builder;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | alias('Schema', SchemaFacade::class);
11 | }
12 |
13 | public function register()
14 | {
15 | // TODO 缓存 redis 实例可以配置
16 | $this->app->singleton(Meta::class, function () {
17 | return new RedisMeta($this->getRedis());
18 | });
19 | $this->app->singleton(Cache::class, function () {
20 | return new RedisCache($this->getRedis());
21 | });
22 | }
23 |
24 | /**
25 | * 默认取名称为 pea 的 Redis 实例;如果没有,则取 default 。
26 | */
27 | private function getRedis()
28 | {
29 | $redis = $this->app->make('redis');
30 | return $redis->connection('pea') ?: $redis->connection();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/CacheTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('mget')->with([
13 | 'a',
14 | 'b',
15 | 'c',
16 | ])->andReturn([
17 | 1,
18 | "null",
19 | '[]',
20 | ]);
21 |
22 | $cache = new RedisCache($redis);
23 | $result = $cache->get(['a', 'b', 'c']);
24 | $this->assertEquals([ 'a' => 1 , 'c' => [] ], $result);
25 | }
26 |
27 | public function testSet()
28 | {
29 | $redis = M::mock(Redis::class);
30 | $pipe = M::mock('pipe');
31 | $pipe->shouldReceive('setex')->with('a', 86400, '[1,2]');
32 | $pipe->shouldReceive('setex')->with('c', 86400, '[]');
33 | $pipe->shouldReceive('execute');
34 | $redis->shouldReceive('pipeline')->andReturn($pipe);
35 |
36 | $cache = new RedisCache($redis);
37 | $cache->set([
38 | 'a' => [1, 2],
39 | 'b' => null,
40 | 'c' => [],
41 | ]);
42 | }
43 |
44 | public function testDel()
45 | {
46 | $redis = M::mock(Redis::class);
47 | $redis->shouldReceive('del')->with([
48 | 'a',
49 | 'b',
50 | ]);
51 |
52 | $cache = new RedisCache($redis);
53 | $cache->del(['a', 'b']);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test/MetaTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('incr')->with('1031d621441d2f689f95870d86225cf2');
13 |
14 | $meta = new RedisMeta($redis);
15 | $meta->flush('angejia', 'user');
16 | }
17 |
18 | public function testFlushAll()
19 | {
20 | $redis = M::mock(Redis::class);
21 | $redis->shouldReceive('incr')->with('2d23e940e190c4fefe3955fe8cf5c8a8');
22 |
23 | $meta = new RedisMeta($redis);
24 | $meta->flushAll('angejia', 'user');
25 | }
26 |
27 | public function testPrefixForRow()
28 | {
29 | $redis = M::mock(Redis::class);
30 | $redis->shouldReceive('get')->with('2d23e940e190c4fefe3955fe8cf5c8a8')
31 | ->andReturn(1);
32 |
33 | $meta = new RedisMeta($redis);
34 | $prefix = $meta->prefix('angejia', 'user');
35 | $this->assertEquals('pea:angejia:user:1', $prefix);
36 | }
37 |
38 | public function testPrefixForTable()
39 | {
40 | $redis = M::mock(Redis::class);
41 | // schema version
42 | $redis->shouldReceive('get')->with('2d23e940e190c4fefe3955fe8cf5c8a8')
43 | ->andReturn(1);
44 | // update version
45 | $redis->shouldReceive('get')->with('1031d621441d2f689f95870d86225cf2')
46 | ->andReturn(6);
47 |
48 | $meta = new RedisMeta($redis);
49 | $prefix = $meta->prefix('angejia', 'user', true);
50 | $this->assertEquals('pea:angejia:user:1:6', $prefix);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/ModelTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('getDatabaseName')->andReturn('angejia');
40 | $conn->shouldReceive('getQueryGrammar')->andReturn(new Grammar);
41 | $conn->shouldReceive('getPostProcessor')->andReturn(new Processor);
42 |
43 | $this->conn = $conn;
44 |
45 | // 让所有 Model 使用我们伪造的数据库连接
46 | $resolver = M::mock(ConnectionResolverInterface::class);
47 | $resolver->shouldReceive('connection')
48 | ->andReturnUsing(function () {
49 | return $this->conn;
50 | });
51 | User::setConnectionResolver($resolver);
52 |
53 | // 模拟 Meta 服务
54 | $meta = M::mock(Meta::class);
55 | $meta->shouldReceive('prefix')
56 | // 查找 angejia.user 表主键缓存 key 前缀
57 | ->with('angejia', 'user')
58 | // 缓存 key 前缀全部使用空字符串 ''
59 | ->andReturn('');
60 | $meta->shouldReceive('prefix')
61 | // 查找 angejia.user 表主键缓存 key 前缀
62 | ->with('angejia', 'user', true)
63 | // 缓存 key 前缀全部使用空字符串 ''
64 | ->andReturn('');
65 | $this->meta = $meta;
66 |
67 | // 模拟 Cache 服务
68 | $cache = M::mock(Cache::class);
69 | $this->cache = $cache;
70 |
71 | // 注入依赖的服务
72 | $this->app->bind(Meta::class, function () {
73 | return $this->meta;
74 | });
75 | $this->app->bind(Cache::class, function () {
76 | return $this->cache;
77 | });
78 | }
79 |
80 | public function testModelTable()
81 | {
82 | $user = new User;
83 | $this->assertEquals('user', $user->table());
84 | }
85 |
86 | public function testModelNeedCache()
87 | {
88 | $user = new User;
89 | $this->assertTrue($user->needCache());
90 | }
91 |
92 | public function testOneCachedSimpleGet()
93 | {
94 | $this->cache->shouldReceive('get')
95 | // 查询 id 为 1 的缓存
96 | ->with([
97 | '3558193cd9818af7fe4d2c2f5bd9d00f',
98 | ])
99 | // 模拟全部命中缓存
100 | ->andReturn([
101 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
102 | ]);
103 |
104 | $dispatcher = M::Mock(Dispatcher::class);
105 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
106 | $dispatcher->shouldReceive('fire')->with('angejia.pea.hit.simple.1000', ['table' => 'user', 'db' => 'angejia']);
107 | $this->app->instance(Dispatcher::class, $dispatcher);
108 |
109 | // 查询 id 为 1 的记录,应该命中缓存
110 | $u1 = User::find(1);
111 |
112 | $this->assertEquals('海涛', $u1->name);
113 | }
114 |
115 | public function testOneMissedSimpleGet()
116 | {
117 | // 模拟数据库返回查询未命中缓存的数据
118 | $this->conn->shouldReceive('select')
119 | ->with('select * from "user" where "id" in (?) limit 1', [1], true)
120 | ->andReturn([
121 | (object) [ 'id' => 1, 'name' => '海涛', ],
122 | ]);
123 |
124 |
125 | // 模拟缓存查询结果
126 | $this->cache->shouldReceive('set')
127 | ->with([
128 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
129 | ]);
130 | $this->cache->shouldReceive('get')
131 | // 查询 id 为 1 的缓存
132 | ->with([
133 | '3558193cd9818af7fe4d2c2f5bd9d00f',
134 | ])
135 | // 模拟全部没有命中缓存
136 | ->andReturn([]);
137 |
138 | $dispatcher = M::Mock(Dispatcher::class);
139 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
140 | $dispatcher->shouldReceive('fire')->with('angejia.pea.miss.simple', ['table' => 'user', 'db' => 'angejia']);
141 | $this->app->instance(Dispatcher::class, $dispatcher);
142 |
143 | // 查询 id 为 1 的记录,应该命中缓存
144 | $u1 = User::find(1);
145 |
146 | $this->assertEquals('海涛', $u1->name);
147 | }
148 |
149 | public function testAllCachedSimpleGet()
150 | {
151 | $this->cache->shouldReceive('get')
152 | // 查询 id 为 1 和 2 的缓存
153 | ->with([
154 | '3558193cd9818af7fe4d2c2f5bd9d00f',
155 | '343a10e6c2480e111dd3e9e564eb7966',
156 | ])
157 | // 模拟全部命中缓存
158 | ->andReturn([
159 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
160 | '343a10e6c2480e111dd3e9e564eb7966' => (object) [ 'id' => 2, 'name' => '涛涛', ],
161 | ]);
162 |
163 | $dispatcher = M::Mock(Dispatcher::class);
164 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
165 | $dispatcher->shouldReceive('fire')->with('angejia.pea.hit.simple.1000', ['table' => 'user', 'db' => 'angejia']);
166 | $this->app->instance(Dispatcher::class, $dispatcher);
167 |
168 |
169 | // 查询 id 为 1 和 2 的记录,应该全部命中缓存
170 | // TODO 此处顺序是个问题
171 | list($u1, $u2) = User::find([1, 2]);
172 |
173 | $this->assertEquals('海涛', $u1->name);
174 | $this->assertEquals('涛涛', $u2->name);
175 | }
176 |
177 | public function testPartialCachedSimpleGet()
178 | {
179 | $this->cache->shouldReceive('get')
180 | ->with([
181 | '3558193cd9818af7fe4d2c2f5bd9d00f',
182 | '343a10e6c2480e111dd3e9e564eb7966',
183 | ])
184 | // 缓存中只有 id 为 1 的数据
185 | ->andReturn([
186 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
187 | ]);
188 |
189 | // 模拟数据库返回查询未命中缓存的数据
190 | $this->conn->shouldReceive('select')
191 | ->with('select * from "user" where "id" in (?)', [2], true)
192 | ->andReturn([
193 | (object) [ 'id' => 2, 'name' => '涛涛', ],
194 | ]);
195 |
196 | // 查询完成后需要将数据写入缓存
197 | $this->cache->shouldReceive('set')
198 | ->with([
199 | '343a10e6c2480e111dd3e9e564eb7966' => (object) [ 'id' => 2, 'name' => '涛涛', ],
200 | ]);
201 |
202 | $dispatcher = M::Mock(Dispatcher::class);
203 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
204 | $dispatcher->shouldReceive('fire')->with('angejia.pea.hit.simple.500', ['table' => 'user', 'db' => 'angejia']);
205 | $this->app->instance(Dispatcher::class, $dispatcher);
206 |
207 | list($u1, $u2) = User::find([1, 2]);
208 |
209 | $this->assertEquals('海涛', $u1->name);
210 | $this->assertEquals('涛涛', $u2->name);
211 | }
212 |
213 | public function testAllMissedSimpleGet()
214 | {
215 | $this->cache->shouldReceive('get')
216 | ->with([
217 | '3558193cd9818af7fe4d2c2f5bd9d00f',
218 | '343a10e6c2480e111dd3e9e564eb7966',
219 | ])
220 | // 缓存中只有 id 为 1 的数据
221 | ->andReturn([]);
222 |
223 | // 模拟数据库返回查询未命中缓存的数据
224 | $this->conn->shouldReceive('select')
225 | ->with('select * from "user" where "id" in (?, ?)', [1, 2], true)
226 | ->andReturn([
227 | (object) [ 'id' => 1, 'name' => '海涛', ],
228 | (object) [ 'id' => 2, 'name' => '涛涛', ],
229 | ]);
230 |
231 | // 查询完成后需要将数据写入缓存
232 | $this->cache->shouldReceive('set')
233 | ->with([
234 | '343a10e6c2480e111dd3e9e564eb7966' => (object) [ 'id' => 2, 'name' => '涛涛', ],
235 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
236 | ]);
237 |
238 | $dispatcher = M::Mock(Dispatcher::class);
239 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
240 | $dispatcher->shouldReceive('fire')->with('angejia.pea.miss.simple', ['table' => 'user', 'db' => 'angejia']);
241 | $this->app->instance(Dispatcher::class, $dispatcher);
242 |
243 | list($u1, $u2) = User::find([1, 2]);
244 |
245 | $this->assertEquals('海涛', $u1->name);
246 | $this->assertEquals('涛涛', $u2->name);
247 | }
248 |
249 | public function testFlushCacheForInsert()
250 | {
251 | $pdo = M::mock('\PDO');
252 | // 模拟数据库返回自增主键 ID
253 | $pdo->shouldReceive('lastInsertId')->andReturn(1);
254 | $this->conn->shouldReceive('getPdo')->andReturn($pdo);
255 | $this->conn->shouldReceive('insert');
256 |
257 | // 模拟刷新表级缓存
258 | $this->meta->shouldReceive('flush')->with('angejia', 'user');
259 |
260 | $this->cache->shouldReceive('del')->with([
261 | '3558193cd9818af7fe4d2c2f5bd9d00f',
262 | ]);
263 |
264 | $user = new User;
265 | $user->name = '海涛';
266 | $user->save();
267 | $this->assertEquals(1, $user->id);
268 | }
269 |
270 | public function testFlushCacheForBatchInsert()
271 | {
272 | $pdo = M::mock('\PDO');
273 | // 模拟数据库返回自增主键 ID
274 | $pdo->shouldReceive('lastInsertId')->andReturn(1);
275 | $this->conn->shouldReceive('getPdo')->andReturn($pdo);
276 | $this->conn->shouldReceive('insert');
277 |
278 | // 模拟刷新表级缓存
279 | $this->meta->shouldReceive('flush')->with('angejia', 'user');
280 |
281 | $this->cache->shouldReceive('del')->with([
282 | '3558193cd9818af7fe4d2c2f5bd9d00f',
283 | '343a10e6c2480e111dd3e9e564eb7966',
284 | ]);
285 |
286 | User::insert([
287 | ['id' => 1, 'name' => '海涛'],
288 | ['id' => 2, 'name' => 'haitao'],
289 | ]);
290 | }
291 |
292 | public function testFlushCacheForUpdateOne()
293 | {
294 | // 模拟数据库更新操作
295 | $this->conn->shouldReceive('update');
296 | // 模拟刷新表级缓存
297 | $this->meta->shouldReceive('flush')->with('angejia', 'user');
298 | // 模拟刷新行级缓存
299 | $this->cache->shouldReceive('del')
300 | ->with(['3558193cd9818af7fe4d2c2f5bd9d00f']);
301 |
302 | $this->cache->shouldReceive('get')
303 | // 查询 id 为 1 的缓存
304 | ->with([
305 | '3558193cd9818af7fe4d2c2f5bd9d00f',
306 | ])
307 | // 模拟全部命中缓存
308 | ->andReturn([
309 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
310 | ]);
311 |
312 | // 模拟返回受到影响的数据,用于清理缓存
313 | $this->conn->shouldReceive('select')
314 | ->andReturn([
315 | (object) [ 'id' => 1, 'name' => '海涛', ],
316 | ]);
317 |
318 |
319 | $user = User::find(1);
320 | $user->name = '海涛2';
321 | $user->save();
322 | }
323 |
324 | public function testFlushCacheForDeleteOne()
325 | {
326 | // 模拟数据库更新
327 | $this->conn->shouldReceive('delete');
328 |
329 | // 模拟刷新表级缓存
330 | $this->meta->shouldReceive('flush')->with('angejia', 'user');
331 | // 模拟刷新行级缓存
332 | $this->cache->shouldReceive('del')
333 | ->with(['3558193cd9818af7fe4d2c2f5bd9d00f']);
334 |
335 | // 模拟数据库返回首次查询结果
336 | $this->cache->shouldReceive('get')
337 | // 查询 id 为 1 的缓存
338 | ->with([
339 | '3558193cd9818af7fe4d2c2f5bd9d00f',
340 | ])
341 | // 模拟全部命中缓存
342 | ->andReturn([
343 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
344 | ]);
345 |
346 | // 模拟返回受到影响的数据,用于清理缓存
347 | $this->conn->shouldReceive('select')
348 | ->andReturn([
349 | (object) [ 'id' => 1, 'name' => '海涛', ],
350 | ]);
351 |
352 | $user = User::find(1);
353 | $user->delete();
354 | }
355 |
356 | /**
357 | * 复杂查询,命中缓存
358 | */
359 | public function testCachedAwfulGet()
360 | {
361 | $this->cache->shouldReceive('get')
362 | ->with([
363 | 'a52d401e05fd1cc438bd070bc4c1c14f',
364 | ])
365 | ->andReturn([
366 | 'a52d401e05fd1cc438bd070bc4c1c14f' => [
367 | (object) [ 'id' => 1, 'name' => '海涛', ],
368 | ]
369 | ]);
370 |
371 | $dispatcher = M::Mock(Dispatcher::class);
372 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
373 | $dispatcher->shouldReceive('fire')->with('angejia.pea.hit.awful', ['table' => 'user', 'db' => 'angejia']);
374 | $this->app->instance(Dispatcher::class, $dispatcher);
375 |
376 | $users = User::where('status', 1)->orderBy('id', 'desc')->get();
377 | $user0 = $users[0];
378 | $this->assertEquals(1, $user0->id);
379 | }
380 |
381 | /**
382 | * 复杂查询未命中缓存
383 | */
384 | public function testMissAwfulGet()
385 | {
386 | // 模拟数据库返回结果
387 | $this->conn->shouldReceive('select')
388 | ->with('select * from "user" where "status" = ? order by "id" desc', [1], true)
389 | ->andReturn([
390 | (object) [ 'id' => 1, 'name' => '海涛', ],
391 | ]);
392 | // 模拟未命中缓存
393 | $this->cache->shouldReceive('get')
394 | ->with([
395 | 'a52d401e05fd1cc438bd070bc4c1c14f',
396 | ])
397 | ->andReturn([]);
398 | // 模拟缓存查询结果
399 | $this->cache->shouldReceive('set')
400 | ->with([
401 | 'a52d401e05fd1cc438bd070bc4c1c14f' => [
402 | (object) [ 'id' => 1, 'name' => '海涛', ],
403 | ]
404 | ]);
405 |
406 | $dispatcher = M::Mock(Dispatcher::class);
407 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
408 | $dispatcher->shouldReceive('fire')->with('angejia.pea.miss.awful', ['table' => 'user', 'db' => 'angejia']);
409 | $this->app->instance(Dispatcher::class, $dispatcher);
410 |
411 | $uers = User::where('status', 1)->orderBy('id', 'desc')->get();
412 | }
413 |
414 | /**
415 | * 复杂查询未命中缓存
416 | */
417 | public function testRawAwfulGet()
418 | {
419 | // 模拟数据库返回结果
420 | $this->conn->shouldReceive('select')
421 | ->with('select count(*) from "user" where "id" = ?', [1], true)
422 | ->andReturn([
423 | (object) ["count(*)" => 1],
424 | ]);
425 | // 模拟未命中缓存
426 | $this->cache->shouldReceive('get')
427 | ->with([
428 | '122a32ae3f8656ba95b87700de71506c',
429 | ])
430 | ->andReturn([]);
431 | // 模拟缓存查询结果
432 | $this->cache->shouldReceive('set')
433 | ->with([
434 | '122a32ae3f8656ba95b87700de71506c' => [
435 | (object) ["count(*)" => 1],
436 | ]
437 | ]);
438 |
439 | $dispatcher = M::Mock(Dispatcher::class);
440 | $dispatcher->shouldReceive('fire')->with('angejia.pea.get', ['table' => 'user', 'db' => 'angejia']);
441 | $dispatcher->shouldReceive('fire')->with('angejia.pea.miss.awful', ['table' => 'user', 'db' => 'angejia']);
442 | $this->app->instance(Dispatcher::class, $dispatcher);
443 |
444 | $data = User::where('id', 1)->select(new Expression('count(*)'))->get();
445 | $this->assertEquals(1, $data[0]['count(*)']);
446 | }
447 |
448 | /**
449 | * 普通查询,全部命中缓存
450 | */
451 | public function testAllCachedNormalGet()
452 | {
453 | // 模拟命中主键 1 和 2,通过表级缓存返回
454 | $this->cache->shouldReceive('get')
455 | ->with([
456 | 'b7f40265619c7ef9fc80ff41bee68632',
457 | ])
458 | ->andReturn([
459 | 'b7f40265619c7ef9fc80ff41bee68632' => [
460 | (object) [ 'id' => 1, ],
461 | (object) [ 'id' => 2, ],
462 | ]
463 | ]);
464 |
465 | $this->cache->shouldReceive('get')
466 | ->with([
467 | '3558193cd9818af7fe4d2c2f5bd9d00f',
468 | '343a10e6c2480e111dd3e9e564eb7966',
469 | ])
470 | // 缓存中只有 id 为 1 的数据
471 | ->andReturn([
472 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
473 | '343a10e6c2480e111dd3e9e564eb7966' => (object) [ 'id' => 2, 'name' => '涛涛', ],
474 | ]);
475 |
476 | $users = User::where('status', 1)->take(2)->skip(1)->get();
477 | $user0 = $users[0];
478 | $this->assertEquals(1, $user0->id);
479 | }
480 |
481 | /**
482 | * 普通查询,部分命中缓存
483 | */
484 | public function testPartialCachedNormalGet()
485 | {
486 | $this->conn->shouldReceive('select')
487 | ->with('select * from "user" where "id" in (?)', [2], true)
488 | ->andReturn([
489 | (object) [ 'id' => 2, 'name' => '涛涛', ],
490 | ]);
491 |
492 | // 查询完成后需要将数据写入缓存
493 | $this->cache->shouldReceive('set')
494 | ->with([
495 | '343a10e6c2480e111dd3e9e564eb7966' => (object) [ 'id' => 2, 'name' => '涛涛', ],
496 | ]);
497 |
498 | // 模拟命中主键 1 和 2,通过表级缓存返回
499 | $this->cache->shouldReceive('get')
500 | ->with([
501 | 'b7f40265619c7ef9fc80ff41bee68632',
502 | ])
503 | ->andReturn([
504 | 'b7f40265619c7ef9fc80ff41bee68632' => [
505 | (object) [ 'id' => 1, ],
506 | (object) [ 'id' => 2, ],
507 | ]
508 | ]);
509 |
510 | $this->cache->shouldReceive('get')
511 | ->with([
512 | '3558193cd9818af7fe4d2c2f5bd9d00f',
513 | '343a10e6c2480e111dd3e9e564eb7966',
514 | ])
515 | // 缓存中只有 id 为 1 的数据
516 | ->andReturn([
517 | '3558193cd9818af7fe4d2c2f5bd9d00f' => (object) [ 'id' => 1, 'name' => '海涛', ],
518 | ]);
519 |
520 | $users = User::where('status', 1)->take(2)->skip(1)->get();
521 | $user0 = $users[0];
522 | $this->assertEquals(1, $user0->id);
523 | }
524 |
525 | /**
526 | * 普通查询,空结果集
527 | */
528 | public function testEmptyNormalGet()
529 | {
530 | // 模拟获取主键列表,返回空结果
531 | $this->conn->shouldReceive('select')
532 | ->with('select * from "user" where "status" = ? limit 2 offset 1', [1], true)
533 | ->andReturn([]);
534 |
535 | // 查询完成后需要将数据写入缓存
536 | $this->cache->shouldReceive('set')
537 | ->with([
538 | 'b7f40265619c7ef9fc80ff41bee68632' => [],
539 | ]);
540 |
541 | // 模拟未命中表级缓存
542 | $this->cache->shouldReceive('get')
543 | ->with([
544 | 'b7f40265619c7ef9fc80ff41bee68632',
545 | ])
546 | ->andReturn([]);
547 |
548 | $users = User::where('status', 1)->take(2)->skip(1)->get();
549 | $this->assertEquals(0, count($users));
550 | }
551 |
552 | /**
553 | * 获取当前查询缓存 key
554 | */
555 | public function testQueryCacheKey()
556 | {
557 | // 简单查询
558 | $key = User::where('id', 1)->key();
559 | $this->assertEquals([1 => '3558193cd9818af7fe4d2c2f5bd9d00f' ], $key);
560 | $key = User::whereIn('id', [1, 2])->key();
561 | $this->assertEquals([
562 | 1 => '3558193cd9818af7fe4d2c2f5bd9d00f',
563 | 2 => '343a10e6c2480e111dd3e9e564eb7966',
564 | ], $key);
565 |
566 | // 复杂查询
567 | $key = User::where('id', 2)->orderBy('id', 'desc')->key();
568 | $this->assertEquals('791b4459a33f6b3d4929b5b7ea9de084', $key);
569 | }
570 |
571 | /**
572 | * 刷新当前表所有缓存
573 | */
574 | public function testFlushCache()
575 | {
576 | $this->meta->shouldReceive('flushAll')->with('angejia', 'user');
577 |
578 | User::flush();
579 | }
580 | public function testEmptySimpleGet()
581 | {
582 | // 模拟获取主键列表,返回空结果
583 | $this->conn->shouldReceive('select')
584 | ->with('select * from "user" where "id" in (?, ?)', [1, 2], true)
585 | ->andReturn([
586 | (object) ['id' => 2, 'name' => '海涛'],
587 | ]);
588 |
589 | // 查询完成后需要将数据写入缓存
590 | $this->cache->shouldReceive('set')
591 | ->with([
592 | '3558193cd9818af7fe4d2c2f5bd9d00f' => [],
593 | '343a10e6c2480e111dd3e9e564eb7966' => (object) ['id' => 2, 'name' => '海涛'],
594 | ]);
595 |
596 | // 模拟未命中表级缓存
597 | $this->cache->shouldReceive('get')
598 | ->with([
599 | '3558193cd9818af7fe4d2c2f5bd9d00f',
600 | '343a10e6c2480e111dd3e9e564eb7966',
601 | ])
602 | ->andReturn([]);
603 |
604 | $users = User::whereIn('id', [1, 2])->get();
605 | $this->assertEquals(1, count($users));
606 | $user = $users[0];
607 | $this->assertEquals('海涛', $user->name);
608 | }
609 |
610 | public function testEmptySimpleHitGet()
611 | {
612 | // 模拟命中表级空缓存
613 | $this->cache->shouldReceive('get')
614 | ->with([
615 | '3558193cd9818af7fe4d2c2f5bd9d00f',
616 | ])
617 | ->andReturn([
618 | '3558193cd9818af7fe4d2c2f5bd9d00f' => [],
619 | ]);
620 |
621 | $users = User::where('id', 1)->get();
622 | $this->assertEquals(0, count($users));
623 | }
624 | }
625 |
626 | class User extends Model
627 | {
628 | protected $table = 'user';
629 | protected $needCache = true;
630 | public $timestamps = false;
631 | }
632 |
--------------------------------------------------------------------------------
/test/TestCase.php:
--------------------------------------------------------------------------------
1 | app = Container::getInstance();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------