【问题标题】:How to fetch results from a PDO prepare statement如何从 PDO 准备语句中获取结果
【发布时间】:2019-03-05 01:17:27
【问题描述】:

我正在使用 PDO 准备语句来选择结果。

我的 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 中获取。但我得到了一个错误:

致命错误:在第 84 行的 operations.php 中的布尔值上调用成员函数 fetchColumn()

【问题讨论】:

    标签: php pdo prepared-statement


    【解决方案1】:

    您的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 $stmt->fetchAll(PDO::FETCH_ASSOC);
    

    我将$result 更改为$stmt,因为这不是结果。它是一个语句对象。

    原始问题

    在您的 原始 代码(见下文)中,您将使用来自 execute 的返回值覆盖它,这是一个布尔值。

    // Old code (don't use this)
    $result = $result->execute([
        ':user_email' => $user_email,
    ]);
    
    //$result = TRUE|FALSE
    
    if ($result->fetchColumn() !== false)
    {
        return $result;
    }
    

    然后您尝试调用 Boolean 的方法,这将不起作用。但问题远不止于此。假设您没有覆盖它。

    // Old code (don't use this)
    $result->execute([
        ':user_email' => $user_email,
    ]);
    
    //$result = PDOStatment object.
    
    if ($result->fetchColumn() !== false)
    {
        return $result;
    }
    

    现在结果仍然是您的 PDOStatement,这很好,但正如我所说,您没有保存获取的数据。这次您返回 PDOStatement 对象。这不是你想要的。

    然后,正如我之前所说,如果您确实保存并返回它,它可能仍然不是您想要的。因为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() 从结果集的下一行返回单个列,如果没有更多行,则返回 FALSE。

    我也可以从你的资料中看出,你的数据库设置可能是错误的。也就是说,如果你真的需要动态表$table。我可以这么说的原因是您不应该复制任何用户数据(或者实际上任何数据,这称为规范化),并且动态表意味着电子邮件可能单独存在于两个(或更多)表中。

    如果不是这样,那就不要让它动态化。为什么这是一个问题?好吧,想想如果用户现在更改他们的“电子邮件”会发生什么,因为它存在于两个表中(可能)。你必须在这两个地方更新它。但它比这更糟糕,因为它使您对电子邮件所做的任何事情都过于复杂。

    没有看到您的表的架构,我只能推测它,以及如何修复它。但通常你会使用外键并将用户记录与它相关联。然后使用 JOIN 即可访问电子邮件而无需重复。

    也就是说,在少数情况下这可能是可以接受的,但我无法知道您的情况是否属实。一个简单的例子是用户和管理员的单独表(基本上是两个用户系统)。

    安全

    最后要非常小心:

    "select * from ".$table_name." where username = :user_email"
    

    这里的问题是它对SQL injection 开放。任何时候将变量连接到 SQL 中,都会为 injection 攻击打开大门。好吧,你可能会说我正在传递一个罐装字符串account。这没问题,但在失败点没有验证。因此,也许在五个月内您重用此代码并忘记您从未验证过表名。也许不是,但事实仍然是,如果用户数据可以进入该参数,您将无法防止表名上的 injection。它的可能性是存在的。

    就这么简单:

      public function verify_user($table_name,$user_email){
         $allowed = ['account','users'];
         if(!in_array($table_name, $allowed )) throw new Exception('Invalid table name');
     }
    

    现在看,在表名中注入 东西几乎是不可能的。此外,因为它采用相同的方法(在故障点),您将永远不会失去这种保护。后者很容易赶时间并复制一段代码更改一些内容,然后......你知道的。

    只要我的两分钱。

    更新

    因此,即使用户输入进入$table 的可能性很小,您也不能保证 100%,因为在verify_user 中,您无法知道数据来自哪里,但您相信相信这不是用户输入。说到SQL注入,你不能说这没问题,因为我只会以某种方式调用这个方法。它必须是 100% 防注入或尽可能接近人类。

    你问为什么这很重要?想象一下。

       $userprofileobj->verify_user('account --',$_SESSION['user_email']);
    

    这两个小--s 类似于 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 程序员,所以不要误会)把表名放进去并不是你想要在你的数据库被破坏并且你暴露之后给出的答案用户数据给第三方。这不是一个选择。

    所有这些:

    if(!in_array($table_name, ['table1', 'table2', ...])) throw new Exception('Invalid table name');
    

    如果 "needle" $table_name 不在 "haystack" 中,则抛出错误 - 表名的固定列表。所以如果我这样做(使用我们上面的例子):

    if(!in_array('account --', ['table1', 'table2', ...])) throw new Exception('Invalid table name');
    

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

    【讨论】:

    • 感谢您的回复和建议。但它也不起作用。现在问题出在哪里?
    • But it is also not working. Where is the problem now 我怎么知道。什么不起作用。我不知道你的数据库是什么样的,你在搜索什么等等......
    • 我正在尝试从我的查询中获取行并且我已经更改了我的 operation.php
    • 好的。现在我明白了...感谢您的帮助。
    • 是的,你也可以这样做if(!in_array($table_name, ['ACCOUNT','PRODUCTS','CONTACTS'])) throw new Exception('Invalid table name'); 全部在一行上。没有那个,我会在答案中举一个例子。
    【解决方案2】:
    $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(...),它返回一个布尔值 (true/false) 以及 ->fetchColumn() 方法。当你执行你的 execute() 时,你正在用 execute() 的结果覆盖你的 PDO 语句对象,它只是一个布尔值,根本没有任何方法。因此,这就是 $result 没有 ->fetchColumn() 方法的原因。

    【讨论】:

    • 没关系
    • 谢谢你的回复..但是解决办法是什么。
    • 最简单的方法是将 $result = $result->execute(..) 变成 $result->execute(..),但是 @ArtisticPhoenix 在他们的回答中更详细地介绍了。跨度>
    • 这不是最简单的事情。这只是需要做的事情之一。改变这一点并不会改变它们返回 $result 的事实,这不是 fetch* 操作的“结果”。换句话说,修复将在指针移过第一行后返回 Statement 对象,并且您不能为 PDO 倒回光标。此外,当查询没有限制并拉取所有字段时使用fetchColumn,意味着查询不是最优的,或者他们使用了错误的获取方法。
    猜你喜欢
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 2015-10-29
    • 1970-01-01
    • 2017-03-26
    • 2021-05-03
    • 2011-01-15
    • 1970-01-01
    相关资源
    最近更新 更多