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

NAME

Class::Plugin::Util - Utility functions for supporting Plug-ins.

VERSION

This document describes Class::Plugin::Util version 0.009;

SYNOPSIS

    use Class::Plugin::Util qw( supports doesnt_support factory_new first_available_new)

DESCRIPTION

This module has utility functions for creating dynamic classes.

COOKBOOK

Loading plug-ins.

If you have a class that has a method that returns a list of modules it requires you can check that everything is OK before you load it.

    use Class::Plugin::Util qw(supports);
    use MyPlugin::XMLSupport;

    # The plugin we want to use has a requires class method that
    # returns an array of modules it needs to function properly:
    
    my @required_modules = MyPlugin::XMLSupport->requires;

    # The plugin shouldn't use the required modules itself
    # it should only return the modules it needs to use in
    # in the required method above. The supports method checks
    # if the required modules are available and loads the modules
    # for us.

    if (supports( @required_modules )) {
        print 'We have XML support', "\n";

        my $xml = MyPlugin::XMLSupport->new( );
    
        [ ... ]
    }

    package MyPlugin::XMLSupport;
    {
        sub new {
            return bless { }, shift;
        }

        sub requires {
            return 'XML::Parser';
        }
    }

Load the best available module.

Say you want to support the ability to export data. Right now you need support for exporting to a list of formats, let's say YAML, JSON and XML,

As there are several implementations of YAML on CPAN you want to load the best module that the user has available on his system.

Exporting data should be as easy as:

    my $exporter = MyApp::Export->new({
        format => 'YAML',
    });

    $exporter->export($data);

You could implement this with Class::Plugin::Util like this:

MyApp/Export.pm - This is the main class.

    package MyApp::Export;
    use strict;
    use warnings;
    use Class::Plugin::Util qw( first_available_new );
    {

        my @LIST_OF_YAML_HANDLERS = qw(
            MyApp::Export::YAML::LibYAML
            MyApp::Export::YAML::Syck
            MyApp::Export::YAML
        );

        my @LIST_OF_JSON_HANDLERS = qw(
            MyApp::Export::JSON::Syck
            MyApp::Export::JSON::PC
            MyApp::Export::JSON
        );

        my %FORMAT_TO_HANDLER = (
            'JSON'  => [ @LIST_OF_JSON_HANDLERS ],
            'YAML'  => [ @LIST_OF_YAML_HANDLERS ],
        ); 
        
        sub new {
            my ($class, $arg_ref) = @_;
           
            # The format argument decides which format we choose. 
            my $format = uc( $arg_ref->{format} );
            # Default format is YAML.
            $format  ||= 'YAML',
    
            my $select_ref = $FORMAT_TO_HANDLER{$format};

            my $object = Class::Plugin::Util::first_available_new($select_ref, $arg_ref);

            return $object;
        } 
    }

    1;

MyApp/Export/Base.pm - This is base class export handlers should inherit from.

    package MyApp::Export::Base;
    use strict;
    use warnings;
    use Carp;
    use Class::Plugin::Util;
    {
        sub new {
            my ($class, $arg_ref) = @_;
           
            # All MyApp::Export:: classes should have a requires method which returns
            # a list of all modules it requires to do it's work. 
            my @this_handler_requires = $class->requires;

            # check if we're missing any modules.
            my $missing_module = Class::Plugin::Util::doesnt_support(@this_handler_requires);
            
            if ($missing_module) {
                carp    "$class requires $missing_module, " .
                        "please install from CPAN."         ;
            }

            my $self = { };
            bless $self, $class;

            return $self; 
        }

        # transform is the function exporters should use to transform the data to it's format.
        sub transform {
            croak 'You cannot use MyApp::Export::Base directly. Subclass it!';
        }

        # the list of modules we require.
        sub requires {
            croak 'You cannot use MyApp::Export::Base directly. Subclass it!';
        }

        sub export {
            my ($self, $data) = @_;
            return if not $data;

            return $self->transform($data);
        }
    }

    1;

MyApp/Export/YAML/LibYAML.pm - Example implementation of YAML::LibYAML support for MyApp::Export

    package MyApp::Export::YAML::LibYAML;
    use strict;
    use warnings;
    use base 'MyApp::Export::Base';
    {
        
        my @MODULES_REQUIRED = qw( YAML::LibYAML );

        sub transform {
            my ($self, $data_ref) = @_;

            return YAML::LibYAML::Dump($data_ref);
        }

        sub requires {
            return @MODULES_REQUIRED;
        }
    }

    1;

