Badger
view release on metacpan or search on metacpan
lib/Badger/Factory.pm view on Meta::CPAN
"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 : { @_ };
@$items{ keys %$args } = values %$args;
}
return $items;
}
sub item {
my $self = shift; $self = $self->prototype unless ref $self;
my ($type, @args) = $self->type_args(@_);
# In most cases we're expecting $type to be a name (e.g. Table) which we
# lookup in the items hash, or tack onto one of the module bases in the
# path (e.g. Template::Plugin) to create a full module name which we load
# and instantiate (e.g. Template::Plugin::Table). However, the name might
# be explicitly mapped to a reference of some kind, or the $type passed
# in could already be a reference (e.g. Template::TT2::Filters allow the
# first argument to be a code ref or object which implements the required
# filtering behaviour). In which case, we bypass any name-based lookup
# and skip straight onto the "look what I found!" phase
return $self->found($type, $type, \@args)
unless textlike $type;
$type = $type . ''; # auto-stringify any textlike objects
# OK, so $type is a string. We'll also create a canonical version of the
# name (lower case dotted) to provide a case/syntax insensitve fallback
# (e.g. so "foo.bar" can match against "Foo.Bar", "Foo::Bar" and so on)
my $items = $self->{ $self->{ items } };
my $canon = dotid $type;
$self->debug("Looking for '$type' or '$canon' in $self->{ items }") if DEBUG;
# $self->debug("types: ", $self->dump_data($self->{ types })) if DEBUG;
# false but defined entry indicates the item is not found
return $self->not_found($type, \@args)
if exists $items->{ $type }
&& not $items->{ $type };
my $item = $items->{ $type }
|| $items->{ $canon }
# TODO: this needs to be defined-or, like //
# Plugins can return an empty string to indicate that they
# do nothing.
# HMMM.... or does it?
|| $self->find($type, \@args)
# || $self->default($type, \@args)
|| return $self->not_found($type, \@args);
$items->{ $type } = $item
unless $self->{ no_cache };
return $self->found($type, $item, \@args);
}
sub type_args {
# Simple method to grok $type and @args from argument list. The only
# processing it does is to set $type to 'default' if it is undefined or
# false. Subclasses can re-define this to insert their own type mapping or
# argument munging, e.g. to inject values into the configuration params
# for an object
shift;
my $type = shift || DEFAULT;
my @args;
if (ref $type eq HASH) {
@args = ($type, @_);
$type = $type->{ type } || DEFAULT;
}
else {
@args = @_;
}
return ($type, @args);
}
sub find {
my $self = shift;
my $type = shift;
my $bases = $self->path;
my $module;
# run the type through the type map to handle any unusual capitalisation,
# spelling, aliases, etc.
$type = $self->{ names }->{ $type } || $type;
foreach my $base (@$bases) {
return $module
if $module = $self->load( $self->module_names($base, $type) );
}
return undef;
}
sub load {
my $self = shift;
my $loaded = $self->{ loaded };
foreach my $module (@_) {
# see if we've previously loaded a module with this name (true
# value) or failed to load a module (defined but false value)
if ($loaded->{ $module }) {
$self->debug("$module has been previously loaded") if DEBUG;
return $module;
}
elsif (defined $loaded->{ $module }) {
next;
lib/Badger/Factory.pm view on Meta::CPAN
sub found {
my ($self, $type, $item, $args) = @_;
if (ref $item) {
# if it's a reference we found then forward it onto the appropriate
# method, e.g found_array(), found_hash(), found_code(). Fall back
# on found_ref()
my $iref = blessed($item)
? OBJECT
: lc ref $item;
$self->debug(
"Looking for handler methods: ",
FOUND,'_'.$iref, "() or ",
FOUND_REF, "()"
) if DEBUG;
my $method
= $self->can(FOUND . '_' . $iref)
|| $self->can(FOUND_REF)
|| return $self->error_msg( bad_ref => $self->{ item }, $type, $iref );
$item = $method->($self, $type, $item, $args);
}
else {
# otherwise it's the name of a module
$item = $self->found_module($type, $item, $args);
}
# NOTE: an item can be defined but false, e.g. a Template::Plugin which
# return '' from its new() method to indicate it does nothing objecty
return unless defined $item;
$self->debug("Found result: $type => $item") if DEBUG;
# TODO: what about caching result? Do we always leave that to subclasses?
return $self->result($type, $item, $args);
}
sub found_module {
# This method is called when a module name is found, either by being
# predefined in the factory entry table, or loaded on demand from disk.
# It ensures the module is loaded and and instantiates an object from the
# class name
my ($self, $type, $module, $args) = @_;
$self->debug("Found module: $type => $module") if DEBUG;
$self->{ loaded }->{ $module } ||= class($module)->load;
return $self->construct($type, $module, $args);
}
sub found_array {
# This method is called when an ARRAY reference is found. We assume that
# the first item is the module name (which needs to be loaded) and the
# second item is the class name (which needs to be instantiated).
my ($self, $type, $item, $args) = @_;
my ($module, $class) = @$item;
$self->{ loaded }->{ $module } ||= class($module)->load;
return $self->construct($type, $class, $args);
}
sub not_found {
my ($self, $type, @args) = @_;
return $type eq DEFAULT
? $self->error_msg( no_default => $self->{ item } )
: $self->error_msg( not_found => $self->{ item }, $type );
}
sub construct {
my ($self, $type, $class, $args) = @_;
$self->debug("constructing class: $type => $class") if DEBUG;
return $class->new(@$args);
}
sub module_names {
my $self = shift;
my @bits =
map { camel_case($_) }
map { split /[\.]+/ } @_;
return (
join( PKG, map { ucfirst $_ } @bits ),
join( PKG, @bits )
);
}
sub can {
my ($self, $name) = @_;
# upgrade class methods to calls on prototype
$self = $self->prototype unless ref $self;
# NOTE: this method can get called before we've called init_factory()
# to define the item/items members, so we tread carefully.
if ($self->{ item } && $self->{ item } eq $name) {
return $self->SUPER::can('item');
}
elsif ($self->{ items } && $self->{ items } eq $name) {
return $self->SUPER::can('items');
}
else {
return $self->SUPER::can($name);
}
}
sub result {
$_[2];
}
sub AUTOLOAD {
my ($self, @args) = @_;
my ($name) = ($AUTOLOAD =~ /([^:]+)$/ );
return if $name eq 'DESTROY';
$self->debug("AUTOLOAD $name") if DEBUG;
local $RUNAWAY = $RUNAWAY;
$self->error("AUTOLOAD went runaway on $name")
if ++$RUNAWAY > 10;
# upgrade class methods to calls on prototype
$self = $self->prototype unless ref $self;
$self->debug("factory item: $self->{ item }") if DEBUG;
lib/Badger/Factory.pm view on Meta::CPAN
=head2 found_module($module)
This method is called when a requested item has been mapped to a module name.
The module is loaded if necessary, then the L<construct()> method is called
to construct an object.
=head2 found_array(\@array)
An entry in the C<items> (aka C<widgets> in our earlier example) table
can be a reference to a list containing a module name and a separate class
name.
my $widgets = My::Widgets->new(
widgets => {
wizbang => ['Wiz::Bang', 'Wiz::Bang::Bash'],
},
);
If the C<wizbang> widget is requested from the C<My::Widgets> factory
in the example above, then the L<found()> method will call C<found_array()>,
passing the array reference as an argument.
The module listed in the first element is loaded. The class name in
the second element is then used to instantiate an object.
=head2 found_hash(\%hash)
This method isn't implemented in the base class, but can be defined by
subclasses to handle the case where a request is mapped to a hash reference.
=head2 found_scalar(\$scalar)
This method isn't implemented in the base class, but can be defined by
subclasses to handle the case where a request is mapped to a scalar reference.
=head2 found_object($object)
This method isn't defined in the base class, but can be defined by subclasses
to handle the case where a request is mapped to an existing object.
=head2 construct($name,$class,\@args)
This method instantiates a C<$class> object using the arguments provided.
In the base class this method simply calls:
$class->new(@$args);
=head2 result($name,$result,\@args)
This method is called at the end of a successful request after an object
has been instantiated (or perhaps re-used from an internal cache). In the
base class it simply returns C<$result> but can be redefined in a subclass
to do something more interesting.
=head2 module_names($type)
This method performs the necessary mapping from a requested module name to
its canonical form.
=head2 not_found($name,@args)
This method is called when the requested item is not found. The method simply
throws an error using the C<not_found> message format. The method can be
redefined in subclasses to perform additional fallback handing.
=head2 can($method)
This method implements the magic to ensure that the item-specific accessor
methods (e.g. C<widget()>/C<widgets()>) are generated on demand.
=head2 AUTOLOAD(@args)
This implements the other bit of magic to generate the item-specific accessor
methods on demand.
=head1 AUTHOR
Andy Wardley L<http://wardley.org/>
=head1 COPYRIGHT
Copyright (C) 2006-2009 Andy Wardley. All Rights Reserved.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=head1 SEE ALSO
L<Badger::Factory::Class>, L<Badger::Codecs>.
=cut
# Local Variables:
# mode: perl
# perl-indent-level: 4
# indent-tabs-mode: nil
# End:
#
# vim: expandtab shiftwidth=4:
( run in 0.684 second using v1.01-cache-2.11-cpan-98e64b0badf )