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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With