Php 准备好的插入物不';如果ATTR\u EMULATE\u PREPARES为false,则不会执行任何操作

Php 准备好的插入物不';如果ATTR\u EMULATE\u PREPARES为false,则不会执行任何操作,php,mysql,pdo,Php,Mysql,Pdo,因此,在与PDO进行了大量斗争之后,我已经缩小了问题的根源。当我将属性ATTR\u EMULATE\u PREPARES设置为false时,我的插入查询运行时不会出错,但不会向数据库表中添加条目 但是,当我通过PhpMyAdmin或将ATTR\u EMULATE\u PREPARES设置为true运行查询时,它将成功执行 这让我有些沮丧,因为应该没有什么好的理由说明它不起作用 这是我通过PhpMyAdmin直接执行的查询 INSERT INTO account (guid, displaynam

因此,在与PDO进行了大量斗争之后,我已经缩小了问题的根源。当我将属性
ATTR\u EMULATE\u PREPARES
设置为false时,我的插入查询运行时不会出错,但不会向数据库表中添加条目

但是,当我通过PhpMyAdmin或将
ATTR\u EMULATE\u PREPARES
设置为true运行查询时,它将成功执行

这让我有些沮丧,因为应该没有什么好的理由说明它不起作用

这是我通过PhpMyAdmin直接执行的查询

INSERT INTO account (guid, displayname, password_hash, password_salt, email, superuser) VALUES (UNHEX('fcfd7f2355f211e5acfd2174e316c493'), 'bob', 'test', 'test', 'test', 1);
以下是相关的代码片段

$db = null;
try
{
    $db = new PDO($pdo_connectionstring, $pdo_username, $pdo_password);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
catch (PDOException $ex)
{
    die('[FATAL ERROR] Could not connect to the Database: '.$ex->getMessage());
}

try
{
    $stmt = $db->prepare("INSERT INTO `account` (`guid`, `displayname`, `password_hash`, `password_salt`, `email`, `superuser`) VALUES (UNHEX(:guid), :displayname, :passwordhash, :passwordsalt, :email, :superuser);");

    $stmt->bindValue(':guid', $guid, PDO::PARAM_STR);
    $stmt->bindValue(':displayname', $displayname, PDO::PARAM_STR);
    $stmt->bindValue(':passwordhash', $hash, PDO::PARAM_STR);
    $stmt->bindValue(':passwordsalt', $salt, PDO::PARAM_STR);
    $stmt->bindValue(':email', $email, PDO::PARAM_STR);
    $stmt->bindValue(':superuser', $superuser, PDO::PARAM_BOOL);

    $stmt->execute();
}
catch (Exception $ex)
{
    $jsonresult['generalerror'] = 'Failed to create user. Please contact your GM: ' . $ex->getMessage();
}
编辑:这是我的数据库架构的一个示例,以及相关系统和组件的版本信息

  • Debian 6.0.10在1和1的共享主机上运行
  • PHP 5.4.44
  • MySQL服务器5.5.44-0+deb7u1日志
编辑:感谢@RyanVincent解决了这个问题。第一部分是,我的数据库列
superuser
被定义为
tinyint(1)
,尽管它是一种常见的布尔存储数据类型,但在绑定值时需要
PDO::PARAM_INT
。第二部分是,当PDO驱动程序运行时,
PDO::ATTR_EMULATE_PREPARES
设置为
false
时,只有当本地PDO驱动程序遇到问题时,如果数据库返回错误消息,它不会抛出错误或异常
PDOStatement::execute()
返回一个关于查询是否成功的布尔值。如果
execute()
返回false,则由开发人员手动检查
PDOStatement::errorCode()
PDOStatement::errorInfo()
。这在事务期间尤其值得注意,因为如果其中一条语句失败,回滚事务非常重要。PDO的一个恼人的怪癖是,如果您设置了错误的数据类型,即
PDO::PARAM_BOOL
而不是像我那样设置了
PDO::PARAM_INT
,那么返回的
errorInfo()
将几乎是空的,让您对到底出了什么问题感到挠头


TL:DR当将
ATTR\u EMULATE\u
设置为
false
时,使用额外的错误捕获,如果某些错误不起作用,检查并仔细检查您的数据类型。

问题是,当使用
emulate_
as
false
时,查询将失败,并且不会从
PDO
引发异常

TL;DR
:转到:测试设置和说明

TL;DR
:转到:测试和结果

TL;DR
:转到:结论

PDO准备的查询处理概述
mysql
驱动程序:

  • 它不理解
    命名占位符
    ,它只使用
    标记
    绑定变量的位置
  • 如果成功,每个命令都会返回有用信息,如果无法完成请求,则返回
    false
这样做的结果是,当您有
PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION
时,
PDO
负责将其转换为
异常

通过
PDO执行查询时预期发生的情况概述:

  • prepare
    :使用
    命名占位符将查询发送到
    PDO
  • PDO
    使用“?”对其进行格式化,并将其发送到
    mysql
    驱动程序
  • mysql
    驱动程序将实际的
    mysql
    服务器发送给它,该服务器对其进行处理,并将解释其如何运行的信息返回给
    mysql
    驱动程序
  • mysql
    驱动程序将结果发送回
    PDO
  • PDO
    根据需要检查并将其转换为有用的结果或
    错误代码
    异常
可以看出,有比我们希望的更多的地方会发生混乱

为了调试正在发生的事情,我想确切地知道
mysql服务器
接收到了什么以及返回了什么

为此,mysql服务器提供了查询的常规日志记录,请参见:

我提供了一些实用程序来控制打开和关闭
常规日志
,用于写入
日志文件
和写入
mysql.general\u log

测试代码:和

调试并解释实际O/p代码:

我已经提供了
PHPUnit
测试脚本来运行O/p代码并进行检查。我将它用作“执行和报告”工具,而不是测试任何东西

这真的是在解释我发现了什么并演示了它。它花费了相当多的时间和相当多的“反复试验”。重点是“错误”。;-)

