The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Object::Wrapper - Sanity-check wrapper for objects.

SYNOPSIS

    pacakge My;

    # use parent works just as well if available.

    use base qw( Object::Wrapper );

    # For example, if the validation data were the current
    # process id ("$$") then AUTOLOAD could abort operations
    # called across forks (see Object::Wrapper::Fork) and
    # perform proper post-fork cleanup in DESTROY (see
    # Object::Wrapper::Fork::DBI).

    sub constructor
    {
        my $object  = WhateverWorksForYou->();

        # whatever is necessary for the autoload to validate
        # the object: pid ($$), time, use counter.
        #
        # push the virgin object and its validation data
        # into Object::Wrapper.

        my @valid   = ( ... );

        __PACKAGE__->new( $object, @valid )
    }

    # for example, if you want to check for forks:

    AUTOLOAD
    {
        my $franger = shift;

        my $pid     = $franger->[1];

        if( $pid == $$ )
        {
            my $i       = rindex $AUTOLOAD, '::';
            my $name    = substr $AUTOLOAD 2+$i;

            # call by reference allows the method to 
            # modify the object in place (e.g., trampoline).

            $franger->[0]->$name( @_ );
        }
        else
        {
            confess "Method call crosses fork: $$ ($pid)";
        }
    }

    sub cleanup
    {
        # called with the original validation data.

        my ( $head, $pid ) = @_;

        if( $pid != $$ )
        {
            # post-fork cleanup

            ...
        }
        else
        {
            # within-process 

            ...
        }
    }


    # checking for a maxium time window:

    sub new
    {
        ...

        my $window  = $seconds + time;

        __PACKAGE__->SUPER::new( $object, $window );
    }

    AUTOLOAD
    {
        my $franger = shift;

        my $cutoff  = $franger->[1];

        if( $cutoff > 0 )
        {
            ...

            $franger->[0]->$name( @_ )
        }
        else
        {
            die "Expired object ($cutoff)\n"
        }
    }

    # checking for maximum use and time:
    
    my $redispatch  = Object::Franger->can( 'new' );

    sub new
    {
        my $object  = ...;

        $redispatch->( __PACKAGE__, $object, $window, $counter )
    }

    AUTOLOAD
    {
        my $franger = shift;

        time > $franger->[1]    or die "Time expired\n";
        --$franger->[2]         or die "Overtaxed\n";

        ...
    }


    # in both of these latter cases the default stub cleanup
    # may be sufficient.

You may just want to track the object over time and see how long it existed or how many times it was used: store a Benchmark object and have your cleanup print the differences.

DESCRIPTION

Wrap objects to allow simple access and possibly complicated validation of method calls.

The skeleton provided here handles Fork issues, particulary those for DBI and DBD::* handles. Adding extra layers for timeouts or maxiumum number of uses is also simple enougn.

The bulk of the work is done in AUTOLOAD, which re-validates the object prior to dispatching it by reference (i.e., this works with trampolines and other method-modifyer modules).

DESTROY calls the wrapped object with its original arguments, which can be checked on the way out for appropriate cleanup (e.g., within-proc or post-fork).

Interface

new

This takes a reference to the object or package being wrapped and returns the wrapper. It would normaly be called from the wrapped objects constructor:

    sub your_constructor
    {
        my $thingy  = $madness->$method( @argz );

        Object::Wrapper->new( $thingy, @sanity );
    }

This will accept a package name, if the things you are trying to wrap are all class methods:

    sub construct
    {
        Object::Wrapper->new( __PACKAGE__, @sanity )
    }
cleanup

This is provided in the wrapped object's space.

Its job is to clean up after the object on a fork. For example, DBI handles usually cannot be shared across forks. The "InactiveDestroy" flag helps one side close down the objects safely by disabling the side effects of destruction.

