2017-08-14 97 views
2

我有一个能够定义凭证的类。从类子例程中覆盖模块子例程

$po = PoService->new()->set_basicauth("jack", "secret"); 

事情是,为了做到这一点,它必须重新定义模块子程序。所以,我就是这么做的:

sub set_basicauth { 
    my ($self, $creds_username, $creds_password) = @_; 

    sub SOAP::Transport::HTTP::Client::get_basic_credentials { 
    return $creds_username => $creds_password; 
    } 

    return $self; 
} 

但是,当我运行的代码,它告诉我一个消息:

Variable "$creds_username" will not stay shared at /opt/PoService.pm line 53. 
Variable "$creds_password" will not stay shared at /opt/PoService.pm line 53. 

我干了什么错在这里做什么?

回答

1

问题是混合了一个词法(my)变量和一个嵌套子例程。

内部子程序关闭它使用的两个变量。然而,在词汇方面,它们在每次新呼叫时被重新定义,而内部(,名为)sub忠实地保持参考原始值。所以整个事情只有在第一次被调用时才能按预期工作。

幸运的是,我们得到了这个事实的警告。具有添加use diagnostics;(或见perldiag

变量 “$ X” 不会留在-e线共享1(#1)
(W闭合)的内(嵌套的)命名子程序引用 词法变量在外部命名子例程中定义。

当内子程序被调用时,它将看到的 外子程序的变量,因为它是前和第一 呼叫到外子程序期间的值;在这种情况下,在第一次调用 外部子程序完成之后,内部和外部子程序将不再为共享该变量的共同值的 更长。换句话说, 变量将不再被共享。

这个问题通常可以通过使用sub {}语法使内部子程序 匿名来解决。当创建外部子程序中的内部匿名子代 引用变量时,它们会自动回弹到这些变量的当前值。

虽然这解释了这个问题,但解决方案不适合您的目的,如果我理解正确的话。这似乎适合

一种方法是完全限定名

sub SOAP::Transport::HTTP::Client::get_basic_credentials { 
    return $main::$creds_username => $main::$creds_password; 
} 

sub set_basicauth { 
    my $self = shift; 

    ($main::$creds_username, $main::$creds_password) = @_; 

    # ... (not sure of your purpose, presumably use SOAP::) 

    return $self; 
} 

其中$main应该不同,如果代替实际的包名。子可以被放置在另一个内部,但是它没有任何目的,因为它被编译为任何其他命名的子。

另一种选择是用our使变量全局。

+0

谢谢@zdim。我已经将解决​​方案修正为一个工作。 – sancho21

+0

@ sancho21伟大的:)。感谢您的建议编辑。但是,声明'我的'变种是另一回事。它引入了一个词法,在这种情况下它是全球性的(无论如何,在更大的范围内)。如果你再引用它('$ var'),它就是_that_词法,而不是'$ main :: var' - 你必须调用'$ main :: var'。 '$ main :: var'在符号表中创建一个条目(而'my $ var'没有)。在完全合格时不需要声明它们(如果它不起作用,那肯定是另一个原因)。我会添加一些评论或链接(但后来,现在已经太晚了)。 – zdim

+0

@ikegami谢谢你的改进(编辑) – zdim

1

有没有这样的事情作为一个嵌套命名的潜艇。

sub set_basicauth { 
    my ($self, $username, $password) = @_; 

    $self->{username} = $username; 
    $self->{password} = $password; 
} 

sub request {  
    my ($self, ...) = @_; 

    my $username = $self->{username}; 
    my $password = $self->{password}; 

    local *SOAP::Transport::HTTP::Client::get_basic_credentials = sub { 
    return $username => $password; 
    }; 

    ... code that uses SOAP::Lite ... 
}