测试设置和说明 使用的代码已更改,以将结果存储在上述数组中

    // these change for each test...
    $this->userPDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $this->userPDO->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    try
    {
        self::$rcActual['exception'] = false;

        $stmt = $this->userPDO->prepare(
                   "INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX(:guid), :displayname,  :passwordhash,   :passwordsalt,   :email,  :superuser);");


        self::$rcActual['prepare'] = $stmt !== false; // record result

        $stmt->bindValue(':guid',         $this->guid,         PDO::PARAM_STR);
        $stmt->bindValue(':displayname',  $this->displayname, PDO::PARAM_STR);
        $stmt->bindValue(':passwordhash', $this->hash,         PDO::PARAM_STR);
        $stmt->bindValue(':passwordsalt', $this->salt,         PDO::PARAM_STR);
        $stmt->bindValue(':email',        $this->email,        PDO::PARAM_STR);
        $stmt->bindValue(':superuser',    $this->superuser,   PDO::PARAM_BOOL);

        self::$rcActual['execute'] = $stmt->execute(); // record result

        $this->assertTrue(self::$rcActual['execute']);
        $this->assertFalse(self::$rcActual['exception']);
    }
    catch (\Exception $e) {
        self::$rcActual['exception'] = true;
        self::$rcActual['exeptionMsg'] = $e->getCode() .' : '. $e->getMessage();
        $this->assertTrue(self::$rcActual['exception']);
    }
启动跟踪文件的mysql常规日志记录的代码:

    $testFilename = $this->logging->newTraceFilename('Test_01_EmulatesOn_ExceptionOn'));
    $this->logging->setLogFile($testFilename);
    $this->logging->startLoggingToFile(); // start logging...
    $this->logging->stopLogging();
