Badger

 view release on metacpan or  search on metacpan

lib/Badger/Factory.pm  view on Meta::CPAN

#
# AUTHOR
#   Andy Wardley   <abw@wardley.org>
#
#========================================================================

package Badger::Factory;

use Badger::Debug ':dump';
use Badger::Class
    version   => 0.01,
    debug     => 0,
    base      => 'Badger::Prototype Badger::Exporter',
    import    => 'class',
    utils     => 'plural blessed textlike dotid camel_case',
    words     => 'ITEM ITEMS ISA',
    constants => 'PKG ARRAY HASH REFS ONCE DEFAULT LOADED',
    constant  => {
        OBJECT         => 'object',
        FOUND          => 'found',
        FOUND_REF      => 'found_ref',
        PATH_SUFFIX    => '_PATH',
        NAMES_SUFFIX   => '_NAMES',
        DEFAULT_SUFFIX => '_DEFAULT',
    },
    messages  => {
        no_item    => 'No item(s) specified for factory to manage',
        no_default => 'No default defined for %s factory',
        bad_ref    => 'Invalid reference for %s factory item %s: %s',
        bad_method => qq{Can't locate object method "%s" via package "%s" at %s line %s},
    };

our $RUNAWAY = 0;
our $AUTOLOAD;

*init = \&init_factory;


sub init_factory {
    my ($self, $config) = @_;
    my $class = $self->class;
    my ($item, $items, $path, $map, $default);

    # 'item' and 'items' can be specified as config params or we look for
    # $ITEM and $ITEMS variables in the current package or those of any
    # base classes.  NOTE: $ITEM and $ITEMS must be in the same package
    unless ($item = $config->{ item }) {
        foreach my $pkg ($class->heritage) {
            no strict   REFS;
            no warnings ONCE;

            if (defined ($item = ${ $pkg.PKG.ITEM })) {
                $items = ${ $pkg.PKG.ITEMS };
                last;
            }
        }
    }
    return $self->error_msg('no_item')
        unless $item;

    # use 'items' in config, or grokked from $ITEMS, or guess plural
    $items = $config->{ items } || $items || plural($item);

    my $ipath    = $item.PATH_SUFFIX;
    my $inames   = $item.NAMES_SUFFIX;
    my $idefault = $item.DEFAULT_SUFFIX;

    # Merge all XXXX_PATH package vars with any 'xxxx_path' or 'path' config
    # items.  Ditto for XXXX_NAME / 'xxxx_name' / 'aka' and  XXXXS/ 'xxxxs'

    my @path  = @$config{ path  => lc $ipath  };
    my @names = @$config{ names => lc $inames };

    $self->{ path     } = $class->list_vars(uc $ipath, @path);
    $self->{ names    } = $class->hash_vars(uc $inames, @names);
    $self->{ $items   } = $class->hash_vars(uc $items, $config->{ $items });
    $self->{ items    } = $items;
    $self->{ item     } = $item;
    $self->{ loaded   } = { };
    $self->{ no_cache } = defined $config->{ no_cache }     # quick hack
        ? $config->{ no_cache }
        : $class->any_var('NO_CACHE') || 0;

    # see if a 'xxxx_default' or 'default' configuration option is specified
    # or look for the first XXXX_DEFAULT or DEFAULT package variable.
    $default = $config->{ $idefault }
            || $config->{ default }
            || $class->any_var_in( uc $idefault, uc DEFAULT );
    if ($default) {
        $self->debug("Setting default to $default") if DEBUG;
        $self->{ default } = $self->{ names }->{ default } = $default;
    }

    $self->debug(
        "Initialised $item/$items factory\n",
        " Path: ", $self->dump_data($self->{ path }), "\n",
        "Names: ", $self->dump_data($self->{ names })
    ) if DEBUG;

    return $self;
}

sub path {
    my $self = shift->prototype;
    return @_
        ? ($self->{ path } = ref $_[0] eq ARRAY ? shift : [ @_ ])
        :  $self->{ path };
}

sub default {
    my $self = shift->prototype;
    return @_
        ? ($self->{ default } = $self->{ names }->{ default } = shift)
        :  $self->{ default };
}

sub items {
    my $self  = shift->prototype;
    my $items = $self->{ $self->{ items } };
    if (@_) {
        my $args = ref $_[0] eq HASH ? shift : { @_ };

lib/Badger/Factory.pm  view on Meta::CPAN

    my $factory = My::Widgets->new(
        widget_path => ['His::Widget', 'Her::Widget'],
        widgets     => {
            extra => 'Another::Widget::Module',
            super => 'Golly::Gosh',
        }
    );

    $w = $factory->widget( foo => { msg => 'Hello World' } );

The L<Badger::Factory::Class> module can be used to simplify the process
of defining factory subclasses.

    package My::Widgets;

    use Badger::Factory::Class
        item    => 'widget',
        path    => 'My::Widget Your::Widget';
        widgets => {
            extra => 'Another::Widget::Module',
            super => 'Golly::Gosh',
        };

=head1 DESCRIPTION

This module implements a base class factory object for loading modules and
instantiating objects on demand. It originated in the L<Template::Plugins>
module, evolved over time in various directions for other projects, and was
eventually pulled back into line to become C<Badger::Factory>.

=head2 Defining a Factory Module

The C<Badger::Factory> module isn't designed to be used by itself. Rather it
should be used as a base class for your own factory modules. For example,
suppose you have a project which has lots of C<My::Widget::*> modules. You can
define a factory for them like so:

    package My::Widgets;
    use base 'Badger::Factory';

    our $ITEM           = 'widget';
    our $ITEMS          = 'widgets';
    our $WIDGET_PATH    = ['My::Widget', 'Your::Widget'];
    our $WIDGET_DEFAULT = 'foo';
    our $WIDGET_NAMES   = {
        html => 'HTML',
    };

    # lookup table for any non-standard spellings/capitalisations/paths
    our $WIDGETS     = {
        url   => 'My::Widget::URL',       # non-standard capitalisation
        color => 'My::Widget::Colour',    # different spelling
        amp   => 'Nigels::Amplifier',     # different path
    };

    1;

The C<$ITEM> and C<$ITEMS> package variables are used to define the
singular and plural names of the items that the factory is responsible for.
In this particular case, the C<$ITEMS> declaration isn't strictly
necessary because the module would correctly "guess" the plural name
C<widgets> from the singular C<widget> defined in C<$ITEM>.  However,
this is only provided as a convenience for those English words that
pluralise regularly and shouldn't be relied upon to work all the time.
See the L<pluralise()|Badger::Utils/pluralise()> method in L<Badger::Utils>
for further information, and explicitly specify the plural in C<$ITEMS> if
you're in any doubt.

The C<$WIDGET_PATH> is used to define one or more base module names under
which your widgets are located.  The name of this variable is derived
from the upper case item name in C<$ITEM> with C<_PATH> appended.  In this
example, the factory will look for the C<Foo::Bar> module as either
C<My::Widget::Foo::Bar> or C<Your::Widget::Foo::Bar>.

The C<$WIDGET_DEFAULT> specifies the default item name to use if a request
is made for a module using an undefined or false name.  If you don't specify
any value for a default then it uses the literal string C<default>.  Adding
a C<default> entry to your C<$WIDGET_NAMES> or C<$WIDGETS> will have the same
effect.

The C<$WIDGET_NAMES> is used to define any additional name mappings. This is
usually required to handle alternate spellings or unusual capitalisations that
the default name mapping algorithm would get wrong. For example, a request for
an C<html> widget would look for C<My::Widget::Html> or C<Your::Widget::Html>.
Adding a C<$WIDGET_MAP> entry mapping C<html> to C<HTML> will instead send it
looking for C<My::Widget::HTML> or C<Your::Widget::HTML>.

If you've got any widgets that aren't located in one of these locations,
or if you want to provide some aliases to particular widgets then you can
define them in the C<$WIDGETS> package variable.  The name of this variable
is the upper case conversion of the value defined in the C<$ITEMS> package
variable.

=head2 Using Your Factory Module

Now that you've define a factory module you can use it like this.

    use My::Widgets;

    my $widgets = My::Widgets->new;
    my $foo_bar = $widgets->widget('Foo::Bar');

The C<widget()> method is provided to load a widget module and instantiate
a widget object.

The above example is equivalent to:

    use My::Widget::Foo::Bar;
    my $foo_bar = My::Widget::Foo::Bar->new;

Although it's not I<strictly> equivalent because the factory could just
has easily have loaded it from C<Your::Widget::Foo::Bar> in the case that
C<My::Widget::Foo::Bar> doesn't exist.

You can specify additional arguments that will be forwarded to the object
constructor method.

    my $foo_bar = $widgets->widget('Foo::Bar', x => 10, y => 20);

If you've specified a C<$WIDGET_DEFAULT> for your factory then you can call
the L<widget()> method without any arguments to get the default object.



( run in 2.764 seconds using v1.01-cache-2.11-cpan-75ffa21a3d4 )