はじめに
こんばんは。
今回も備忘録ブログです。
最近多いな。備忘録。
現在お仕事もらっている案件で、独自フレームワークを使用しているPHPプロジェクトをLaravelに載せ替えるという案件をやっています。
理由としてはよくある感じで、その独自フレームワークを保守できる人が居なくなっており、コア部分もバンバンnoticeでているみたいな状態だからのようです。
すでにそのプロジェクトのとあるディレクトリにLaravelプロジェクトが入っており、Sessionを使わないところ以外は部分的に載せ替えられているが、Sessionを使用する部分では、当然ですがLaravel側の求めるSessionの形ではないので、Sessionの共存が出来ないようでした。
今回無理やりですが、共存できるようにSessionHandlerを作成して、そのクラスを session_set_save_handler()
で指定して使用するようにしました。
前提として、SessionはDBを使用しています。
やったこと
1. 旧システムにlaravelをinstall
本当は Illuminate
の該当部分のみでよいのかもしれませんが、とりあえず全部入れてみました。。
この辺適当です。すいません。
$ composer require laravel/laravel
2. laravel用sessionテーブル作成
artisanのコマンドでsessionテーブルのマイグレーションファイルを作成して、実行します。
$ php artisan session:table
$ php artisan migrate
3. laravel側session設定
config/session.php
を設定します。
該当箇所は driver
cookie
の2つです。
実際は、.envに以下を追加するという修正です。
SESSION_DRIVER=database
SESSION_COOKIE=xxxxxxxxxxxxxxxx
4. laravel側session設定を旧プロジェクトに持っていく
config/session.php
の各設定を旧プロジェクトに定数として持っていきます。
以下はlaravel5.8のデフォルト設定です。
define("SESSION_NAME", "xxxxxxxxxxxxxxxxxxxxx");
define("SESSION_TIME", time() + (120 * 60));
define("SESSION_PATH", "/");
define("SESSION_DOMAIN", null);
define("SESSION_SECURE", false);
define("SESSION_HTTP_ONLY", true);
5. application keyと、cipherの設定を旧プロジェクトにコピーして持っていく
laravelプロジェクトを作成する際に 以下のコマンドでアプリケーションのkeyを作成すると思います。
$ php artisan key:generate
こちらのkeyを旧プロジェクトにdefineやconstなどで定義しておきます。
define('LARAVEL_APP_KEY', 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz');
また、 laravelの config/app.php
に設定されている cipher
の値を同じく定数化しておきます。
laravel5.8のデフォルトだと AES-256-CBC
になっていると思います。
define('LARAVEL_CIPHER', 'AES-256-CBC');
この2つは 暗号化されたsessionIdを作成するために必要です。
6. php.serialize_handlerをphp_serializeに変更
sessionがシリアライズされる方法をphpの serialize()
で実行された状態と同じにします。
ini_set('session.serialize_handler', 'php_serialize');
7. SessionHandlerを作成
<?php
namespace CompanyName\Session;
use CompanyName\Util\Util;
class AppSessionHandler implements \SessionHandlerInterface
{
@var
protected $decryptSessionId;
@var
protected $pdo;
@param
@param
public function __construct(string $decryptSessionId, \PDO $pdo)
{
$this->decryptSessionId = $decryptSessionId;
$this->pdo = $pdo;
}
@param
@param
@return
public function open($savePath, $name)
{
return true;
}
@param
@return
public function read($sessionId)
{
$stmt = $this->pdo->prepare("SELECT payload FROM sessions WHERE id = :id");
$stmt->bindValue(":id", $this->decryptSessionId);
$stmt->execute();
$results = $stmt->fetchColumn();
$stmt->closeCursor();
return is_null($results) ? '' : base64_decode($results);
}
@param
@param
@return
public function write($sessionId, $sessionData)
{
$sql =<<<SQL
INSERT INTO sessions (id, ip_address, user_agent, payload, last_activity)
VALUES(:id, :ip_address, :user_agent, :payload, :last_activity)
ON DUPLICATE KEY UPDATE last_activity = :last_activity, payload = :payload
SQL;
$payload = $this->getSavePayload($sessionId, $sessionData);
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(":id", $this->decryptSessionId);
$stmt->bindValue(":ip_address", Util::ip());
$stmt->bindValue(":user_agent", Util::userAgent());
$stmt->bindValue(":payload", base64_encode(serialize($payload)));
$stmt->bindValue(":last_activity", Util::currentTime());
$stmt->execute();
$results = $stmt->rowCount();
$stmt->closeCursor();
return ($results) ? true : false;
}
@param
@return
public function destroy($sessionId)
{
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id = :id");
$stmt->bindValue(":id", $this->decryptSessionId);
$stmt->execute();
$stmt->closeCursor();
return true;
}
@return
public function close()
{
return true;
}
@param
@return
public function gc($maxlifetime)
{
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE last_activity < :lastActivity");
$stmt->bindValue(":lastActivity", $maxlifetime);
$stmt->execute();
$stmt->closeCursor();
return true;
}
@param
@param
@return
protected function getSavePayload(string $sessionId, string $sessionData):array
{
$registeredPayload = $this->read($sessionId);
if (strlen($registeredPayload) >= 1) {
$registeredPayload = unserialize($registeredPayload);
} else {
$registeredPayload = $this->initPayload();
}
if (strlen($sessionData) >= 1) {
$payload = array_merge($registeredPayload, unserialize($sessionData));
} else {
$payload = $registeredPayload;
}
$payload['_previous'] = ['url' => Util::referer()];
return $payload;
}
@return
protected function initPayload():array
{
return $payload = [
'_token' => \Illuminate\Support\Str::random(40),
'_flash' => ['old' => [], 'new' => []]
];
}
}
対応した部分はここまでになります。
これらを設定したのが以下のコードです。
<?php
define("LARAVEL_APP_KEY", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
define("SESSION_NAME", "xxxxxxxxxxxxxxxxxxxxx");
define("SESSION_TIME", time() + (120 * 60));
define("SESSION_PATH", "/");
define("SESSION_DOMAIN", null);
define("SESSION_SECURE", false);
define("SESSION_HTTP_ONLY", true);
define("LARAVEL_CIPHER", "AES-256-CBC");
ini_set('session.serialize_handler', 'php_serialize');
$encryptValue = base64_decode(substr(LARAVEL_APP_KEY, 7));
$encrypter = new \Illuminate\Encryption\Encrypter($encryptValue, LARAVEL_CIPHER);
if (!isset($_COOKIE[SESSION_NAME])) {
$sessionId = \Illuminate\Support\Str::random(40);
$encryptSessionId = $encrypter->encrypt($sessionId, false);
setcookie(SESSION_NAME, $encryptSessionId, SESSION_TIME,SESSION_PATH, SESSION_DOMAIN, SESSION_SECURE, LARAVEL_CIPHER);
} else {
$decryptSessionId = $encrypter->decrypt($_COOKIE[SESSION_NAME], false);
$pdo = \CompanyName\Util\Db::getInstance(DBNAME, DBHOST, DBUSER, DBPASSWORD);
$sessionHandler = new \CompanyName\Session\AppSessionHandler($decryptSessionId, $pdo);
session_name(SESSION_NAME);
session_set_save_handler($sessionHandler, true);
}
これで、旧システムでSessionを使うような処理(ログインなど)を行っている状態で、laravelページに遷移後、 session()->all()
などを行うと旧プロジェクトでのSession情報が取れ、その逆も可能になりました。
なんか抜けもありそうだから、もうちょい調査しますが、とりあえずここまで。