因此,经过与PDO的大量斗争,我已经在这里缩小了问题的根源。当我将属性ATTR_EMULATE_PREPARES
设置为false时,我的插入查询运行时没有错误,但不会在数据库表中添加条目。
但是当我通过PhpMyAdmin运行查询或将ATTR_EMULATE_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();
}
编辑:以下是我的数据库架构的SQLFiddle,以及相关系统和组件的版本信息
编辑:感谢@RyanVincent搞清楚这一点。第一部分是我的数据库列superuser
被定义为tinyint(1)
,尽管它是一种常见的布尔存储数据类型,但在绑定值时需要PDO::PARAM_INT
。第二部分是当PDO驱动程序运行时PDO::ATTR_EMULATE_PREPARES
设置为false
,如果数据库返回错误消息,则不会抛出错误或异常,仅在本地PDO驱动程序时遇到问题。 PDOStatement::execute()
返回一个布尔值,表明查询是否成功。如果PDOStatement::errorCode()
返回false,则由开发人员手动检查PDOStatement::errorInfo()
和execute()
。这在事务期间尤为重要,因为如果其中一个语句失败,则回滚事务很重要。 PDO的一个恼人的怪癖是如果你设置了错误的数据类型I.E. PDO::PARAM_BOOL
代替PDO::PARAM_INT
,就像我一样,返回的errorInfo()
几乎是空的,无论如何都会让你摸不着头脑。
ATTR_EMULATE_PREPARES
设置为false
时,TL:DR ,使用其他错误捕获功能,如果某些功能无效,请检查并仔细检查您的数据类型。
答案 0 :(得分:3)
问题是,当emulate_prepares
用作false
时,查询将失败,并且PDO
不会引发任何异常。
TL;DR
:转到:测试设置和解释
TL;DR
:goto:测试和结果
TL;DR
:转到:结论
mysql
驱动程序:
named placeholders
,它只使用?
来标记bind variables
的位置。false
,如果它无法完成请求。结果是,当您PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION
时,PDO
有责任将其转换为Exception
。
通过PDO
执行查询时预计会发生什么情况:
prepare
:使用PDO
向named placeholders
发送查询。PDO
将其格式化为'?'并将其发送给mysql
驱动程序。mysql
驱动程序向它发送实际的mysql
服务器,该服务器对其进行处理,并返回说明它如何返回mysql
驱动程序的信息mysql
驱动程序将结果发送回PDO
。PDO
检查并根据需要将其转换为有用的结果或error codes
或exceptions
。可以看出,由于混乱发生,所以有比我们想要的更多的地方。
为了调试正在发生的事情,我想确切地知道mysql server
收到的内容和返回的内容。
为此,mysql server
提供了general logging for queries
,请参阅:5.2.3 The General Query Log。
我已提供实用程序来控制general log
的开启和关闭,以便写入log files
并写入mysql.general_log
表。
经过测试的代码:IMysqlGeneralLogging: Control the MySQL General Logging Facility
和class
。
调试和解释实际的O / P代码:
我提供了PHPUnit
测试脚本来运行O / P代码并检查。我正在使用它作为执行和报告'工具,而不是测试任何东西。
这真正解释了我发现并展示它的内容。花费了相当多的时间和相当多的“试验和错误”。重点在于错误'。 ; - )
/**
* Test Data for all tests...
*/
protected $guid = '420D4B65565311E5958000FFD7CBE75F';
protected $displayname = 'Example User 42';
protected $hash = '$2y$17$12345678901234567890121234567890123456789012345678942';
protected $salt = '$2y$17$1234567890123456789042$';
protected $email = 'example42@example.com';
protected $superuser = true;
/**
* Expected Results
* Must be static so they are not reset with each test
*/
protected static $rcExpected = array(
'prepare' => true,
'execute' => true,
'exception' => false,
'exceptionMsg' => '',
);
/**
* Actual results stored in here.
*/
protected static $rcActual = array(
'prepare' => false,
'execute' => false,
'exception' => false,
'exceptionMsg' => '',
);
使用的代码已更改为将结果存储在上述数组中。
// 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 general logging
到跟踪文件的代码:
$testFilename = $this->logging->newTraceFilename('Test_01_EmulatesOn_ExceptionOn'));
$this->logging->setLogFile($testFilename);
$this->logging->startLoggingToFile(); // start logging...
并停止记录到跟踪文件:
$this->logging->stopLogging();
$this->userPDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->userPDO->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
测试代码:Q32451215Test 01 EmulatesOn_ExpectsAllOk
结果:
预期/实际:
prepare : Expected: true, Actual: true
execute : Expected: true, Actual: true
exception : Expected: false, Actual: false
exceptionMsg : Expected: '', Actual: ''
跟踪日志:
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)
注意:
prepare
和execute
语句。有趣?测试代码:Q32451215Test 02A EmulatesOff__WithSuperUserAsBooleanParamType
未更改的绑定语句:
$stmt->bindValue(':superuser', $this->superuser, PDO::PARAM_BOOL);
结果:
预期/实际:
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,
)
跟踪日志:
150915 11:37:12 693 Prepare INSERT INTO `account`
(`guid`, `displayname`, `password_hash`, `password_salt`, `email`, `superuser`)
VALUES
(UNHEX(?), ?, ?, ?, ?, ?)
注意:
这些是unexpected
!; - /
prepare statement
!execute
命令发送到服务器。 PDO execute
语句返回false
但未提出exception
! errorCode
或errorInfo
消息。 这表示PDO
或mysql driver
不喜欢' PDO :: PARAM_BOOL`或提供给它的实际值。
SQL statement
失败的唯一方法是检查单个prepare
和execute
语句返回的结果!另外,你不知道为什么失败了!这使得调试有趣; - /
PDO::PARAM_INT
测试代码:Q32451215Test 02B EmulatesOff__WithSuperUserAsIntegerParamType
这是对代码的唯一更改:
$stmt->bindValue(':superuser', $this->superuser, PDO::PARAM_INT);
结果:
预期/实际:
prepare : Expected: true, Actual: true
execute : Expected: true, Actual: true
exception : Expected: false, Actual: false
exceptionMsg : Expected: '', Actual: ''
errorCode : '00000', ErrorMsg: ''
跟踪日志:
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
语句。PDO execute
语句返回true
errorCode
或errorInfo
消息。 奇怪的是,只有binding paramater
被更改了。
PDO::ATTR_ERRMODE
==> PDO::ERRMODE_EXCEPTION
错误总会引发异常。
绑定参数将尝试以合理的方式解释,而不会导致失败。
正常使用此模式。
PDO::ATTR_ERRMODE
==> PDO::ERRMODE_EXCEPTION
不会一直提出异常
绑定参数将完全按照请求进行解释。
false
声明返回PDO execute
。这是查询失败的唯一线索。
始终检查PDO prepare
和PDO execute
的返回 false 。