LaravelのRedisで存在しないキーを取得しようとするとnullが返ってくるお話


false が返ってきたり null が返ってきたりする楽しい世界です。

前提

動作環境

  • Laradock
    • Laravel 6.12
    • Redis 5.0.3
    • phpredis 5.1.1

DBの中身

Redis の中には key1 のみが入っているとします。

127.0.0.1:6379> SET key1 value1
OK
127.0.0.1:6379> KEYS *
1) "key1"

要約

Redis

Redis で存在しないキーを指定すると、 nil が返ってきます。

127.0.0.1:6379> GET key1
"value1"
127.0.0.1:6379> GET key2
(nil)

PhpRedis

PhpRedis では、boolean 型の false が返ってきます。

$redis = new Redis();
$redis->connect('redis', 6379);
$value = $redis->get('key2');
var_dump($value);
string(6) "value1"
bool(false)

Laravel

Laravel で Redis に接続するときに PhpRedis を使っている場合、null 型の null が返ってきます。

use Illuminate\Support\Facades\Redis;

Redis::set('key1', 'value1');
$value = Redis::get('key1');  //--> "value1"
$value = Redis::get('key2');  //--> null

解説

Redis

Redis の GETMGET で存在しないキーを取得しようとすると、 nil が返ってきます。

"nil" という 文字列SET することはできますが、 nil という 特別な値SET することはできません。1

127.0.0.1:6379> GET key2
(nil)
127.0.0.1:6379> SET key3 nil
OK
127.0.0.1:6379> GET key3
"nil"

PhpRedis

PhpRedis の getmget で存在しないキーを取得しようとすると、 boolean 型の false が返ってきます。

このことは 公式ドキュメント にも明記されています。

Return value

String or Bool: If key didn't exist, FALSE is returned. Otherwise, the value related to this key is returned.

しかし、ここでぜひ思い出していただきたいのは、PHP では "0" や空文字と false は等しく扱われるということです。

// 値として "0" をセットしてみる。
$redis->set('key4', 0);
$val4 = $redis->get('key4');

// キーの存在チェックのつもり...
if (!$val4) {
    echo "key4は存在しない";
}

// 値として空文字をセットしてみる。
$redis->set('key5', "");
$val5 = $redis->get('key5');

// これもキーの存在チェックのつもり...
if (!$val5) {
    echo "key5は存在しない";
}

さて、結果はどうでしょうか?

key4は存在しない
key5は存在しない

これを防ぐには、 === 演算子による厳密比較しかありません。

// 正しいキーの存在チェック
if ($val4 === false) {
    echo "key4は存在しない";
}

Laravel

さて、ようやく本題です。

Laravel では、Redis にアクセスする際に PhpRedis を使うことができます。

PhpRedis を使っているのですから、 get()mget()false を返してきそうなものですが、実際には null が返ってきます。

その理由は Illuminate\Redis\Connections\PhpRedisConnection.php にあります。

PhpRedisConnection.php
public function get($key)
{
    $result = $this->command('get', [$key]);

    return $result !== false ? $result : null;
}

public function mget(array $keys)
{
    return array_map(function ($value) {
        return $value !== false ? $value : null;
    }, $this->command('mget', [$keys]));
}

取得した値が false かどうかをわざわざチェックして、 false だったときには null を返すようにしていることが分かります。

なぜこんな面倒なことをしているのか?

かつて PhpRedis が返す false をそのまま使った結果、キャッシュ周りで バグ が起きました。そのときに null を返すようにしたようです。

感想

Laravel で Redis にアクセスするときに PhpRedis を使っているからといって、PhpRedis と同じ結果になるとは限りません。

バグ修正時の ある開発者のコメント をもじって言うならば、

僕らは Laravel を使っているのであって、PhpRedis を直接使っているわけではない

といったところでしょうか。2

参考


  1. この Redis の動作は明瞭で、あとで見る PHP のゆるゆるな型システムから戻ると、とても気持ちよく感じます(笑) 

  2. nullを返すこと自体は別に構わないのですが、ドキュメントに記載されていないのは、少し不親切に感じるところです。フレームワークのソースコードを見れば分かるだろ!と言われれば、それまでですが...