NAME
Method::Declarative - Create methods with declarative syntax
SYNOPSIS
use Method::Declarative
(
'--defaults' =>
{
precheck =>
[
[ qw(precheck1 arg1 arg2) ],
# ...
],
postcheck =>
[
[ qw(postcheck1 arg3 arg4) ],
# ...
],
init =>
[
[ 'initcheck1' ],
# ...
],
end =>
[
[ 'endcheck1' ],
# ...
],
once =>
[
[ 'oncecheck1' ],
] ,
package => '__CALLER__::internal',
},
method1 =>
{
ignoredefaults => [ qw(precheck end once) ],
code => '__method1',
},
) ;
DESCRIPTION
The Method::Declarative module creates methods in a using class' namespace. The methods are created using a declarative syntax and building blocks provided by the using class. This class does not create the objects themselves.
The using class invokes Method::Declarative, passing it list of key-value pairs, where each key is the name of a method to declare (or the special key '--default') and a hash reference of construction directives. The valid keys in the construction hash refs are:
- code
-
The value corresponding to
code
key is a method name or code reference to be executed as the method. It is called like this:$obj->$codeval(@args)
where
$obj
is the object or class name being used,$codeval
is the coresponding reference or method name, and@args
are the current arguments for the invocation. If$codeval
is a method name, it needs to be reachable from$obj
.A
code
key in a method declaration will override anycode
key set in the--defaults
section. - end
-
The value corresponding to the
end
key is an array reference, where each entry of the referenced array is another array ref. Each of the internally referenced arrays starts with a code reference or method name. The remaining elements of the array are used as arguments.Each method declared by the arrays referenced from
end
are called on the class where the declared method resides in an END block when Method::Declarative unloads.Each method is called like this:
$pkg->$codeval($name[, @args]);
where
$pkg
is the package or class name for the method,$name
is the method name, and@args
is the optional arguments that can be listed in each referenced list.end
blocks are run in the reverse order of method declaration (for example, if method1 is declared before method2, method2'send
declaration will be run before method1's), and for each method they are run in the order in which they are declared.Note that this is not an object destructor, and no objects of a particular class may still exist when these methods are run.
- ignoredefaults
-
The value corresponding to the
ignoredefaults
key is an array reference pointing to a list of strings. Each string must corespond to a valid key, and indicates that any in-force defaults for that key are to be ignored. See the section on the special--defaults
method for details. - init
-
The value corresponding to the
init
key is identical in structure to that corresponding to theend
key. The only difference is that the declared methods/code refs are executed as soon as the method is available, rather than during an END block. - once
-
The value corresponding to the
once
key is identical in structure to that corresponding to theend
key. The values are used when the method is invoked, however.If the method is invoked on an object based on a hash ref, or on the class itself, and it has not been invoked before on that object or hash ref, the methods and code refs declared by this key are executed one at a time, like this:
$obj->$codeval($name, $isscalar, $argsref[, @args ]);
where
$obj
is the object or class on which the method is being invoked,$codeval
is the method name or code reference supplied,$name
is the name of the method,$isscalar
is a flag to specify if the declared method itself is being executed in a scalar context,$argsref
is a reference to the method arguments (\@_
, in other words), and@args
are any optional arguments in the declaration.The return value of each method or code reference call is used as the new arguments array for successive iterations or the declared method itself (including the object or class name). Yes, that means that these functions can change the the object or class out from under successive operations.
Any method or code ref returning an empty list will cause further processing for the method to abort, and an empty list or undefined value (as appropriate for the context) will be returned as the declared method's return value.
- package
-
The value coresponding to the
package
key is a string that determines where the declared method is created (which is the caller's package by default, unless modified with a--defaults
section). The string '__CALLER__' can be used to specify the caller's namespace, so constructions like the one in the synopsis can be used to create methods in a namespace based on the calling package namespace. - postcheck
-
The value coresponding to the
postcheck
key is identical in structure to that coresponding to theend
key. Thepostcheck
operations are run like this:$obj->$codeval($name, $isscalar, $vref[, @args ]);
where
$obj
is the underlying object or class,$codeval
is the method or code ref from the list,$name
is the name of the declared method,$isscalar
is the flag specifying if the declared method was called in a scalar context,$vref
is an array reference of the currently to-be-returned values, and@args
is the optional arguments from the list.Each method or code reference is expected to return the value(s) it wishes to have returned from the method. Returning a null list does NOT stop processing of later
postcheck
declarations. - precheck
-
The
precheck
phase operates similarly to theonce
phase, except that it's triggered on all method calls (even if the underlying object is not a hash reference or a class name).
Any illegal or unrecognized key will cause a warning, and processing of the affected hashref will stop. This means a --defaults
section will be ineffective, or a declared method won't be created.
The --defaults section
The values in a hashref tagged with the key --defaults
(called "The --defaults section") provide defaults for each of the keys. For the keys that take array references pointing to lists of array refs, the values are prepended. For example, if the following declaration were encountered:
use Method::Declarative
(
'--defaults' =>
{
package => 'Foo',
precheck => [ [ '__validate' ] ],
},
new =>
{
ignoredefaults => [ 'precheck' ],
code => sub { return bless {}, (ref $_[0]||$_[0]); },
},
method1 =>
{
precheck => [ [ '__firstcanfoo', 'shortstop' ] ],
code => '__method1_guts',
}
) ;
then the methods new() and method1() would be created in the package Foo. The following code fragment:
my $res = Foo->new()->method1($arg);
would actually be expanded like this:
my $obj = Foo->new(); # Returns a blessed hashref
my @aref = $obj->__validate('method1', 1, [ $obj, $arg ]);
@aref = $aref[0]->__firstcanfoo('method1', 1, \@aref, 'shortstop');
my $res = $aref[0]->__method1_guts(@aref[1..$#aref]);
MOTIVATION
This module was born out of my increasing feeling of "there just has to be a better way" while I was grinding out yet another `leven-teen hundred little methods that differed just enough that I couldn't conveniently write a universal template for all of them, but that were similar enough that I saw a huge amount of duplicated code.
Take, for example a subclass of CGI::Application that's responsible for the presentation of a moderately complex web app with three sections - a general section, a members's only section, and an administration section. The methods that present the general section only need to load the appropriate templates (and possibly validate some form data or update a database), while the methods that present the member's only and admin sections need to validate credentials against a database first, and the methods for the administrative section also need to check the admin user against a capabilities table. Add in some basic sanity checking (making sure the object methods aren't called as class methods, check for a database connection, etc.), and real soon you have a whole hoard of methods that pretty much look alike except for about a half dozen lines each.
With Method::Declarative, you can stick much of the pre- and post- processing into the '--defaults' section, and forget about it.
EXAMPLE
Following the MOTIVATION section above, for the general section of the site, we may need to do something like this:
BEGIN { our ($dbuser,$dbpasswd) = qw(AUserName APassword); }
use Method::Declarative
(
'--defaults' =>
{
precheck =>
[
[ '__load_rm_template' ],
[ '__populate_template' ],
],
code => 'output',
},
main => { },
home => { },
aboutus => { },
faq =>
{
ignoredefaults => [ 'precheck' ],
precheck =>
[
[ '__connect_to_database', $dbuser, $dbpasswd ],
[ '__load_rm_template' ],
[ '__load_faq' ],
[ '__populate_template' ],
],
}
) ;
In this particular example, you could have the __load_rm_template
load an HTML::Template object and return it, , with the template to be loaded determined from the run mode, have __populate_template
fill out common run mode-dependent parameters in the template (and return the template as the new argument array), and have __connect_to_database
and __load_faq
do the obvious things.
With that, the run mode methods main(), home(), and aboutus() become trivial, and faq() isn't that much more complicated. When the home() method is invoked, it results in this series of calls:
# This returns ($obj, $tmpl)
$obj->__load_rm_template('main', 1, [ $obj ]);
# This returns ($tmpl)
$obj->__populate_template('main', 1, [ $obj, $tmpl ]);
# This returns the HTML
$tmpl->output;
Adding authentication checking wouldn't be that much more complex:
BEGIN { our ($dbuser,$dbpasswd) = qw(AUserName APassword); }
use Method::Declarative
(
'--defaults' =>
{
precheck =>
[
[ '__connect_to_database', $dbuser, $dbpasswd ],
[ '__load_rm_template' ],
[ '__check_auth' ],
[ '__populate_template' ],
],
code => 'output',
},
login => { },
account_view => { },
account_update =>
{
ignoredefaults => 'precheck',
precheck =>
precheck =>
[
[ '__connect_to_database', $dbuser, $dbpasswd ],
[ '__check_update_auth' ],
[ '__update_account' ],
[ '__load_rm_template' ],
[ '__populate_template' ],
],
}
) ;
We can even go futher, and add capabilities:
BEGIN { our ($dbuser,$dbpasswd) = qw(AUserName APassword); }
use Method::Declarative
(
'--defaults' =>
{
precheck =>
[
[ '__connect_to_database', $dbuser, $dbpasswd ],
[ '__check_auth' ],
],
code => 'output',
},
login => { code => '__process_admin_login' },
chpasswd =>
{
precheck =>
[
[ '__has_capability', 'change_password' ],
[ '__change_password' ],
],
},
) ;
CAVEATS
This module is S-L-O-W. That's because the main engine of the module is essentially an interpreter that loops through the given data structures every time a method is called.
The Method::Declarative module will use the __Method__Declarative_done_once
key of hashref-based objects to scoreboard calls to methods with a once
phase declaration. This probably won't cause a problem unless your object happens to be tied or restricted.
BUGS
Please report bugs to <perl@jrcsdevelopment.com>.
AUTHOR
Jim Schneider
CPAN ID: JSCHNEID
perl@jrcsdevelopment.com
COPYRIGHT
Copyright (c) 2006 by Jim Schneider.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
SEE ALSO
perl(1) CGI::Application(3) HTML::Template(3).