草庐IT

php - 如果ATTR_EMULATE_PREPARES为false,则Prepared Insert不会执行任何操作

coder 2023-10-11 原文

所以在和PDO进行了大量的斗争之后,我缩小了我问题的根源。当我将属性ATTR_EMULATE_PREPARES设置为false时,我的insert查询将毫无错误地运行,但不会向数据库表中添加条目。
但是,当我通过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,以及有关系统和组件的版本信息
Debian6.0.10运行在1和1的共享主机上
菲律宾比索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()返回关于查询是否成功的布尔值。如果PDOStatement::errorCode()返回false,则由开发人员手动检查PDOStatement::errorInfo()execute()。这在事务处理期间特别值得注意,因为如果其中一个语句失败,回滚事务非常重要。pdo的一个恼人的怪癖是,如果您设置了错误的数据类型,即PDO::PARAM_BOOL而不是像我那样PDO::PARAM_INT,那么返回的errorInfo()将几乎是空的,这会让您挠头到底出了什么问题。
tl:dr在将ATTR_EMULATE_PREPARES设置为false时,使用额外的错误捕获,如果有问题不起作用,请检查并重新检查数据类型。

最佳答案

问题是,当使用emulate_prepares作为false时,查询将失败,并且不会从PDO引发异常。
TL;DR:转到:测试设置和说明
TL;DR:转到:测试和结果
TL;DR:转到:结论
pdo准备查询处理综述
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 codesexceptions
可以看到,有比我们想象的更多的地方,让混乱发生。
为了调试正在发生的事情,我想知道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 Facilityclass
调试并解释实际的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();

测试和结果:
测试系列1-标准PDO(在上模拟):
    $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)

注:
没有单独的prepareexecute语句发送到服务器。有趣吗?
测试系列2-PDO(模拟关闭):
测试2a:与测试1相同的设置,除了模拟关闭
测试代码: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
未返回异常的errorCodeerrorInfo消息。
这表示PDOmysql driver不喜欢“pdo::param_bool”或提供给它的实际值。
检查SQL statement失败的唯一方法是检查单个prepareexecute语句返回的结果!而且,你也不知道为什么会失败!
这使调试变得有趣;-/
测试2b:与测试2a相同的设置,除了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)

笔记:
这些完全符合预期:
跟踪日志包含prepareexecute语句。
PDO execute语句返回true
未返回异常的errorCodeerrorInfo消息。
奇怪的是只有binding paramater被改变了。
结论
标准PDO:模拟
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
错误总是会引发异常。
绑定参数将尝试以合理的方式进行解释,而不会导致失败。
正常使用此模式。
标准PDO:仿真准备
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
不总是会引发异常
绑定参数将完全按照请求进行解释。
这可能导致从false语句返回PDO execute
这是查询失败的唯一线索。
始终检查从PDO prepare返回的值和从PDO execute返回的值是否为false。

关于php - 如果ATTR_EMULATE_PREPARES为false,则Prepared Insert不会执行任何操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32451215/

有关php - 如果ATTR_EMULATE_PREPARES为false,则Prepared Insert不会执行任何操作的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  3. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  4. ruby - 默认情况下使选项为 false - 2

    这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb

  5. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  7. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  8. ruby-on-rails - link_to 不显示任何 rails - 2

    我试图在索引页中创建一个超链接,但它没有显示,也没有给出任何错误。这是我的index.html.erb代码。ListingarticlesTitleTextssss我检查了我的路线,我认为它们也没有问题。PrefixVerbURIPatternController#Actionwelcome_indexGET/welcome/index(.:format)welcome#indexarticlesGET/articles(.:format)articles#indexPOST/articles(.:format)articles#createnew_articleGET/article

  9. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

    我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

  10. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

    如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

随机推荐