php5.6からphp7.1にあげたら Failed to read session data: user (path: /var/lib/php/session) と怒られた

  • 3
    いいね
  • 0
    コメント

タイトル通り怒られてしまって困ったので原因調査してみました。
最終的にはSessionHandlerのreadメソッドで null が返っているのが原因でした。
この時php warningが出るので、 set_error_handler でエラー時に例外を投げるようにしていると例外が投げられてstatus 500になったりするかもしれません。

phpのmanual的には何も読まなかった時は空文字列を返さなければならないようです。
http://php.net/manual/ja/sessionhandlerinterface.read.php

読み込んだデータをエンコードした文字列を返します。 何も読まなかった場合は空文字列を返さなければなりません。 この値は、PHP が内部的に使うためだけのものであることに注意しましょう。

私が困ったときに実際に書いてた実装は下記のような感じでした。
sessionが見つからないときに null を返すような実装です。

    public function read($sessionId)
    {
        $sql = sprintf(
            'SELECT data FROM %s WHERE session_id = :session_id',
            self::TABLE_NAME
        );
        $bindValues = ['session_id' => $sessionId];
        $row = $this->slave->fetchOne($sql, $bindValues);

        return empty($row) ? null : $row['data'];
    }

再現環境

せっかくなのでphp5.6で問題ないけど、php7.1でエラーが出ることを確認しようと思います。
検証用に書いたコードはこちらにあります。
https://github.com/ara-ta3/sessionhander-in-php56-php71
dockerとdocker-composeが入っていれば make run で動くはずです。
私の環境のversion達は下記のような感じでした。

$docker -v
Docker version 1.13.1, build 092cba3

$docker-compose -v
docker-compose version 1.11.1, build 7c5d5e4

Serverを起動

make run を実行するとdocker for macを利用の場合は
http://localhost:81 にphp5.6実装のサーバが立ち上がり
http://localhost:82 にphp7.1実装のサーバが立ち上がります。

webの実装は下記のような感じです。
/root/web/index.php
https://github.com/ara-ta3/sessionhander-in-php56-php71/blob/master/web/index.php
(コメント加筆してます

<?php

class DummySession implements \SessionHandlerInterface {

    public function close()
    {
        return true;
    }

    public function destroy($session_id)
    {
        return true;
    }

    public function gc($maxlifetime)
    {
        return true;
    }

    public function open($save_path, $name)
    {
        return true;
    }

    public function read($session_id)
    {
        // 読めなかった場合、空文字列を返すべきなのでホントはnullは返さない
        return null;
    }

    public function write($session_id, $session_data)
    {
        return true;
    }
}


function main() {
    // error_handlerを設定してphp warningが出た際に例外を投げるようにしている
    set_error_handler(
        function ($errno, $errstr, $errfile, $errline) {
            throw new ErrorException(
                $errstr, 0, $errno, $errfile, $errline
            );
        }
    );
    session_set_save_handler(new DummySession());
    session_start();
    echo "OK";
}

main();

リクエストを投げる

curlで81番Port(php56)と、82番Port(php71)にリクエストを投げてみます。

$curl -i localhost:81
HTTP/1.1 200 OK
Date: Sat, 25 Feb 2017 13:18:15 GMT
Server: Apache/2.2.15 (CentOS)
X-Powered-By: PHP/5.6.30
Set-Cookie: PHPSESSID=23oirg7ujc7eqvvuqmciv8e2r5; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 2
Connection: close
Content-Type: text/html; charset=UTF-8

OK

$curl -i localhost:82
HTTP/1.0 500 Internal Server Error
Date: Sat, 25 Feb 2017 13:18:17 GMT
Server: Apache/2.2.15 (CentOS)
X-Powered-By: PHP/7.1.2
Set-Cookie: PHPSESSID=qj7nhva6j4ub01726kfoqmmjcj; path=/
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8

というようにphp71の環境では例外が出てしまいました。
ログを見るとタイトル通りな感じでした。

php71_1  | [Sat Feb 25 22:18:17 2017] [error] [client 172.20.0.1] PHP Fatal error:  Uncaught ErrorException: session_start(): Failed to read session data: user (path: /var/lib/php/session) in /var/www/html/index.php:46\nStack trace:\n#0 [internal function]: {closure}(2, 'session_start()...', '/var/www/html/i...', 46, Array)\n#1 /var/www/html/index.php(46): session_start()\n#2 /var/www/html/index.php(50): main()\n#3 {main}\n  thrown in /var/www/html/index.php on line 46

まとめ

  • SessionHandlerの実装でreadメソッドでnullを返すとphp warningが出る

という感じでした。
ぐぐったら下記ページにたどり着いてすぐ原因がわかったのでよかったです。
http://stackoverflow.com/questions/41540302/failed-to-read-session-data-on-php7-1