MyApp/Export/JSON/Syck.pm - Example implementation of JSON::Syck support for MyApp::Export.

    package MyApp::Export::JSON::Syck;
    use strict;
    use warnings;
    use base 'MyApp::Export::Base';
    {
    
        my @MODULES_REQUIRED = qw( JSON::Syck );

        sub transform {
            my ($self, $data_ref) = @_;

            return JSON::Syck::Dump($data_ref);
        }

        sub requires {
            return @MODULES_REQUIRED;
        }
    }

    1;

Abstract Factory

You want the user to be able to select which database type to use in a configuration file, have support for different database systems without listing all database modules (i.e DBD::mysql, DBD::pg etc) in your distributions dependency list, and you want to be able to add new database types with

SUBROUTINES/METHODS

CLASS METHODS

Class::Plugin::Util::supports( @required_modules )

Require all the given modules, but return false if any one of them fails to load.

Class::Plugin::Util::doesnt_support( @required_modules )

In a list of modules, return the first module that is not installed. If every module is installed, it returns nothing.

Class::Plugin::Util::factory_new($class, @arguments_to_new)

Given a class name, load the module (via UNIVERSAL::require) and return a new instance of it.

Class::Plugin::Util::first_available_new(\@list_of_class_to_try, @arguments_to_new)

Given a list of modules, pick the first module installed and return a new instance of it. If no modules are installed, it returns nothing.

Class::Plugin::Util::require_class($class)

Load module by class name. Does not die on error. (like missing file).

This function also uses elaborate ways to find out if the module is already loaded, so it doesn't have to load it again.

If $opt_import is set, require_class will behave as use and will import the module into the callers namespace. (c<@opt_imports> specifies what to import).

Some examples:

Regular require
    require_class('Carp::Clan');

behaves like:

    require Carp::Clan;
Require + Import (without specified imports).
    require_class('Carp::Clan', {import => 1});

behaves like:

    require Carp::Clan;
    Carp::Clan->import();
Require + Import (with specified imports).
    require_class('Carp::Clan', {
        import => [qw(carp croak confess)]
    });

behaves like:

    require Carp::Clan;
    Carp::Clan->import('crap', 'croak', 'confess');
Use
    BEGIN { require_class('Carp::Clan', {import => 1} };

behaves like:

    use Carp::Clan;

and:

    BEGIN {
        require_class('Carp::Clan', {
            import => [ qw(cluck confess) ]
        });
    }

behaves like:

    use Carp::Clan qw(cluck confess);

load_plugins($superclass, [\%|\@|$ignore])

Find all subclass for a class that have a register_plugin method. The register_plugin method must return a hashref containing some info about the plugin, e.g:

    return {
        name    => 'MyPluginName',
        class   => __PACKAGE__,
        aliases => [qw(foo FOO bar BAR)],
    }

This method then returns a hash with information for all these classes. You can then get the list of plug-ins and their aliases by using get_plugins:

    load_plugins();

    sub new {
        my ($class, $wanted_type) = @_;

        my $plugins_ref = get_plugins();
        my $plugin = $plugins_ref->{$wanted_type};

        return $plugin->new();
    }
    

get_plugins($superclass)

Get a hashref with plugin aliases and the class they point to after a load_plugins() call. See documentation for load_plugins for more info.

DIAGNOSTICS

No information available.

CONFIGURATION AND ENVIRONMENT

This module requires no configuration file or environment variables.

DEPENDENCIES

ALTERNATIVES

For the 'Choosing the first available module' problem you might want to look at Best by Gaal Yahas, if all the modules has the same interface.

INCOMPATIBILITIES

None known.

BUGS AND LIMITATIONS

No bugs have been reported.

Please report any bugs or feature requests to bug-modwheel@rt.cpan.org, or through the web interface at http://rt.cpan.org.

AUTHOR

Ask Solem, ask@0x61736b.net.

LICENSE AND COPYRIGHT

Copyright (c), 2007 Ask Solem ask@0x61736b.net.

All rights reserved.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.