Why do we use Catalyst's Context Object? What is its purpose?
I was thinking that I don't really understand why just about everything in catalyst uses the context object. Seems that just about everything starts with
my ( $self, $c ) = @_;
we wrap DBIC with a catalyst model and end up with
$c->model('DBIC::Table') ...
or maybe we do
$c->log->warn('foo');
but I don't understand why don't we just do
log('warn', 'foo'); # or whatever the API for some log library is.
Why do we do everything though th开发者_运维技巧e context object? what makes it special?
If I understand what is going on correctly (and I haven't looked at catalyst very hard, so that is easily possible), The context variable is the calling framework. When a request comes in, the framework builds all of the information into itself and the calls a method in your class passing itself along so your method has access to all of that information and the rest of the framework. You may find that reading about inversion of control (or IoC) helps you understand.
Also, by wrapping up all of the functionality in a context variable, you don't run into any namespace issues. Controller, model, etc. classes only have to have the methods they declare in their namespace.
A common idiom in Perl and other languages is to pass around "god" objects that effectively provide an interface to a namespace. This enables calling methods instead of requiring all of the functions to be imported into your namespace. Each of these namespaces can also be customized with different runtime data (instances of the object).
If you don't like the syntax of calling methods on the object, it sounds like what you are looking for is something similar to Javascript's with
block. While Perl does not have a native structure that does this, it does provide the tools to make one:
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->()
}
Given the mock object:
{package Obj;
sub new {bless []}
sub log {shift; say "logging @_"}
sub model {shift; say "setting model to @_"}
}
You can then write:
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
};
Which prints:
setting model to DBIC::Table logging hello world logging hello again
Have fun, just keep in mind that builtin names or names of already defined subroutines will not be overridden in the with
block. You can use the ucfirst
version of the name or just call the method in those instances. All the new subroutines in the with
block must also be called with parens Log('hello')
and not Log 'hello'
since the name is not known at compile time.
Someone would typically write everything what you have shown in a Catalyst::Controller. Now you must remember that a Catalyst Controller exists to do your URL mapping. Sure, it is possible to import a lot of functions into the controller, but when Catalyst itself import a log
function, how do you use this function for URL mapping?
For example sub log : Local { ... }
. Shortly that would not be possible, or it will be more complex then it should be. A Controller have nearly no Functions, so that you don't need to remember a lot of functions and don't have any conflicts.
Its the same reason why Perl itself choose to have special characters in special variables. Like $/
, $_
, $]
and so on. Sure they could also use $INPUT_RECORD_SEPARATOR
or $RS
as the default, but then you need to knew them, and it probably can conflict with your code if you don't knew all special variables.
Another reason is that your additional features you call on $c
have some context. For example you can enable or disable logging with $c->log->disable('warn', 'error')
or just enable them. This context is correctly passed into deeper controller. And they are not global, you can set them on every request to another state.
Another reasons is, that extra functionality that you use can and sometimes need to read the configuration file or other things. Using a object that you pass around for every request (Every $c
is special for every request) and can modified for every request gives your extension the possibility to request information from your application or to handle a state for a specific request.
But if you still don't want this, you are not forced to use $c
. For example you can just load Log::Log4Perl manually, and use a special config for it, and not use $c->log
at all. Or you can import a lot of functions by yourself. But i think the default to not pollute the namespace and give you the possibility to do something special for every request is a good default.
And at least there is no rule that you must use $c
. For example, i myself use DateTime directly and create new objects and don't use Catalyst::Plugin::DateTime that allows me to do $c->datetime
. And i don't see any benefit of doing the last. Just because there exists a lot of plugins that extends $c
, does not mean they are useful or you must use them.
精彩评论