为什么我们使用Catalyst的Context对象?它的目的是什么?

时间:2011-06-19 00:15:26

标签: perl catalyst

我在想我真的不明白为什么催化剂中的所有东西都使用了上下文对象。似乎几乎所有事情都以

开头
my ( $self, $c ) = @_;

我们用催化剂模型包装DBIC,最后用

$c->model('DBIC::Table') ...

或者我们可能

$c->log->warn('foo');

但我不明白为什么我们不这样做

log('warn', 'foo'); # or whatever the API for some log library is.

为什么我们通过上下文对象做所有事情?是什么让它与众不同?

3 个答案:

答案 0 :(得分:5)

如果我理解正确的事情(并且我没有非常努力地查看催化剂,那么这很容易),上下文变量是调用框架。当一个请求进入时,框架会将所有信息构建到自身中,并调用类中的方法传递自身,这样您的方法就可以访问所有这些信息以及框架的其余部分。您可能会发现阅读inversion of control(或IoC)有助于您理解。

此外,通过将上下文变量中的所有功能包装起来,您不会遇到任何名称空间问题。控制器,模型等类只需要在它们的命名空间中声明它们的方法。

答案 1 :(得分:2)

Perl和其他语言中的一个常见习惯是传递“god”对象,这些对象有效地提供了命名空间的接口。这使得调用方法成为可能,而不是要求将所有函数导入到命名空间中。这些命名空间中的每一个也可以使用不同的运行时数据(对象的实例)进行自定义。

如果你不喜欢在对象上调用方法的语法,听起来你正在寻找的类似于Javascript的with块。虽然Perl没有这样做的原生结构,但它确实提供了制作一个的工具:

use warnings;
use strict;
use Carp ();

sub with ($&) {
    my ($obj, $code) = @_;
    my $auto = (caller).'::AUTOLOAD';
    no strict 'refs';
    local *$auto = sub {
        my ($name) = $$auto =~ /([^:]+)$/;
        my $method = $obj->can($name)
                  || $obj->can(lcfirst $name)
            or Carp::croak "no method '$name' on '$obj' in with block";
        unshift @_, $obj;
        goto &$method
    };
    $code->()
}

给出模拟对象:

{package Obj;
    sub new   {bless []}
    sub log   {shift; say "logging @_"}
    sub model {shift; say "setting model to @_"}
}

然后你可以写:

my $c = Obj->new;

with $c => sub {
    model('DBIC::Table');
    Log('hello world');   # ucfirst
    &log('hello again');  # or with a & since 'log' is a builtin
};

打印哪些:

setting model to DBIC::Table
logging hello world
logging hello again

玩得开心,请记住,with块中不会覆盖已定义子程序的内置名称或名称。您可以使用名称的ucfirst版本,也可以在这些实例中调用方法。 with块中的所有新子例程也必须使用parens Log('hello')而不是Log 'hello'来调用,因为在编译时不知道该名称。

答案 2 :(得分:1)

通常会有人编写您在Catalyst :: Controller中显示的所有内容。现在您必须记住,存在一个Catalyst Controller来进行URL映射。当然,可以将很多函数导入控制器,但是当Catalyst本身导入log函数时,如何使用此函数进行URL映射?

例如sub log : Local { ... }。很快这是不可能的,或者它应该是更复杂的。 Controller几乎没有函数,所以你不需要记住很多函数,也没有任何冲突。

与Perl本身选择在特殊变量中使用特殊字符的原因相同。与$/$_$]等相同。当然他们也可以使用$INPUT_RECORD_SEPARATOR$RS作为默认值,但是你需要知道它们,如果你不知道所有的特殊变量,它可能会与你的代码冲突。

另一个原因是您在$c上调用的其他功能有一些上下文。例如,您可以使用$c->log->disable('warn', 'error')启用或禁用日志记录,或者只启用它们。此上下文正确传递到更深层的控制器。它们不是全球性的,您可以根据对另一个州的每个请求设置它们。

另一个原因是,您使用的额外功能可能有时需要读取配置文件或其他内容。使用您为每个请求传递的对象(每个$c对每个请求都是特殊的)并且可以针对每个请求进行修改,使您的扩展可以从您的应用程序请求信息或处理特定请求的状态。

但如果你仍然不想这样,你就不会被迫使用$c。例如,您可以手动加载Log :: Log4Perl,并为其使用特殊配置,而根本不使用$c->log。或者您可以自己导入很多功能。但我认为默认不污染命名空间并为您提供为每个请求做一些特殊操作的可能性是一个很好的默认值。

至少没有必须使用$c的规则。例如,我自己直接使用DateTime并创建新对象,并且不使用允许我执行$c->datetime的Catalyst :: Plugin :: DateTime。我没有看到做到最后的任何好处。仅仅因为存在许多扩展$c的插件,并不意味着它们有用或者你必须使用它们。