はじめに
こんばんは。 今回も備忘録ブログです。 最近多いな。備忘録。
現在お仕事もらっている案件で、独自フレームワークを使用している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 string */ protected $decryptSessionId; /** * @var \PDO */ protected $pdo; /** * AppSessionHandler constructor. * @param string $decryptSessionId * @param \PDO $pdo */ public function __construct(string $decryptSessionId, \PDO $pdo) { $this->decryptSessionId = $decryptSessionId; $this->pdo = $pdo; } /** * @param string $savePath * @param string $name * @return bool */ public function open($savePath, $name) { return true; } /** * @param string $sessionId * @return string */ 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 string $sessionId * @param string $sessionData * @return bool */ 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 string $sessionId * @return bool */ 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 bool */ public function close() { return true; } /** * @param int $maxlifetime * @return bool */ 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 string $sessionId * @param string $sessionData * @return array */ 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; } /** * payloadの初期化 * @return array */ 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情報が取れ、その逆も可能になりました。
なんか抜けもありそうだから、もうちょい調査しますが、とりあえずここまで。