NAME

Wrap::Sub - Object Oriented subroutine wrapper with pre and post hooks, and more.

Coverage Status

SYNOPSIS

    use Wrap::Sub;

Basic functionality example

    my $wrapper = Wrap::Sub->new;

    # create the wrapped sub object

    my $foo_obj  = $wrapper->wrap(
        'My::Module::foo',
        pre  => sub { print "$Wrap::Sub::name in pre\n"; },
        post => sub { print "$Wrap::Sub::name in post\n"; },
    );

    # add/change/remove a routine via a method call (send in undef to remove)

    $foo_obj->pre(sub { print "changed pre\n"; } );
    $foo_obj->post(sub { print "changed post\n"; } );

    # name of sub

    $foo_obj->name;

    # unwrap and rewrap

    $foo_obj->unwrap;
    $foo_obj->rewrap;

    # wrapped or not?

    my $is_wrapped = $foo_obj->is_wrapped;

    # list all subs wrapped under the current wrap object

    my @wrapped_subs = $wrapper->wrapped_subs;

    # retrieve all wrapped sub objects

    my @sub_objects = $wrapper->wrapped_objects;

Here's an example that shows how you can get elapsed time of a sub (this example requires Time::HiRes)

    my $pre_cref = sub {
        return gettimeofday();
    };

    my $post_cref = sub {
        my $pre_return = shift;
        my $time = gettimeofday() - $pre_return->[0];
        print "$Wrap::Sub::name finished in $time seconds\n";
    };

    $foo_obj->pre($pre_cref);
    $foo_obj->post($post_cref);

Manipulate the return from the original subroutine, take action, then return the modified results

    my $post_cref = sub {
        my ($pre_return, $sub_return) = @_;

        if ($sub_return->[0] != 1){
            die "$Wrap::Sub::name returned an error\n";
        }
        else {
            $sub_return->[0] = 100;
            return $sub_return->[0];
        }
    };

    $foo_obj->post($post_cref, post_return => 1);

Get an ordered list (array of array references) of the last expression evaluated in all sub object pre() and post() functions

    my @pre_results = $wrapper->pre_results;
    my @post_results = $wrapper->post_results;

    for my $sub_post_result (@post_results){
        print "$sub_post_result->[0]\n";
    }

Wrap all subs in a module (does not include imported subs), and have them all perform the same actions

    my $wrapper = Wrap::Sub->new(
        pre  => sub { print "start $Wrap::Sub::name\n"; },
        post => sub { print "finish $Wrap::Sub::name\n"; },
    );

    # wrapping a module returns an href with the sub name as the key,
    # and the value being the wrapped sub object

    my $module_subs = $wrapper->wrap('My::Module');

    # unwrap them all, and confirm

    for my $sub_name (keys %$module_subs){
        my $sub_obj = $module_subs->{$sub_name};

        print "$sub_name is wrapped\n" if $sub_obj->is_wrapped;

        $sub_obj->unwrap;

        if ($sub_obj->is_wrapped) {
            die "$sub_name not unwrapped!"
        }
    }

DESCRIPTION

This module allows you to wrap subroutines with pre and post hooks while keeping track of your wrapped subs. It also allows you to easily wrap all subs within a module (while filtering out the subs that are imported).

Thanks to code taken out of Hook::LexWrap, caller() works properly within the wrapped subs.

There are other modules that do this, see "SEE ALSO". I wrote it out of sheer curiosity and experience, not because I didn't check the CPAN for existing modules that do the same thing.

WRAP OBJECT METHODS

new(%opts)

Instantiates and returns a new Wrap::Sub object, ready to be used to start creating wrapped sub objects.

Options (note that if these are set in new(), all subs wrapped with this object will exhibit the same behaviour. Set in wrap() or use pre() and post() methods to individualize wrapped subroutine behaviour).

pre => $cref

A code reference containing actions that will be executed prior to executing the sub that's wrapped. Receives the parameters that are sent into the wrapped sub (@_). Has access to $Wrap::Sub::name parameter, which holds the name of the original wrapped sub.

post => $cref

A code reference containing actions that will be executed after the wrapped sub is executed. Has access to $Wrap::Sub::name parameter, which holds the name of the original wrapped sub.

Receives an array reference with two elements, each another array reference. The first inner reference contains the return value(s) from the pre hook, and the second contains the return value(s) from the wrapped sub. If neither pre or the actual sub have return values, the respective inner array reference will be empty.

Returns whatever you decide you want it to. See post_return parameter for further details on returning values.

post_return => Bool

