—package
Mojo::DynamicMethods;
use
Mojo::Base -strict;
sub
import
{
my
(
$flag
,
$caller
) = (
$_
[1] //
''
,
caller
);
return
unless
$flag
eq
'-dispatch'
;
my
$dyn_pkg
=
"${caller}::_Dynamic"
;
my
$caller_can
=
$caller
->can(
'SUPER::can'
);
monkey_patch
$dyn_pkg
,
'can'
,
sub
{
my
(
$self
,
$method
,
@rest
) =
@_
;
# Delegate to our parent's "can" if there is one, without breaking if not
my
$can
=
$self
->
$caller_can
(
$method
,
@rest
);
return
undef
unless
$can
;
no
warnings
'once'
;
my
$h
=
do
{
no
strict
'refs'
; *{
"${dyn_pkg}::${method}"
}{CODE} };
return
$h
&&
$h
eq
$can
?
undef
:
$can
;
};
{
no
strict
'refs'
;
unshift
@{
"${caller}::ISA"
},
$dyn_pkg
;
}
}
sub
register {
my
(
$target
,
$object
,
$name
,
$code
) =
@_
;
state
%dyn_methods
;
state
$setup
=
do
{ fieldhash
%dyn_methods
; 1 };
my
$dyn_pkg
=
"${target}::_Dynamic"
;
monkey_patch(
$dyn_pkg
,
$name
,
$target
->BUILD_DYNAMIC(
$name
, \
%dyn_methods
))
unless
do
{
no
strict
'refs'
; *{
"${dyn_pkg}::${name}"
}{CODE} };
$dyn_methods
{
$object
}{
$name
} =
$code
;
}
1;
=encoding utf8
=head1 NAME
Mojo::DynamicMethods - Fast dynamic method dispatch
=head1 SYNOPSIS
package MyClass;
use Mojo::Base -base;
use Mojo::DynamicMethods -dispatch;
sub BUILD_DYNAMIC {
my ($class, $method, $dyn_methods) = @_;
return sub {...};
}
sub add_helper {
my ($self, $name, $cb) = @_;
Mojo::DynamicMethods::register 'MyClass', $self, $name, $cb;
}
package main;
# Generate methods dynamically (and hide them from "$obj->can(...)")
my $obj = MyClass->new;
$obj->add_helper(foo => sub { warn 'Hello Helper!' });
$obj->foo;
=head1 DESCRIPTION
L<Mojo::DynamicMethods> provides dynamic method dispatch for per-object helper methods without requiring use of
C<AUTOLOAD>.
To opt your class into dynamic dispatch simply pass the C<-dispatch> flag.
use Mojo::DynamicMethods -dispatch;
And then implement a C<BUILD_DYNAMIC> method in your class, making sure that the key you use to lookup methods in
C<$dyn_methods> is the same thing you pass as C<$ref> to L</"register">.
sub BUILD_DYNAMIC {
my ($class, $method, $dyn_methods) = @_;
return sub {
my ($self, @args) = @_;
my $dynamic = $dyn_methods->{$self}{$method};
return $self->$dynamic(@args) if $dynamic;
my $package = ref $self;
croak qq{Can't locate object method "$method" via package "$package"};
};
}
Note that this module is B<EXPERIMENTAL> and might change without warning!
=head1 FUNCTIONS
L<Mojo::DynamicMethods> implements the following functions.
=head2 register
Mojo::DynamicMethods::register $class, $ref, $name, $cb;
Registers the method C<$name> as eligible for dynamic dispatch for C<$class>, and sets C<$cb> to be looked up for
C<$name> by reference C<$ref> in a dynamic method constructed by C<BUILD_DYNAMIC>.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=cut