由于CPanel即将全面升级到PHP8以上的版本,并不再支援以下的版本所以目前再对网站进行升级準备
网站利用 session_set_save_handler 将 session 储存在 mysql 中升级后出现以下错误讯息PHP Warning: session_start(): Failed to read session data: user (path: {主机端路径}) in {引用档案路径} on line 4原始码如下:
<?php
require_once dirname(__FILE__)."/define.php"; //将资料库参数设定为静态值的档案
function getConnection(){
$conn = new PDO(DB_TYPE.\':host=\'.DB_HOST.\';dbname=\'.DB_NAME, DB_USER, DB_PASSWD, array(PDO::MYSQL_ATTR_INIT_COMMAND=>"SET NAMES\'utf8\';"));
$conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$conn->query(\'SET NAMES UTF8\');
return $conn;
}
function sess_open($save_path, $session_name){
$dbh = getConnection();
try{
$dbh->query("SELECT 1");
}catch(PDOException $e){
die("MySQL Error:".$e->getMessage());
}
return true;
}
function sess_close(){
return true;
}
function sess_read($key){
$dbh = getConnection();
$sql = "SELECT `value` FROM `db_session` WHERE `sesskey`=:sesskey AND `expiry`>UNIX_TIMESTAMP()";
$sth = $dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->execute();
$data = $sth->fetchAll();
if(count($data) > 0) return $data[0]["value"];
return false;
}
function sess_write($key, $val){
$dbh = getConnection();
$expiry = time() + SESS_LIFE;
$sql = "INSERT INTO `db_session` (`sesskey`, `expiry`, `value`) VALUES (:sesskey, :expiry1, :value1) ON DUPLICATE KEY UPDATE `expiry`=:expiry2,`value`=:value2";
$sth = $dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->bindParam(":expiry1", $expiry, PDO::PARAM_INT);
$sth->bindParam(":value1", $val, PDO::PARAM_STR);
$sth->bindParam(":expiry2", $expiry, PDO::PARAM_INT);
$sth->bindParam(":value2", $val, PDO::PARAM_STR);
$sth->execute();
return ($sth->errorCode() == "00000")?true:false;
}
function sess_destroy($key){
$dbh = getConnection();
$sql = "DELETE FROM `db_session` WHERE `sesskey`=:sesskey";
$sth = $dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->execute();
return ($sth->errorCode() == "00000")?true:false;
}
function sess_gc($maxlifetime){
$dbh = getConnection();
$sql = "DELETE FROM `db_session` WHERE `expiry`<UNIX_TIMESTAMP()";
$sth = $dbh->prepare($sql);
$sth->execute();
return ($sth->errorCode() == "00000")?true:false;
}
ini_set(\'session.gc_maxlifetime\', SESS_LIFE);
ini_set(\'session.gc_probability\', \'1\');
ini_set(\'session.gc_divisor\', \'1\');
session_set_save_handler("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");
?>
查了一下,PHP目前倾向 session_set_save_handler 不支援两个以上的参数有修改成下面的进行测试,依然不行
<?php
require_once dirname(__FILE__)."/define.php"; //将资料库参数设定为静态值的档案
class SysSession implements SessionHandlerInterface
{
private $dbh;
function __construct()
{
$conn = new PDO(DB_TYPE.\':host=\'.DB_HOST.\';dbname=\'.DB_NAME, DB_USER, DB_PASSWD, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES\'utf8\';"));
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$conn->query(\'SET NAMES UTF8\');
$this->dbh = $conn;
}
public function open($savePath, $sessionName): bool
{
try {
$this->dbh->query("SELECT 1");
} catch (PDOException $e) {
//die("MySQL Error:".$e->getMessage());
return false;
}
return true;
}
public function close(): bool
{
$this->dbh = null;
return true;
}
public function read($key): string | false
{
$sql = "SELECT `value` FROM `db_session` WHERE `sesskey`=:sesskey AND `expiry`>UNIX_TIMESTAMP()";
$sth = $this->dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->execute();
$data = $sth->fetchAll();
return count($data) ? $data[0]["value"] : false;
}
public function write($key, $val): bool
{
$expiry = time() + SESS_LIFE;
$sql = "REPLACE INTO `db_session` (`sesskey`, `expiry`, `value`) VALUES (:sesskey, :expiry, :value)";
$sth = $this->dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->bindParam(":expiry", $expiry, PDO::PARAM_INT);
$sth->bindParam(":value", $val, PDO::PARAM_STR);
$sth->execute();
return ($sth->errorCode() == "00000") ? true : false;
}
public function destroy($key): bool
{
$sql = "DELETE FROM `db_session` WHERE `sesskey`=:sesskey";
$sth = $this->dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->execute();
return ($sth->errorCode() == "00000") ? true : false;
}
public function gc($maxlifetime): int | false
{
$sql = "DELETE FROM `db_session` WHERE `expiry`<UNIX_TIMESTAMP()";
$sth = $this->dbh->prepare($sql);
$sth->execute();
return ($sth->errorCode() == "00000") ? true : false;
}
}
ini_set(\'session.gc_maxlifetime\', SESS_LIFE);
ini_set(\'session.gc_probability\', \'1\');
ini_set(\'session.gc_divisor\', \'1\');
$handler = new SysSession();
session_set_save_handler($handler, true);
?>
这边是引用的测试档
<?php
require_once dirname(__FILE__)."/include/session_mysql.php";
session_start(); //报错的地方
$a = isset($_SESSION["a"])?(int)$_SESSION["a"]:1;
$_SESSION["a"] = $a + 1;
echo $a;
?>
请问我是哪边写错了吗?
1 个回答
2
zivzhong
iT邦研究生 4 级 ‧ 2025-01-20 17:53:55
最佳解答
参考一下 Chatgpt 说:
根据您提供的情况,问题主要出在自订的 session handler 无法正确读取或处理会话数据,导致 session_start()
失败。以下是详细的分析与解决方案:
问题分析
-
PHP 8 中
SessionHandlerInterface
的要求:- 在 PHP 8 中,
SessionHandlerInterface::read
方法必须返回一个字符串。如果没有读取到会话数据,应返回空字符串\'\'
,而不是false
。返回false
会被视为读取失败,导致session_start()
发出警告。
- 在 PHP 8 中,
-
您的代码中
read
方法的实现:- 在类基础的实现中,
read
方法在找不到会话数据时返回false
,这不符合 PHP 8 的要求。 - 在过程式的实现中,
sess_read
方法也是类似地在找不到数据时返回false
。
- 在类基础的实现中,
-
其他潜在问题:
- 确认资料库连线是否正常,并且
db_session
表的结构是否符合预期。 - 检查
SET NAMES \'utf8\';
是否正确执行,确保字符集设定无误。
- 确认资料库连线是否正常,并且
解决方案
1. 修改 read
方法的返回值
将 read
方法在找不到会话数据时返回空字符串 \'\'
,而不是 false
。这样可以满足 PHP 8 的要求,避免 session_start()
发出警告。
类基础的实现修改:
public function read($key): string
{
$sql = "SELECT `value` FROM `db_session` WHERE `sesskey`=:sesskey AND `expiry`>UNIX_TIMESTAMP()";
$sth = $this->dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->execute();
$data = $sth->fetchAll(PDO::FETCH_ASSOC);
return count($data) ? $data[0]["value"] : \'\';
}
过程式的实现修改:
function sess_read($key){
$dbh = getConnection();
$sql = "SELECT `value` FROM `db_session` WHERE `sesskey`=:sesskey AND `expiry`>UNIX_TIMESTAMP()";
$sth = $dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->execute();
$data = $sth->fetchAll(PDO::FETCH_ASSOC);
if(count($data) > 0) {
return $data[0]["value"];
}
return \'\'; // 返回空字符串而不是 false
}
2. 确保其他方法的正确性
-
write
方法:- 确保
write
方法返回布林值true
或false
,以表示写入是否成功。
- 确保
-
gc
方法:-
gc
方法应该返回布林值true
或false
,而不是int | false
。
-
3. 确认资料库连线与表结构
- 确认
db_session
表中有sesskey
,expiry
, 和value
这些栏位,并且sesskey
是主键或有适当的索引。 - 确认资料库连线参数 (
DB_TYPE
,DB_HOST
,DB_NAME
,DB_USER
,DB_PASSWD
) 正确无误。 - 确认
SESS_LIFE
常数已正确定义,并设置为适当的会话存活时间。
4. 完整範例修正后的类基础实现
以下是修正后的类基础实现範例:
<?php
require_once dirname(__FILE__)."/define.php"; // 将资料库参数设定为静态值的档案
class SysSession implements SessionHandlerInterface
{
private $dbh;
function __construct()
{
try {
$this->dbh = new PDO(
DB_TYPE . \':host=\' . DB_HOST . \';dbname=\' . DB_NAME,
DB_USER,
DB_PASSWD,
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES \'utf8\';")
);
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
}
public function open($savePath, $sessionName): bool
{
try {
$this->dbh->query("SELECT 1");
} catch (PDOException $e) {
return false;
}
return true;
}
public function close(): bool
{
$this->dbh = null;
return true;
}
public function read($key): string
{
$sql = "SELECT `value` FROM `db_session` WHERE `sesskey`=:sesskey AND `expiry`>UNIX_TIMESTAMP()";
$sth = $this->dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->execute();
$data = $sth->fetchAll(PDO::FETCH_ASSOC);
return count($data) ? $data[0]["value"] : \'\';
}
public function write($key, $val): bool
{
$expiry = time() + SESS_LIFE;
$sql = "REPLACE INTO `db_session` (`sesskey`, `expiry`, `value`) VALUES (:sesskey, :expiry, :value)";
$sth = $this->dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
$sth->bindParam(":expiry", $expiry, PDO::PARAM_INT);
$sth->bindParam(":value", $val, PDO::PARAM_STR);
return $sth->execute();
}
public function destroy($key): bool
{
$sql = "DELETE FROM `db_session` WHERE `sesskey`=:sesskey";
$sth = $this->dbh->prepare($sql);
$sth->bindParam(":sesskey", $key, PDO::PARAM_STR, 32);
return $sth->execute();
}
public function gc($maxlifetime): bool
{
$sql = "DELETE FROM `db_session` WHERE `expiry`<UNIX_TIMESTAMP()";
$sth = $this->dbh->prepare($sql);
return $sth->execute();
}
}
ini_set(\'session.gc_maxlifetime\', SESS_LIFE);
ini_set(\'session.gc_probability\', \'1\');
ini_set(\'session.gc_divisor\', \'1\');
$handler = new SysSession();
session_set_save_handler($handler, true);
session_start();
?>
5. 测试档案
确保在测试档案中正确引用并启动会话:
<?php
require_once dirname(__FILE__)."/include/session_mysql.php";
// 确保 session_start() 在设置 session handler 之后呼叫
// 已在 session_mysql.php 中呼叫
$a = isset($_SESSION["a"]) ? (int)$_SESSION["a"] : 1;
$_SESSION["a"] = $a + 1;
echo $a;
?>
总结
主要问题在于 read
方法返回了 false
而不是空字符串,这导致 PHP 8 对自订 session handler 的要求不满足。通过修改 read
方法的返回值,并确保其他方法的实现符合 PHP 8 的要求,应该可以解决您遇到的问题。
如果在进行上述修改后仍然遇到问题,建议检查以下项目:
- 确认 PHP 错误日誌中是否有更多详细的错误资讯。
- 确认资料库连线和查询是否正常。
- 确认
db_session
表的结构和数据是否正确。
希望这些建议能够帮助您顺利完成网站的升级!
-
1 -
-
迷路
iT邦研究生 5 级 ‧
2025-01-21 10:01:39
修改read后就可以用了,非常感谢
修改