Set this to true if you want your post() hook to return its results, and false if you want the return value(s) from the actual wrapped sub instead. Disabled by default (ie. you'll get the return from the original sub).

wrap('sub', %opts)

Instantiates and returns a new wrapped sub object on each call. sub is the name of the subroutine to wrap (requires full package name if the sub isn't in main::).

Options:

See new() for a description of the parameters. Setting them here allows you to individualize behaviour of the hooks for each wrapped subroutine.

wrapped_subs

Returns a list of all the names of the subs that are currently wrapped under the parent wrap object.

wrapped_objects

Returns a list of all sub objects underneath the parent wrap object, regardless if its sub is currently wrapped or not.

is_wrapped('Sub::Name')

Returns 1 if the sub currently under the parent wrap object is wrapped or not, and 0 if not. Croaks if there hasn't been a child sub object created with this sub name.

pre_results

As each wrapped sub is called where a pre() method is set, we'll stash the last expression evaluated in it, and push the results to an array. This method will fetch that array.

Each entry is an array reference per pre() call, containing the returned data.

post_results

As each wrapped sub is called where a post() method is set, we'll stash the last expression evaluated in it, and push the results to an array. This method will fetch that array.

Each entry is an array reference per post() call, containing the returned data.

WRAPPED SUB OBJECT METHODS

These methods are for the children wrapped sub objects returned from the parent wrap object. See "WRAP OBJECT METHODS" for methods related to the parent wrap object.

name

Returns the name of the sub represented by this sub object.

pre($cref)

Send in a code reference containing actions that you want to have performed prior to the wrapped sub being executed. Has access to $Wrap::Sub::name parameter, which holds the name of the original wrapped sub.

post($cref, post_return => Bool)

A code reference containing actions that will be executed after the wrapped sub is executed. Has access to $Wrap::Sub::name parameter, which holds the name of the original wrapped sub.

The code supplied receives an array reference containing an array reference holding the return values from pre() and a second array reference containing the return values from the actual wrapped sub. If neither pre or the actual sub have return values, the respective array reference will be empty.

The optional parameter post_return specifies that you want the return value(s) from this function instead of the return value(s) generated by the wrapped sub. Disabled by default.

unwrap

Restores the original functionality back to the sub, and runs reset() on the object.

rewrap

Re-wraps the sub within the object after calling unwrap on it.

reset

Resets the functional parameters (pre, post and post_return), along with called() and called_with back to undef/false. Does not restore the sub back to its original state.

is_wrapped

Returns true (1) if the sub the object refers to is currently wrapped, and false (0) if not.

called

Returns true (1) if the sub being wrapped has been called, and false (0) if not.

called_with

Returns an array of the parameters sent to the subroutine. croak()s if we're called before the wrapped sub has been called.

NOTES

This module has a backwards parent-child relationship. To use, you create a wrap object using "WRAP OBJECT METHODS" new and wrap methods, thereafter, you use the returned wrapped sub object "WRAPPED SUB OBJECT METHODS" to perform the work.

SEE ALSO

This module was created for my own sheer curiosity and experience. There are quite a few like it. I've never used any of them (this list was taken from Hook::WrapSub):

Hook::LexWrap provides a similar capability to Hook::WrapSub, but has the benefit that the caller() function works correctly within the wrapped subroutine.

Sub::Prepend lets you provide a sub that will be called before a named sub. The caller() function works correctly in the wrapped sub.

Sub::Mage provides a number of related functions. You can provide pre- and post-call hooks, you can temporarily override a function and then restore it later, and more.

Class::Hook lets you add pre- and post-call hooks around any methods called by your code. It doesn't support functions.

Hook::Scope lets you register callbacks that will be invoked when execution leaves the scope they were registered in.

Hook::PrePostCall provides an OO interface for wrapping a function with pre- and post-call hook functions. Last updated in 1997, and marked as alpha.

Hook::Heckle provides an OO interface for wrapping pre- and post-call hooks around functions or methods in a package. Not updated sinc 2003, and has a 20% failed rate on CPAN Testers.

Class::Wrap provides the wrap() function, which takes a coderef and a package name. The coderef is invoked every time a method in the package is called.

Sub::Versive lets you stack pre- and post-call hooks. Last updated in 2001.

AUTHOR

Steve Bertrand, <steveb at cpan.org>

BUGS

Please report any bugs or requests at https://github.com/stevieb9/wrap-sub/issues

REPOSITORY

https://github.com/stevieb9/wrap-sub

BUILD RESULTS

CPAN Testers: http://matrix.cpantesters.org/?dist=Wrap-Sub

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Wrap::Sub

LICENSE AND COPYRIGHT

Copyright 2016 Steve Bertrand.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.