Why do we use Catalyst's Context Object? What is its purpose?
Asked Answered
C

3

5

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 the context object? what makes it special?

Celenacelene answered 19/6, 2011 at 0:15 Comment(0)
C
5

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.

Consonance answered 19/6, 2011 at 0:58 Comment(0)
D
2

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.

Decani answered 19/6, 2011 at 2:58 Comment(3)
it wasn't really a question about the syntax... though I do prefer Rakudo's way of calling methods...Celenacelene
@Celenacelene => you asked why catalyst has you write $c->log->warn(...) instead of log(warn => ...). That is a syntactic difference. The reason why is because that is what the authors of catalyst thought was best. So I gave you a way to write it the way you wanted (although in this case, it would be Log()->warn(...) since that's the way catalyst's log method works), what type of answer were you expecting?Decani
asking more about why everything is done through the context object, even external stuff is accessed through it usually. the exact why of the syntax is more obvious than why it's through the context object. as opposed to say, just using a log api directly.Celenacelene
S
1

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.

Salliesallow answered 20/6, 2011 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.