如何从 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');

它不会在我们的 table1table2 列表中找到 account -- 并且会爆炸,从而防止注入攻击。