Perl:CGI和DBI模块的变量范围问题

时间:2020-03-06 14:19:40  来源:igfitidea点击:

我遇到了一个以前从未遇到过的变量范围问题。我正在使用Perl的CGI模块和对DBI的do()方法的调用。这是代码结构,简化了一点:

use DBI;
use CGI qw(:cgi-lib);
&ReadParse;
my $dbh = DBI->connect(...............);
my $test = $in{test};
$dbh->do(qq{INSERT INTO events VALUES (?,?,?)},undef,$in{test},"$in{test}",$test);

#1占位符变量的求值就像未初始化一样。其他两个占位符变量起作用。

问题:为什么%in哈希在do()的上下文中不可用,除非我将其包装在双引号中(#2占位符)或者将值重新分配给新变量(#3占位符)?

我认为这与CGI模块的ReadParse()函数如何将范围分配给%in哈希有关,但是我不知道Perl的作用域是否足以理解为什么%in在顶级可用,而在我的工作范围内不可用() 陈述。

如果有人确实了解范围问题,是否有更好的方法来解决?将所有%in引用都用双引号引起来似乎有些混乱。为每个查询参数创建新变量是不现实的。

明确地说,我的问题是关于变量范围界定的问题。我意识到,不建议使用ReadParse()使用CGI来获取查询参数。

我正在使用Perl 5.8.8,CGI 3.20和DBI 1.52. 预先感谢任何阅读此书的人。

@Pi和@Bob,谢谢建议。预先声明%in的作用域没有任何作用(我始终使用strict)。结果与之前相同:在数据库中,col1为null,而cols 2和3设置为期望值。

供参考,这里是ReadParse函数(见下文)。这是CGI.pm的一部分的标准功能。以我的理解,我并不是为了设置作用域而初始化哈希中的%(除了满足strict),因为在我看来该函数可以处理以下内容:

sub ReadParse {
    local(*in);
    if (@_) {
      *in = $_[0];
    } else {
    my $pkg = caller();
      *in=*{"${pkg}::in"};
    }
    tie(%in,CGI);
    return scalar(keys %in);
}

我想我的问题是在do()上下文中获取%in哈希的最佳方法是什么?再次感谢!我希望这是为我的原始问题提供更多信息的正确方法。

@丹:我听说有关&ReadParse语法。我通常使用CGI :: ReadParse(),但在这种情况下,我认为最好坚持使用CGI.pm文档的确切方式。

解决方案

使用严格;。总是。

尝试声明

our %in;

看看是否有帮助。否则," strict"可能会产生更有用的错误。

首先,这不在做的上下文/范围内。它仍然处于主要或者全局范围内。除非以与Perl中的子例程或者不同"类"相关的某种方式输入{},否则我们不会离开上下文。在()内,我们不会超出范围。

我们提供给我们的样本是未初始化的哈希值,正如Pi所建议的,使用strict肯定会阻止这些事件发生。

我们能给我们一个更具代表性的代码示例吗?我们在哪里设置%IN以及如何设置?

那里的东西很破。 Perl的作用域相对简单,除非我们正在做一些愚蠢的事情,否则我们不太可能会偶然发现类似的事情。如建议的那样,打开严格的编译指示(以及警告。实际上,无论如何我们都应同时使用两者)。

在不能够看到%in是如何定义的情况下很难说出是怎么回事(这与看起来讨厌的ReadParse调用有关吗?为什么用前导&,btw调用它?该语法已被认为是无效的并消失了很长时间)。我建议发布更多代码,以便我们了解发生了什么。

它实际上看起来并不像我们在文档中所描述的那样在使用它:
https://metacpan.org/pod/CGI#COMPATIBILITY-WITH-CGI-LIB.PL

如果必须使用它,那么CGI :: ReadParse();似乎更明智,语法也更简洁。尽管在这种情况下我看不到它有什么大的变化,但是它是一个绑定变量,所以到底谁知道它在做什么;)

我们是否有特定原因不能使用更常见的$ cgi-> param('foo')语法?它稍微干净一点,并且以一种更加可预测的方式隐藏了命名空间。

试试这个

%in = ReadParse();

但我对此表示怀疑。我们是否要获取查询参数或者其他内容?

好的,试试这个:

use CGI;
my %in;
CGI::ReadParse(\%in);

这可能会有所帮助,因为它实际上使用的是我们声明的变量,因此可以控制的范围(此外,它可以让我们"严格使用",而不会产生其他可能使我们感到困惑的麻烦)

我不知道出了什么问题,但是我可以告诉我们一些不是的事情:

  • 这不是范围问题。如果不是,那么$ in {test}的所有实例都不起作用。
  • 这不是古老的&调用语法。 (这不是"正确的",但在这种情况下是无害的。)

ReadParse是讨厌的代码。它修改符号表以在调用包中创建全局变量%in。更糟糕的是,它是一个绑定变量,因此访问它(理论上)可以执行任何操作。查看CGI.pm的源代码,FETCH方法仅调用params()方法来获取数据。我不知道为什么$ dbh-> do()中的提取不起作用。

我们正在使用哪个版本的DBI?通过查看DBI更改日志,可以发现1.00之前的版本不支持attribute参数。我怀疑"未初始化"的" $ in {test}"实际上是我们要传递给" $ dbh-> do()"的" undef"。

由于这开始看起来像是一个" tie()"问题,请尝试以下实验。将其另存为foo.pl并以`perl foo.pl" x = 1"的身份运行

use CGI;

CGI::ReadParse();
p($in{x}, "$in{x}");

sub p { my @a = @_; print "@a\n" }

它应该打印" 1 1"。如果没有,我们就找到了罪魁祸首。

从我们给出的示例来看,这不是范围问题,或者所有参数都不起作用。

看起来DBI(或者DBD,不确定在何处使用绑定参数)不能兑现领带魔术。
解决方法是将我们传递给它的内容进行字符串化或者复制,就像第二个和第三个参数一样。

使用SQLite和DBI 1.53进行的简单测试表明它可以正常工作:

$ perl -MDBI -we'sub TIEHASH { bless {} } sub FETCH { "42" } tie %x, "main" or die; my $dbh = DBI->connect("dbi:SQLite:dbname=dbfile","",""); $dbh->do("create table foo (bar char(80))"); $dbh->do("insert into foo values (?)", undef, $x{foo}); print "got: " . $dbh->selectrow_array("select bar from foo") . "\n"; $dbh->do("drop table foo")'
got: 42

想要共享我们正在使用的数据库吗?

我只是从http://www.carcomplaints.com/test/test.pl.txt尝试了测试密码,它就可以在我的计算机上立即运行,没有问题。我得到了三个预期的值。我没有将其作为CGI运行,而是使用:

...
use CGI qw/-debug/;
...

我在控制台上写了一个变量(" test = test"),脚本插入没有问题。

但是,如果我们不进行此操作,则tt将插入一个空字符串和两个NULL。这是因为我们将值插值到字符串中。这将创建一个字符串,其值为$ in {test},当前为undef。 undef字符串化为一个空字符串,这是插入数据库的内容。

根据DBI文档:当前,绑定绑定变量不起作用。

DBI的内幕非常复杂,不幸的是,经过一些回转才能提高效率,这会引起问题。我同意其他人所说的摆脱丑陋的cgi-lib风格代码的说法。没有良好的框架(去做Catalyst)就做CGI实在令人不快,更不用说十年来已经过时的东西。