use Mojo::Base -strict;
use Hash::Util::FieldHash qw(fieldhash);
use Mojo::Util qw(monkey_patch);
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