并停止记录到跟踪文件:

    $testFilename = $this->logging->newTraceFilename('Test_01_EmulatesOn_ExceptionOn'));
    $this->logging->setLogFile($testFilename);
    $this->logging->startLoggingToFile(); // start logging...
    $this->logging->stopLogging();
测试和结果:
测试系列1-标准PDO(模拟): 测试1:一切正常。。。 测试代码:

结果:

预期/实际:

prepare   : Expected:  true, Actual: true
execute   : Expected:  true, Actual: true
exception : Expected:  false, Actual: false
exceptionMsg : Expected:  '', Actual: ''
prepare   : Expected:  true, Actual: true
execute   : Expected:  true, Actual: false
exception : Expected:  false, Actual: false
exceptionMsg : Expected:  '', Actual: ''
errorCode : '00000', ErrorMsg: array (
  0 => '00000',
  1 => NULL,
  2 => NULL,
)
prepare   : Expected:  true, Actual: true
execute   : Expected:  true, Actual: true
exception : Expected:  false, Actual: false
exceptionMsg : Expected:  '', Actual: ''
errorCode : '00000', ErrorMsg: ''
跟踪日志:

150914 14:15:30   569 Query INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX('420D4B65565311E5958000FFD7CBE75F'), 'Example User 42',  '$2y$17$12345678901234567890121234567890123456789012345678942',   '$2y$17$1234567890123456789042$',   'example42@example.com',  1)
150915 11:37:12   693 Prepare   INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX(?), ?,  ?,   ?,   ?,  ?)
150915 12:06:07   709 Prepare   INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX(?), ?,  ?,   ?,   ?,  ?)

      709 Execute   INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX('420D4B65565311E5958000FFD7CBE75F'), 'Example User 42',  '$2y$17$12345678901234567890121234567890123456789012345678942',   '$2y$17$1234567890123456789042$',   'example42@example.com',  1)
注:

  • 有一个单独的
    prepare
    execute
    语句发送到服务器。有趣

测试系列2-PDO(模拟关闭): 测试2A:与测试1相同的设置,除了模拟关闭 测试代码:

未更改的bind语句:

 $stmt->bindValue(':superuser',    $this->superuser,   PDO::PARAM_BOOL);
结果:

预期/实际:

prepare   : Expected:  true, Actual: true
execute   : Expected:  true, Actual: true
exception : Expected:  false, Actual: false
exceptionMsg : Expected:  '', Actual: ''
prepare   : Expected:  true, Actual: true
execute   : Expected:  true, Actual: false
exception : Expected:  false, Actual: false
exceptionMsg : Expected:  '', Actual: ''
errorCode : '00000', ErrorMsg: array (
  0 => '00000',
  1 => NULL,
  2 => NULL,
)
prepare   : Expected:  true, Actual: true
execute   : Expected:  true, Actual: true
exception : Expected:  false, Actual: false
exceptionMsg : Expected:  '', Actual: ''
errorCode : '00000', ErrorMsg: ''
跟踪日志:

150914 14:15:30   569 Query INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX('420D4B65565311E5958000FFD7CBE75F'), 'Example User 42',  '$2y$17$12345678901234567890121234567890123456789012345678942',   '$2y$17$1234567890123456789042$',   'example42@example.com',  1)
150915 11:37:12   693 Prepare   INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX(?), ?,  ?,   ?,   ?,  ?)
150915 12:06:07   709 Prepare   INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX(?), ?,  ?,   ?,   ?,  ?)

      709 Execute   INSERT INTO `account`
                            (`guid`,       `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
                    VALUES
                            (UNHEX('420D4B65565311E5958000FFD7CBE75F'), 'Example User 42',  '$2y$17$12345678901234567890121234567890123456789012345678942',   '$2y$17$1234567890123456789042$',   'example42@example.com',  1)
注:

这些都是出乎意料的
-/

  • 跟踪日志仅包含
    准备语句
  • 没有向服务器发送
    execute
    命令
  • PDO execute
    语句返回