如何从 PDO prepare 语句中获取结果
How to fetch results from a PDO prepare statement
我正在使用 PDO 准备语句 select 结果。
我的index.php文件是:
include('operations.php');
$userprofileobj = new operations();
if(isset($_SESSION['user_email']))
{
$results = $userprofileobj->verify_user('account', $_SESSION['user_email'])->fetch(PDO::FETCH_ASSOC);
echo $results['username'];
}
我的operations.php文件是:
<?php
include('userclass.php');
class operations extends userclass
{
public function verify_user($table_name, $user_email)
{
$stmt = $this->con->prepare("select * from " . $table_name . " where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
我正在尝试匹配电子邮件,结果应该在 index.php 中获取。但是我得到一个错误:
Fatal error: Call to a member function fetchColumn() on boolean in operations.php on line 84
$result = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$result = $result->execute(..)
您正在覆盖 $result。 $this->con->prepare(..) 将 $result 设置为 PDO 语句(参见 http://php.net/manual/en/pdo.prepare.php)。 PDO 语句对象具有方法 ->execute(...),returns 一个布尔值 (true/false) 以及 ->fetchColumn() 方法。当您执行 execute() 时,您将用 execute() 的结果覆盖您的 PDO 语句对象,它只是一个布尔值,根本没有任何方法。因此,这就是 $result 没有 ->fetchColumn() 方法的原因。
您的 result
变量(除了被覆盖)不是您认为的那样。这是一个 PDO 语句。
试试这个:
$stmt = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
if (false !== ($row = $stmt->fetchColumn()))
{
return $row;
}
但是,这只会 return 第一行的第一列。相反,您可能想要:
return $stmt->fetchAll(PDO::FETCH_ASSOC);
我把$result
改成了$stmt
,因为不是结果。这是一个声明对象。
原始问题
在您的 原始 代码(见下文)中,您正在用 execute
中的 return 覆盖它,这是一个布尔值。
// Old code (don't use this)
$result = $result->execute([
':user_email' => $user_email,
]);
//$result = TRUE|FALSE
if ($result->fetchColumn() !== false)
{
return $result;
}
然后您尝试调用布尔值的方法,但是,这不会起作用。但问题远不止于此。假设您不覆盖它。
// Old code (don't use this)
$result->execute([
':user_email' => $user_email,
]);
//$result = PDOStatment object.
if ($result->fetchColumn() !== false)
{
return $result;
}
现在结果仍然是您的 PDOStatement,这很好,但正如我所说,您没有保存获取的数据。这次你 return PDOStatement 对象。这不是你想要的。
然后,正如我之前所说,如果您保存它并 return 它,它仍然可能不是您想要的。因为fetchColumn()
一次只访问一行和一列。
但是我不知道你想要什么。也许这就是你想要的?在这种情况下,您的查询低于理想。也许您只想查看是否存在具有给定电子邮件的用户?在那种情况下,我会使用这个查询。
$result = $this->con->prepare("SELECT id FROM ".$table_name." WHERE username = :user_email");
$result->execute([
':user_email' => $user_email,
]);
// There isn't any need to check it (see below)
return $result->fetchColumn();
PDOStatement::fetchColumn() returns a single column from the next row of a result set or FALSE if there are no more rows.
我也可以通过你的东西判断,你的数据库设置可能是错误的。也就是说,如果你真的需要一个动态的table$table
。我可以这么说的原因是你不应该复制任何用户数据(或者任何数据,这被称为规范化),并且 table 动态意味着电子邮件可能分别存在于两个(或更多)tables.
如果不是这种情况,则不要使其动态化。为什么这是一个问题?好吧,想想如果用户现在更改他们的“电子邮件”会发生什么,因为它存在于两个 table 中(可能)。你必须在这两个地方更新它。但它比这更糟糕,因为它使您对电子邮件所做的任何事情都过于复杂。
没有看到您的 table 的架构,我只能推测它,以及如何修复它。但通常您会使用外键并将用户记录关联到该外键。然后使用 JOIN 您可以访问电子邮件而不会重复。
也就是说,在某些情况下这可能是可以接受的table,但我无法知道您的情况是否属实。一个简单的例子是用户和管理员(基本上是双用户系统)的单独 table。
安全
最后一件事是非常非常小心:
"select * from ".$table_name." where username = :user_email"
这里的问题是它对 SQL injection 开放。任何时候将变量连接到 SQL 中,都会为 注入 攻击打开大门。好吧,你可能会说我传递的是固定字符串 account
。这没问题,但在 失败点 没有验证。因此,也许在五个月后您会重复使用此代码,而忘记您从未验证过 table 名称。也许不是,但事实仍然是,如果用户数据可以进入该参数,那么您就无法针对 table 名称上的 注入 采取任何保护措施。它的可能性是存在的。
就这么简单:
public function verify_user($table_name,$user_email){
$allowed = ['account','users'];
if(!in_array($table_name, $allowed )) throw new Exception('Invalid table name');
}
现在看来,将 某些内容注入table 名称几乎是不可能的。此外,因为它采用相同的方法(在故障点),您永远不会失去这种保护。后者很容易匆忙复制一段代码并更改一些东西......你知道的。
只是我的两分钱。
更新
因此,即使用户输入进入 $table
的可能性很小,您也不能 100% 保证,因为在 verify_user
中,您无法知道数据来自何处,但您相信这不是用户输入。说到SQL注入,你不能说好this is ok,因为我只会以某种方式调用这个方法。它必须是 100% 防注入或尽可能接近。
你问为什么这很重要?想象一下。
$userprofileobj->verify_user('account --',$_SESSION['user_email']);
那两个小 --
就像 PHP 中的 //
,但是对于 SQL,他们注释掉了 SQL 中的其余行所以你的查询变成了这个。
"select * from account -- where username = :user_email"
或(本质上)
"select * from account"
所以我们刚刚修改了您的查询的内容。现在值得庆幸的是,在 PDO 中不可能一次 运行 两个查询。您可以在 MySqli 中完成(需要一些工作)。但出于安全原因,他们大多取消了这种能力。原因是这样的(或者更糟的是创建数据库用户)。
$userprofileobj->verify_user('account; DROP TABLE account --',$_SESSION['user_email']);
如果你可以做两个查询,你会这样做:
SELECT * FROM account
DROP TABLE account
无论如何,这是危险的事情,应该不惜一切代价避免。太懒了(我是一个 lazy 程序员,所以不要误会)把 table 名字放进去并不是你想在你的数据库之后给出的答案已被泄露,您已将用户数据暴露给第三方。这不是一个选择。
所有这一切:
if(!in_array($table_name, ['table1', 'table2', ...])) throw new Exception('Invalid table name');
如果“needle”$table_name
不在“haystack”中,则会抛出错误 - table 名称的固定列表。所以如果我这样做(使用我们上面的例子):
if(!in_array('account --', ['table1', 'table2', ...])) throw new Exception('Invalid table name');
它不会在我们的 table1
和 table2
列表中找到 account --
并且会爆炸,从而防止注入攻击。
我正在使用 PDO 准备语句 select 结果。
我的index.php文件是:
include('operations.php');
$userprofileobj = new operations();
if(isset($_SESSION['user_email']))
{
$results = $userprofileobj->verify_user('account', $_SESSION['user_email'])->fetch(PDO::FETCH_ASSOC);
echo $results['username'];
}
我的operations.php文件是:
<?php
include('userclass.php');
class operations extends userclass
{
public function verify_user($table_name, $user_email)
{
$stmt = $this->con->prepare("select * from " . $table_name . " where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
我正在尝试匹配电子邮件,结果应该在 index.php 中获取。但是我得到一个错误:
Fatal error: Call to a member function fetchColumn() on boolean in operations.php on line 84
$result = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$result = $result->execute(..)
您正在覆盖 $result。 $this->con->prepare(..) 将 $result 设置为 PDO 语句(参见 http://php.net/manual/en/pdo.prepare.php)。 PDO 语句对象具有方法 ->execute(...),returns 一个布尔值 (true/false) 以及 ->fetchColumn() 方法。当您执行 execute() 时,您将用 execute() 的结果覆盖您的 PDO 语句对象,它只是一个布尔值,根本没有任何方法。因此,这就是 $result 没有 ->fetchColumn() 方法的原因。
您的 result
变量(除了被覆盖)不是您认为的那样。这是一个 PDO 语句。
试试这个:
$stmt = $this->con->prepare("select * from ".$table_name." where username = :user_email");
$stmt->execute([
':user_email' => $user_email,
]);
if (false !== ($row = $stmt->fetchColumn()))
{
return $row;
}
但是,这只会 return 第一行的第一列。相反,您可能想要:
return $stmt->fetchAll(PDO::FETCH_ASSOC);
我把$result
改成了$stmt
,因为不是结果。这是一个声明对象。
原始问题
在您的 原始 代码(见下文)中,您正在用 execute
中的 return 覆盖它,这是一个布尔值。
// Old code (don't use this)
$result = $result->execute([
':user_email' => $user_email,
]);
//$result = TRUE|FALSE
if ($result->fetchColumn() !== false)
{
return $result;
}
然后您尝试调用布尔值的方法,但是,这不会起作用。但问题远不止于此。假设您不覆盖它。
// Old code (don't use this)
$result->execute([
':user_email' => $user_email,
]);
//$result = PDOStatment object.
if ($result->fetchColumn() !== false)
{
return $result;
}
现在结果仍然是您的 PDOStatement,这很好,但正如我所说,您没有保存获取的数据。这次你 return PDOStatement 对象。这不是你想要的。
然后,正如我之前所说,如果您保存它并 return 它,它仍然可能不是您想要的。因为fetchColumn()
一次只访问一行和一列。
但是我不知道你想要什么。也许这就是你想要的?在这种情况下,您的查询低于理想。也许您只想查看是否存在具有给定电子邮件的用户?在那种情况下,我会使用这个查询。
$result = $this->con->prepare("SELECT id FROM ".$table_name." WHERE username = :user_email");
$result->execute([
':user_email' => $user_email,
]);
// There isn't any need to check it (see below)
return $result->fetchColumn();
PDOStatement::fetchColumn() returns a single column from the next row of a result set or FALSE if there are no more rows.
我也可以通过你的东西判断,你的数据库设置可能是错误的。也就是说,如果你真的需要一个动态的table$table
。我可以这么说的原因是你不应该复制任何用户数据(或者任何数据,这被称为规范化),并且 table 动态意味着电子邮件可能分别存在于两个(或更多)tables.
如果不是这种情况,则不要使其动态化。为什么这是一个问题?好吧,想想如果用户现在更改他们的“电子邮件”会发生什么,因为它存在于两个 table 中(可能)。你必须在这两个地方更新它。但它比这更糟糕,因为它使您对电子邮件所做的任何事情都过于复杂。
没有看到您的 table 的架构,我只能推测它,以及如何修复它。但通常您会使用外键并将用户记录关联到该外键。然后使用 JOIN 您可以访问电子邮件而不会重复。
也就是说,在某些情况下这可能是可以接受的table,但我无法知道您的情况是否属实。一个简单的例子是用户和管理员(基本上是双用户系统)的单独 table。
安全
最后一件事是非常非常小心:
"select * from ".$table_name." where username = :user_email"
这里的问题是它对 SQL injection 开放。任何时候将变量连接到 SQL 中,都会为 注入 攻击打开大门。好吧,你可能会说我传递的是固定字符串 account
。这没问题,但在 失败点 没有验证。因此,也许在五个月后您会重复使用此代码,而忘记您从未验证过 table 名称。也许不是,但事实仍然是,如果用户数据可以进入该参数,那么您就无法针对 table 名称上的 注入 采取任何保护措施。它的可能性是存在的。
就这么简单:
public function verify_user($table_name,$user_email){
$allowed = ['account','users'];
if(!in_array($table_name, $allowed )) throw new Exception('Invalid table name');
}
现在看来,将 某些内容注入table 名称几乎是不可能的。此外,因为它采用相同的方法(在故障点),您永远不会失去这种保护。后者很容易匆忙复制一段代码并更改一些东西......你知道的。
只是我的两分钱。
更新
因此,即使用户输入进入 $table
的可能性很小,您也不能 100% 保证,因为在 verify_user
中,您无法知道数据来自何处,但您相信这不是用户输入。说到SQL注入,你不能说好this is ok,因为我只会以某种方式调用这个方法。它必须是 100% 防注入或尽可能接近。
你问为什么这很重要?想象一下。
$userprofileobj->verify_user('account --',$_SESSION['user_email']);
那两个小 --
就像 PHP 中的 //
,但是对于 SQL,他们注释掉了 SQL 中的其余行所以你的查询变成了这个。
"select * from account -- where username = :user_email"
或(本质上)
"select * from account"
所以我们刚刚修改了您的查询的内容。现在值得庆幸的是,在 PDO 中不可能一次 运行 两个查询。您可以在 MySqli 中完成(需要一些工作)。但出于安全原因,他们大多取消了这种能力。原因是这样的(或者更糟的是创建数据库用户)。
$userprofileobj->verify_user('account; DROP TABLE account --',$_SESSION['user_email']);
如果你可以做两个查询,你会这样做:
SELECT * FROM account
DROP TABLE account
无论如何,这是危险的事情,应该不惜一切代价避免。太懒了(我是一个 lazy 程序员,所以不要误会)把 table 名字放进去并不是你想在你的数据库之后给出的答案已被泄露,您已将用户数据暴露给第三方。这不是一个选择。
所有这一切:
if(!in_array($table_name, ['table1', 'table2', ...])) throw new Exception('Invalid table name');
如果“needle”$table_name
不在“haystack”中,则会抛出错误 - table 名称的固定列表。所以如果我这样做(使用我们上面的例子):
if(!in_array('account --', ['table1', 'table2', ...])) throw new Exception('Invalid table name');
它不会在我们的 table1
和 table2
列表中找到 account --
并且会爆炸,从而防止注入攻击。