Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

perl

catalyst

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?

like image 259
xenoterracide Avatar asked Jun 19 '11 00:06

xenoterracide


2 Answers

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.

like image 51
Chas. Owens Avatar answered Nov 15 '22 07:11

Chas. Owens


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.

like image 22
Eric Strom Avatar answered Nov 15 '22 05:11

Eric Strom