For example, handling DBI handles with forks can be done with:

    sub connect
    {
        ...
        my $dbh = DBI->connect( @argz );

        Object::Wrapper->new( $dbh, $$ );
    }

    AUTOLOAD
    {
        my $franger = shift;

        my ( $object, $pid ) = @$franger;

        $pid == $$
        or confess "Oops: dbh crossed fork ($pid, $$)";

        my $name = ...

        $object->$name( @_ )
    }

    sub cleanup
    {
        my ( $dbh, $pid ) = @_;

        my $struct
        = do
        {
            my $drh     = $dbh->{ Driver };

            $drh
            ? $drh->{ CachedKids }
            : ''
        };

        my @kidz
        = $struct
        ? values %$struct
        : ()
        ;

        if( $$ != $pid )
        {
            # handle crossed a fork: turn off side
            # effects of destruction.

            log_message "DB Handle: ignore ($pid)";

            $_->{ InactiveDestroy } = 1
            for
            (
                $dbh,
                @kidz
            );

            $struct
            and %$struct = ();
        }
        else
        {
            log_message "DB Handle: finalize ($pid)";

            $_->{ InactiveDestroy } = 1
            for
            (
                $dbh,
                @kidz
            );

            # purge the global cache, if there is one.

            $struct
            and %$struct = ();
        }

        # at this point the DBI object has been
        # prepared to go out of scope politely.
    }

In fact, this is what Object::Wrapper::Fork (AUTOLOAD) and Object::Wrapper::Fork::DBI (cleanup) do for you.

cleanup_handler

Returns the cleanup handler (assignable). Used in cases where the class uses different name for the cleanup or wants to dispatch into a different class.

Also useful for re-dispatching class-specific handlers in a base-class cleanup.

    # install a new handler by name or coderef 
    # as an argument or via assignment.
    #
    # $thingy can be either an object or class
    # name. in the former the handler will be
    # instlled for "blessed $thingy.

    $thingy->cleanup_handler( $coderef );
    $thingy->cleanup_handler( $name    );

    $thingy->cleanup_handler    = $name;
    $thingy->cleanup_handler    = $coderef;

    # get the current handler.

    my $handler = $object->cleanup_handler;

    goto &$handler;
AUTOLOAD

This is provided by classes derived from Object::Wrapper. This is where the object is re-validated.

Follows the standard rules, gets the franger as first argument, should usually replace the franger with an object on the way out:

    AUTOLOAD
    {
        my ( $franger ) = @_;

        my ( $object, @stuff ) = @$franger;

        validate_the_call or croak ...;

        my $i       = rindex '::', $AUTOLOAD;
        my $name    = substr $AUTOLOAD, 2 + $i;
        my $sub     = $object->can( $name )
        or croak ...;

        local *tmp;

        *tmp    = \$object;

        splice @_, 1, 1, *tmp;

        goto &$sub
    }
DESTROY

Where the object's cleanup is called. The default is to dispatch into $object->can( 'cleanup' ). This can be altered by storing a string or coderef using $object->cleanup_handler.

NOTES

Using classes.

Passing in a class as the "$object" argument will result in class methods being redispatched and the class being called to clean itself up when the franger object goes out of scope. This can be useful for limiting the scope of singleton objects or implementing things like transactions with a wrapped $dbh.

Tied Objects

This module does not support BSDM. Anyone doing this to their objects will have to do it somewhere else.

SEE ALSO

Object::Wrapper::Fork

Supplies new, AUTOLOAD that validate the original pid againsed $$.

Object::Wrapper::Fork::DBI

Supplies connect (takes same arguments as DBI->connect) and cleanup that handles CachedKids appropriately pre- or post-fork.

Object::Wrapper::Count

Supplies AUTOLOAD that decrements a counter, with a die "Object count expired\n".

Object::Wrapper::Window

Supplies AUTOLOAD that comapres time a cutoff, with a die "Object time expired\n".

= Object::Wrapper::Benchmark

Supplies new that stores a Benchmark and counter in the object, AUTOLOAD that counts the calls, cleanup that reports the time and count.

AUTHOR

Steven Lembark <lembark@wrkhors.com>

COPYRIGHT

Copyright (C) 2009 Steven Lembark. This module is released under the same terms as Perl-5.10.0 itself.