File Coverage

blib/lib/Mojo/DynamicMethods.pm
Criterion Covered Total %
statement 46 46 100.0
branch 8 8 100.0
condition 5 5 100.0
subroutine 14 14 100.0
pod 1 1 100.0
total 74 74 100.0


line stmt bran cond sub pod time code
1             package Mojo::DynamicMethods;
2 56     56   1363 use Mojo::Base -strict;
  56         429  
  56         440  
3              
4 56     56   40703 use Hash::Util::FieldHash qw(fieldhash);
  56         72711  
  56         4752  
5 56     56   3560 use Mojo::Util qw(monkey_patch);
  56         3718  
  56         14835  
6              
7             sub import {
8 382   100 382   2816 my ($flag, $caller) = ($_[1] // '', caller);
9 382 100       6755 return unless $flag eq '-dispatch';
10              
11 219         609 my $dyn_pkg = "${caller}::_Dynamic";
12 219         3037 my $caller_can = $caller->can('SUPER::can');
13             monkey_patch $dyn_pkg, 'can', sub {
14 273     273   1152 my ($self, $method, @rest) = @_;
        273      
        271      
        271      
        271      
15              
16             # Delegate to our parent's "can" if there is one, without breaking if not
17 273         1427 my $can = $self->$caller_can($method, @rest);
18 273 100       2044 return undef unless $can;
19 56     56   1549 no warnings 'once';
  56         165  
  56         4462  
20 56     56   349 my $h = do { no strict 'refs'; *{"${dyn_pkg}::${method}"}{CODE} };
  56         382  
  56         8934  
  235         471  
  235         427  
  235         1609  
21 235 100 100     2293 return $h && $h eq $can ? undef : $can;
22 219         2380 };
23              
24             {
25 56     56   387 no strict 'refs';
  56         112  
  56         15193  
  219         689  
26 219         434 unshift @{"${caller}::ISA"}, $dyn_pkg;
  219         13408  
27             }
28             }
29              
30             sub register {
31 19146     19146 1 39086 my ($target, $object, $name, $code) = @_;
32              
33 19146         26666 state %dyn_methods;
34 19146         26529 state $setup = do { fieldhash %dyn_methods; 1 };
  53         336  
  53         939  
35              
36 19146         57813 my $dyn_pkg = "${target}::_Dynamic";
37             monkey_patch($dyn_pkg, $name, $target->BUILD_DYNAMIC($name, \%dyn_methods))
38 56 100   56   427 unless do { no strict 'refs'; *{"${dyn_pkg}::${name}"}{CODE} };
  56         129  
  56         9170  
  19146         36571  
  19146         25671  
  19146         130647  
39 19146         87005 $dyn_methods{$object}{$name} = $code;
40             }
41              
42             "Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn";
43              
44             =encoding utf8
45              
46             =head1 NAME
47              
48             Mojo::DynamicMethods - Fast dynamic method dispatch
49              
50             =head1 SYNOPSIS
51              
52             package MyClass;
53             use Mojo::Base -base, -signatures;
54              
55             use Mojo::DynamicMethods -dispatch;
56              
57             sub BUILD_DYNAMIC ($class, $method, $dyn_methods) {
58             return sub {...};
59             }
60              
61             sub add_helper ($self, $name, $cb) {
62             Mojo::DynamicMethods::register 'MyClass', $self, $name, $cb;
63             }
64              
65             package main;
66              
67             # Generate methods dynamically (and hide them from "$obj->can(...)")
68             my $obj = MyClass->new;
69             $obj->add_helper(foo => sub { warn 'Hello Helper!' });
70             $obj->foo;
71              
72             =head1 DESCRIPTION
73              
74             L provides dynamic method dispatch for per-object helper methods without requiring use of
75             C.
76              
77             To opt your class into dynamic dispatch simply pass the C<-dispatch> flag.
78              
79             use Mojo::DynamicMethods -dispatch;
80              
81             And then implement a C method in your class, making sure that the key you use to lookup methods in
82             C<$dyn_methods> is the same thing you pass as C<$ref> to L.
83              
84             sub BUILD_DYNAMIC ($class, $method, $dyn_methods) {
85             return sub ($self, @args) {
86             my $dynamic = $dyn_methods->{$self}{$method};
87             return $self->$dynamic(@args) if $dynamic;
88             my $package = ref $self;
89             croak qq{Can't locate object method "$method" via package "$package"};
90             };
91             }
92              
93             Note that this module will summon B, use it at your own risk!
94              
95             =head1 FUNCTIONS
96              
97             L implements the following functions.
98              
99             =head2 register
100              
101             Mojo::DynamicMethods::register $class, $ref, $name, $cb;
102              
103             Registers the method C<$name> as eligible for dynamic dispatch for C<$class>, and sets C<$cb> to be looked up for
104             C<$name> by reference C<$ref> in a dynamic method constructed by C.
105              
106             =head1 SEE ALSO
107              
108             L, L, L.
109              
110             =cut