File Coverage

blib/lib/Dancer2/Plugin.pm
Criterion Covered Total %
statement 254 259 98.0
branch 61 80 76.2
condition 15 22 68.1
subroutine 66 71 92.9
pod 6 24 25.0
total 402 456 88.1


line stmt bran cond sub pod time code
1             # ABSTRACT: base class for Dancer2 plugins
2             $Dancer2::Plugin::VERSION = '0.400000';
3             use strict;
4 35     35   652085 use warnings;
  35         183  
  35         651  
5 35     35   5288  
  35         75  
  35         930  
6             use Moo;
7 35     35   5545 use Carp;
  35         75907  
  35         190  
8 35     35   20301 use List::Util qw/ reduce /;
  35         79  
  35         1324  
9 20     35   115 use Module::Runtime 'require_module';
  35         9068  
  35         1668  
10 35     21   7581 use Attribute::Handlers;
  21         17239  
  21         134  
11 21     21   10398 use Scalar::Util;
  21         71890  
  21         114  
12 21     21   685 use Ref::Util qw<is_arrayref is_coderef>;
  21         44  
  21         951  
13 21     21   5553  
  21         15631  
  21         19509  
14             our $CUR_PLUGIN;
15              
16             extends 'Exporter::Tiny';
17              
18             with 'Dancer2::Core::Role::Hookable';
19              
20             has app => (
21             is => 'ro',
22             weak_ref => 1,
23             required => 1,
24             );
25              
26             has config => (
27             is => 'ro',
28             lazy => 1,
29             default => sub {
30             my $self = shift;
31             my $config = $self->app->config;
32             my $package = ref $self; # TODO
33             $package =~ s/Dancer2::Plugin:://;
34             $config->{plugins}{$package} || {};
35             },
36             );
37              
38             my $_keywords = {};
39              
40 0     0 1 0 my $REF_ADDR_REGEX = qr{
41             [A-Za-z0-9\:\_]+
42             =HASH
43             \(
44             ([0-9a-fx]+)
45             \)
46             }x;
47             my %instances;
48              
49             # backwards compatibility
50             our $_keywords_by_plugin = {};
51              
52             has '+hooks' => (
53             default => sub {
54             my $plugin = shift;
55             my $name = 'plugin.' . lc ref $plugin;
56             $name =~ s/Dancer2::Plugin:://i;
57             $name =~ s/::/_/g;
58              
59             +{
60             map { join( '.', $name, $_ ) => [] }
61             @{ $plugin->ClassHooks }
62             };
63             },
64             );
65              
66             my $class = shift;
67             push @{ $class->ClassHooks }, @_;
68             }
69 7     7 0 25  
70 7         14 my ( $self, $name, @args ) = @_;
  7         126  
71             my $plugin_class = ref $self;
72              
73             $self->isa('Dancer2::Plugin')
74 1     1 0 4 or croak "Cannot call plugin hook ($name) from outside plugin";
75 1         3 $plugin_class =~ s/^Dancer2::Plugin:://; # short names
76              
77 1 50       14 my $full_name = 'plugin.' . lc($plugin_class) . ".$name";
78             $full_name =~ s/::/_/g;
79 1         3  
80             $self->app->execute_hook( $full_name, @args );
81 1         5 }
82 1         5  
83             my ( $self, $name ) = @_;
84 1         20 return $self->app->find_plugin($name);
85             }
86              
87             # both functions are there for D2::Core::Role::Hookable
88 2     2 0 1023 # back-compatibility. Aren't used
89 2         14  
90             ### has() STUFF ########################################
91              
92             # our wrapping around Moo::has, done to be able to intercept
93             # both 'from_config' and 'plugin_keyword'
94 0     0 0 0 my $class = shift;
95 20   100 20 0 279 $class->_p2_has_from_config( $class->_p2_has_keyword( @_ ) );
96             };
97              
98             my( $class, $name, %args ) = @_;
99              
100             my $config_name = delete $args{'from_config'}
101             or return ( $name, %args );
102 23     23   734  
103 23         103 $args{lazy} = 1;
104              
105             if ( is_coderef($config_name) ) {
106             $args{default} ||= $config_name;
107 23     23   66 $config_name = 1;
108             }
109 23 100       140  
110             $config_name = $name if $config_name eq '1';
111             my $orig_default = $args{default} || sub{};
112 8         24 $args{default} = sub {
113             my $plugin = shift;
114 8 100       17 my $value = reduce { eval { $a->{$b} } } $plugin->config, split /\./, $config_name;
115 2   66     8 return defined $value ? $value: $orig_default->($plugin);
116 2         2 };
117              
118             return $name => %args;
119 8 100       26 }
120 8   100 1   34  
121             my( $class, $name, %args ) = @_;
122 8     8   88  
123 8         154 if( my $keyword = delete $args{plugin_keyword} ) {
  9         75  
  9         24  
124 8 100       59  
125 8         27 $keyword = $name if $keyword eq '1';
126              
127 8         34 $class->keywords->{$_} = sub { (shift)->$name(@_) }
128             for ref $keyword ? @$keyword : $keyword;
129             }
130              
131 23     23   70 return $name => %args;
132             }
133 23 100       71  
134             ### ATTRIBUTE HANDLER STUFF ########################################
135 8 100       36  
136             # :PluginKeyword shenanigans
137 6     6   101  
138 8 100       173 my( $class, $sym_ref, $code, undef, $args ) = @_;
139              
140             # importing at BEGIN stage doesn't work with 5.10 :-(
141 23         155 return unless ref $sym_ref;
142              
143             my $func_name = *{$sym_ref}{NAME};
144              
145             $args = join '', @$args if is_arrayref($args);
146              
147             for my $name ( split ' ', $args || $func_name ) {
148             $class->keywords->{$name} = $code;
149 7     6 0 18 }
150              
151             }
152 7 50       206  
153             ## EXPORT STUFF ##############################################################
154 6         8  
  6         21  
155             # this @EXPORT will only be taken
156 6 100       21 # into account when we do a 'use Dancer2::Plugin'
157             # I.e., it'll only do its magic for the
158 6   66     37 # plugins themselves, not when they are
159 7         119 # called
160             our @EXPORT = qw/ :plugin /;
161              
162 21     21   197 # compatibility - it will be removed soon!
  20         37  
  21         629  
163             my $no_dsl = {};
164             my $exported_app = {};
165             my( $class, $name, $args, $global ) = @_;
166              
167             my $caller = $global->{into};
168              
169             $name eq 'no_dsl' and $no_dsl->{$caller} = 1;
170             # no_dsl check here is for compatibility only
171             # it will be removed soon!
172             return _exporter_plugin($caller)
173             if $name eq 'plugin' or $name eq 'no_dsl';
174              
175             return _exporter_app($class,$caller,$global)
176             if $name eq 'app' and $caller->can('app') and !$no_dsl->{$class};
177 64     64   15915  
178             return;
179 64         162  
180             }
181 64 100       236  
182             # plugin has been called within a D2 app. Modify
183             # the app and export keywords
184 64 100 100     375 my( $class, $caller, $global ) = @_;
185              
186             $exported_app->{$caller} = 1;
187              
188 28 50 33     458 # The check for ->dsl->app is to handle plugins as well.
      33        
189             # Otherwise you can only import from a plugin to an app,
190 0         0 # but with this, you can import to anything
191             # that has a DSL with an app, which translates to "also plugins"
192             my $app = eval("${caller}::app()") || eval { $caller->dsl->app } ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
193             or return; ## no critic
194              
195             return unless $app->can('with_plugin');
196              
197 28     28   83 my $plugin = $app->with_plugin( '+' . $class );
198             $global->{'plugin'} = $plugin;
199 28         69  
200             return unless $class->can('keywords');
201              
202             # Add our hooks to the app, so they're recognized
203             # this is for compatibility so you can call execute_hook()
204             # without the fully qualified plugin name.
205 28 50 66     1535 # The reason we need to do this here instead of when adding a
206             # hook is because we need to register in the app, and only now it
207             # exists.
208 28 50       206 # This adds a caveat that two plugins cannot register
209             # the same hook name, but that will be deprecated anyway.
210 28         170 {;
211 28         88 foreach my $hook ( @{ $plugin->ClassHooks } ) {
212             my $full_name = 'plugin.' . lc($class) . ".$hook";
213 28 50       240 $full_name =~ s/Dancer2::Plugin:://i;
214             $full_name =~ s/::/_/g;
215              
216             # this adds it to the plugin
217             $plugin->hook_aliases->{$hook} = $full_name;
218              
219             # this adds it to the app
220             $plugin->app->hook_aliases->{$hook} = $full_name;
221              
222             # copy the hooks from the plugin to the app
223             # this is in case they were created at import time
224 28         51 # rather than after
  28         502  
225 7         43 @{ $plugin->app->hooks }{ keys %{ $plugin->hooks } } =
226 7         30 values %{ $plugin->hooks };
227 7         20 }
228             }
229              
230 7         36 {
231             # get the reference
232             my ($plugin_addr) = "$plugin" =~ $REF_ADDR_REGEX;
233 7         48  
234             $instances{$plugin_addr}{'config'} = sub { $plugin->config };
235             $instances{$plugin_addr}{'app'} = $plugin->app;
236              
237             Scalar::Util::weaken( $instances{$plugin_addr}{'app'} );
238 7         142  
  7         234  
239 7         13 ## no critic
  7         219  
240             no strict 'refs';
241              
242             # we used a forward declaration
243             # so the compiled form "plugin_setting;" can be overridden
244             # with this implementation,
245 28         59 # which works on runtime ("plugin_setting()")
  28         92  
  28         222  
246             # we can't use can() here because the forward declaration will
247 28     5   189 # create a CODE stub
  5         112  
248 28         193 no warnings 'redefine';
249             *{"${class}::plugin_setting"} = sub {
250 28         113 my ($plugin_addr) = "$CUR_PLUGIN" =~ $REF_ADDR_REGEX;
251              
252             $plugin_addr
253 20     20   15518 or Carp::croak('Can\'t find originating plugin');
  20         66  
  20         826  
254              
255             # we need to do this because plugins might call "set"
256             # in order to change plugin configuration but it doesn't
257             # change the plugin object, it changes the app object
258             # so we merge them.
259             my $name = ref $CUR_PLUGIN;
260             $name =~ s/^Dancer2::Plugin:://g;
261 20     20   116  
  20         40  
  20         12924  
262 28         247 my $plugin_inst = $instances{$plugin_addr};
263 5     5   73 my $plugin_config = $plugin_inst->{'config'}->();
264             my $app_plugin_config = $plugin_inst->{'app'}->config->{'plugins'}{$name};
265 5 50       19  
266             return { %{ $plugin_config || {} }, %{ $app_plugin_config || {} } };
267             };
268              
269             # FIXME:
270             # why doesn't this work? it's like it's already defined somewhere
271             # but i'm not sure where. seems like AUTOLOAD runs it.
272 5         11 #$class->can('execute_hook') or
273 5         19 *{"${class}::execute_hook"} = sub {
274             # this can also be called by App.pm itself
275 5         12 # if the plugin is a
276 5         16 # "candidate" for a hook
277 5         93 # See: App.pm "execute_hook" method, "around" modifier
278             if ( $_[0]->isa('Dancer2::Plugin') ) {
279 5 50       37 # this means it's probably our hook, we need to verify it
  5 50       19  
  5         38  
280 28         128 my ( $plugin_self, $hook_name, @args ) = @_;
281              
282             my $plugin_class = lc $class;
283             $plugin_class =~ s/^dancer2::plugin:://;
284             $plugin_class =~ s{::}{_}g;
285              
286 28         175 # you're either calling it with the full qualifier or not
287             # if not, use the execute_plugin_hook instead
288             if ( $plugin->hooks->{"plugin.$plugin_class.$hook_name"} ) {
289             Carp::carp("Please use fully qualified hook name or "
290             . "the method execute_plugin_hook");
291 5 100   5   84 $hook_name = "plugin.$plugin_class.$hook_name";
292             }
293 2         7  
294             $hook_name =~ /^plugin\.$plugin_class/
295 2         5 or Carp::croak('Unknown plugin called through other plugin');
296 2         10  
297 2         6 # now we can't really use the app to execute it because
298             # the "around" modifier is the one calling us to begin
299             # with, so we need to call it directly ourselves
300             # this is okay because the modifier is there only to
301 2 50       27 # call candidates, like us (this is in fact how and
302 0         0 # why we were called)
303             $_->( $plugin_self, @args )
304 0         0 for @{ $plugin->hooks->{$hook_name} };
305              
306             return;
307 2 50       35 }
308              
309             return $plugin->app->execute_hook(@_);
310             };
311             }
312              
313             local $CUR_PLUGIN = $plugin;
314             $_->($plugin) for @{ $plugin->_DANCER2_IMPORT_TIME_SUBS() };
315              
316             map { [ $_ => {plugin => $plugin} ] } keys %{ $plugin->keywords };
317 2         5 }
  2         27  
318              
319 2         10 # turns the caller namespace into
320             # a D2P2 class, with exported keywords
321             my $caller = shift;
322 3         75 require_module('Dancer2::Core::DSL');
323 28         128 my $keywords_list = join ' ', keys %{ Dancer2::Core::DSL->dsl_keywords };
324              
325             eval <<"END"; ## no critic
326 28         72 {
327 28         47 package $caller;
  28         517  
328             use Moo;
329 28         66 use Carp ();
  49         242  
  28         558  
330             use Attribute::Handlers;
331              
332             extends 'Dancer2::Plugin';
333              
334             our \@EXPORT = ( ':app' );
335 36     36   68  
336 36         173 around has => sub {
337 36         755 my( \$orig, \$name, \%args ) = \@_;
  36         203  
338              
339 20 50   20 0 149 if (ref \$name eq 'ARRAY'
  20 50   20 0 37  
  20 100   20 0 94  
  20 50   20 1 7772  
  20 50   20 1 44  
  20     22 1 432  
  20     19 1 97  
  20     25 1 38  
  20     13 0 136  
  20     102 0 4296  
  20     20 0 46  
  20     6 0 125  
  6     9   2415  
  20     6   16878  
  20     8   44  
  20     0   5774  
  36     0   3611  
  22     0   118  
  36         193  
  11         80  
  103         7961  
  23         1961  
  6         35  
  3         9  
  8         823  
  7         457  
  6         30  
  6         64  
  6         122  
  5         10  
  5         9  
  5         5  
  5         37  
  4         4440  
  6         140  
340             && exists \$args{'plugin_keyword'}
341             && ref \$args{'plugin_keyword'} eq 'ARRAY') {
342              
343             Carp::croak('Setting "plugin_keyword" to an array is disallowed'
344             . ' when defining multiple attributes simultaneously');
345             }
346              
347             \$orig->( ${caller}->_p2_has( \$_, \%args) )
348             for ref \$name ? @\$name : \$name;
349             };
350              
351             sub PluginKeyword :ATTR(CODE,BEGIN) {
352             goto &Dancer2::Plugin::PluginKeyword;
353             }
354              
355             sub execute_plugin_hook {
356             goto &Dancer2::Plugin::execute_plugin_hook;
357             }
358              
359             my \$_keywords = {};
360             sub keywords { \$_keywords }
361              
362             my \$_ClassHooks = [];
363             sub ClassHooks { \$_ClassHooks }
364              
365             # this is important as it'll do the keywords mapping between the
366             # plugin and the app
367             sub register_plugin { Dancer2::Plugin::register_plugin(\@_) }
368              
369             sub register {
370             my ( \$keyword, \$sub ) = \@_;
371             \$_keywords->{\$keyword} = \$sub;
372              
373             \$keyword =~ /^[a-zA-Z_]+[a-zA-Z0-9_]*\$/
374             or Carp::croak(
375             "You can't use '\$keyword', it is an invalid name"
376             . " (it should match ^[a-zA-Z_]+[a-zA-Z0-9_]*\\\$ )");
377              
378              
379             grep +( \$keyword eq \$_ ), qw<$keywords_list>
380             and Carp::croak("You can't use '\$keyword', this is a reserved keyword");
381              
382             \$Dancer2::Plugin::_keywords_by_plugin->{\$keyword}
383             and Carp::croak("You can't use \$keyword, "
384             . "this is a keyword reserved by "
385             . \$Dancer2::Plugin::_keywords_by_plugin->{\$keyword});
386              
387             \$Dancer2::Plugin::_keywords_by_plugin->{\$keyword} = "$caller";
388              
389             # Exporter::Tiny doesn't seem to generate the subs
390             # in the caller properly, so we have to do it manually
391             {
392             no strict 'refs';
393             *{"${caller}::\$keyword"} = \$sub;
394             }
395             }
396              
397             my \@_DANCER2_IMPORT_TIME_SUBS;
398             sub _DANCER2_IMPORT_TIME_SUBS {\\\@_DANCER2_IMPORT_TIME_SUBS}
399             sub on_plugin_import (&) {
400             push \@_DANCER2_IMPORT_TIME_SUBS, \$_[0];
401             }
402              
403             sub register_hook { goto &plugin_hooks }
404              
405             sub plugin_setting {};
406              
407             sub plugin_args {
408             Carp::carp "Plugin DSL method 'plugin_args' is deprecated. "
409             . "Use '\\\@_' instead'.\n";
410              
411             \@_;
412             }
413             }
414             END
415              
416             $no_dsl->{$caller} or eval <<"END"; ## no critic
417             {
418             package $caller;
419              
420             # FIXME: AUTOLOAD might pick up on this
421             sub dancer_app {
422             Carp::carp "DEPRECATED: Plugin DSL method 'dancer_app'. "
423             . "Please use '\\\$self->app' instead'.\n";
424              
425             \$_[0]->app;
426             }
427              
428             # FIXME: AUTOLOAD might pick up on this
429             sub request {
430 36 100   5 0 7230 Carp::carp "DEPRECATED: Plugin DSL method 'request'. "
  6     5 0 159  
  6     1 0 507  
  2     1 0 22  
  2         19  
  1         1  
  1         2  
  1         7  
  4         2380  
431             . "Please use '\\\$self->app->request' instead'.\n";
432              
433             \$_[0]->app->request;
434             }
435              
436             # FIXME: AUTOLOAD might pick up on this
437             sub var {
438             Carp::carp "DEPRECATED: Plugin DSL method 'var'. "
439             . "Please use '\\\$self->app->request->var' instead'.\n";
440              
441             shift->app->request->var(\@_);
442             }
443              
444             # FIXME: AUTOLOAD might pick up on this
445             sub hook {
446             Carp::carp "DEPRECATED: Plugin DSL method 'hook'. "
447             . "Please use '\\\$self->app->add_hook' instead'.\n";
448              
449             shift->app->add_hook(
450             Dancer2::Core::Hook->new( name => shift, code => shift ) );
451             }
452              
453             }
454             END
455              
456             die $@ if $@;
457              
458             my $app_dsl_cb = _find_consumer();
459              
460             if ( $app_dsl_cb ) {
461             my $dsl = $app_dsl_cb->();
462              
463             {
464             ## no critic qw(TestingAndDebugging::ProhibitNoWarnings)
465             no strict 'refs';
466             no warnings 'redefine';
467             *{"${caller}::dsl"} = sub {$dsl};
468             }
469             }
470 36 50       149  
471             return map { [ $_ => { class => $caller } ] }
472 36         121 qw/ plugin_keywords plugin_hooks /;
473             }
474 36 100       113  
475 18         59 my $class;
476              
477             ## no critic qw(ControlStructures::ProhibitCStyleForLoops)
478             for ( my $i = 1; my $caller = caller($i); $i++ ) {
479 20     20   144 $class = $caller->can('dsl')
  20         38  
  20         568  
  18         54  
480 20     20   91 and last;
  20         44  
  20         4893  
481 18     6   66 }
  18         78  
  6         2213  
482              
483             # If you use a Dancer2 plugin outside a Dancer App, this fails.
484             # It also breaks a bunch of the tests. -- SX
485 36         85 #$class
  72         400  
486             # or croak('Could not find Dancer2 app');
487              
488             return $class;
489             };
490 50     46   82  
491             # This has to be called for now at the end of every plugin package, in order to
492             # map the keywords of the associated app to the plugin, so that these keywords
493 50         202 # can be called from within the plugin code. This function is deprecated, as
494 290 100       1643 # it's tied to the old plugin system. It's kept here for backcompat reason, but
495             # should go away with the old plugin system.
496              
497             my $plugin_module = caller(1);
498              
499             # if you ask yourself why we do the injection in the plugin
500             # module namespace every time the plugin is used, and not only
501             # once, it's because it can be used by different app that could
502             # have a different DSL with a different list of keywords.
503 50         117  
504             my $_DANCER2_IMPORT_TIME_SUBS = $plugin_module->_DANCER2_IMPORT_TIME_SUBS;
505             unshift(@$_DANCER2_IMPORT_TIME_SUBS, sub {
506             my $app_dsl_cb = _find_consumer();
507              
508             # Here we want to verify that "register_plugin" compat keyword
509             # was in fact only called from an app.
510             $app_dsl_cb
511             or Carp::croak(
512             'I could not find a Dancer App for this plugin');
513 13     9 0 32  
514             my $dsl = $app_dsl_cb->();
515              
516             foreach my $keyword ( keys %{ $dsl->dsl_keywords} ) {
517             # if not yet defined, inject the keyword in the plugin
518             # namespace, but make sure the code will always get the
519             # coderef from the right associated app, because one plugin
520 13         149 # can be used by multiple apps. Note that we remove the
521             # first parameter (plugin instance) from what we pass to
522 14     10   48 # the keyword implementation of the App
523             no strict 'refs';
524             $plugin_module->can($keyword)
525             or *{"${plugin_module}::$keyword"} = sub {
526 12 100       265 my $coderef = shift()->app->name->can($keyword);
527             $coderef->(@_);
528             };
529             }
530 12         33 });
531             }
532 12         25  
  12         47  
533             my( $plugin, $name, $args, $global ) = @_;
534             my $class = $args->{class};
535              
536             return _exported_plugin_keywords($plugin,$class)
537             if $name eq 'plugin_keywords';
538              
539 20     20   133 return _exported_plugin_hooks($class)
  20         43  
  20         5104  
540             if $name eq 'plugin_hooks';
541 668         1964  
542 9     7   193 $exported_app->{ $global->{'into'} }
543 9         32 or Carp::croak('Specific subroutines cannot be exported from plugin');
544 812 100       4572  
545             # otherwise, we're exporting a keyword
546 13         66  
547             my $p = $args->{plugin};
548             my $sub = $p->keywords->{$name};
549             return $name => sub(@) {
550 123     121   7514 # localize the plugin so we can get it later
551 122         385 local $CUR_PLUGIN = $p;
552             $sub->($p,@_);
553 122 100       386 }
554             }
555              
556 86 100       258 # "There's a good reason for this, I swear!"
557             # -- Sawyer X
558             # basically, if someone adds a hook to the app directly
559 50 50       133 # that needs to access a DSL that needs the current object
560             # (such as "plugin_setting"),
561             # that object needs to be available
562             # So:
563             # we override App's "add_hook" to provide a register a
564 50         89 # different hook callback, that closes over the plugin when
565 50         963 # it's available, relocalizes it when the callback runs and
566             # after localizing it, calls the original hook callback
567             {
568 41     40   13182 ## no critic;
569 41         374 no strict 'refs';
570             no warnings 'redefine';
571 50         291 my $orig_cb = Dancer2::Core::App->can('add_hook');
572             $orig_cb and *{'Dancer2::Core::App::add_hook'} = sub {
573             my ( $app, $hook ) = @_;
574              
575             my $hook_code = Scalar::Util::blessed($hook) ? $hook->code : $hook->{code};
576             my $plugin = $CUR_PLUGIN;
577              
578             $hook->{'code'} = sub {
579             local $CUR_PLUGIN = $plugin;
580             $hook_code->(@_);
581             };
582              
583             $orig_cb->(@_);
584             };
585             }
586 20     20   133  
  20         46  
  20         584  
587 20     20   94  
  20         46  
  20         6466  
588             # define the exported 'plugin_keywords'
589             my( $plugin, $class ) = @_;
590 12     12   356  
591             return plugin_keywords => sub(@) {
592 12 100       72 while( my $name = shift @_ ) {
593 12         28 ## no critic
594             my $sub = is_coderef($_[0])
595             ? shift @_
596 18     18   37 : eval '\&'.$class."::" . ( ref $name ? $name->[0] : $name );
597 18         53 $class->keywords->{$_} = $sub for ref $name ? @$name : $name;
598 12         53 }
599             }
600 12         256 }
601              
602             my $class = shift;
603             return plugin_hooks => sub (@) { $class->add_hooks(@_) }
604             }
605              
606             1;
607 36     36   106  
608              
609             =pod
610 12     12   3447  
611             =encoding UTF-8
612 19 50       839  
    100          
613             =head1 NAME
614              
615 19 50       384 Dancer2::Plugin - base class for Dancer2 plugins
616              
617             =head1 VERSION
618 36         284  
619             version 0.400000
620              
621 36     36   74 =head1 SYNOPSIS
622 7     7   925  
623 36         226 The plugin itself:
624              
625             package Dancer2::Plugin::Polite;
626              
627             use strict;
628             use warnings;
629              
630             use Dancer2::Plugin;
631              
632             has smiley => (
633             is => 'ro',
634             default => sub {
635             $_[0]->config->{smiley} || ':-)'
636             }
637             );
638              
639             plugin_keywords 'add_smileys';
640              
641             sub BUILD {
642             my $plugin = shift;
643              
644             $plugin->app->add_hook( Dancer2::Core::Hook->new(
645             name => 'after',
646             code => sub { $_[0]->content( $_[0]->content . " ... please?" ) }
647             ));
648              
649             $plugin->app->add_route(
650             method => 'get',
651             regexp => '/goodbye',
652             code => sub {
653             my $app = shift;
654             'farewell, ' . $app->request->params->{name};
655             },
656             );
657              
658             }
659              
660             sub add_smileys {
661             my( $plugin, $text ) = @_;
662              
663             $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;
664              
665             return $text;
666             }
667              
668             1;
669              
670             then to load into the app:
671              
672             package MyApp;
673              
674             use strict;
675             use warnings;
676              
677             use Dancer2;
678              
679             BEGIN { # would usually be in config.yml
680             set plugins => {
681             Polite => {
682             smiley => '8-D',
683             },
684             };
685             }
686              
687             use Dancer2::Plugin::Polite;
688              
689             get '/' => sub {
690             add_smileys( 'make me a sandwich.' );
691             };
692              
693             1;
694              
695             =head1 DESCRIPTION
696              
697             =head2 Writing the plugin
698              
699             =head3 C<use Dancer2::Plugin>
700              
701             The plugin must begin with
702              
703             use Dancer2::Plugin;
704              
705             which will turn the package into a L<Moo> class that inherits from L<Dancer2::Plugin>. The base class provides the plugin with
706             two attributes: C<app>, which is populated with the Dancer2 app object for which
707             the plugin is being initialized for, and C<config> which holds the plugin
708             section of the application configuration.
709              
710             =head3 Modifying the app at building time
711              
712             If the plugin needs to tinker with the application -- add routes or hooks, for example --
713             it can do so within its C<BUILD()> function.
714              
715             sub BUILD {
716             my $plugin = shift;
717              
718             $plugin->app->add_route( ... );
719             }
720              
721             =head3 Adding keywords
722              
723             =head4 Via C<plugin_keywords>
724              
725             Keywords that the plugin wishes to export to the Dancer2 app can be defined via the C<plugin_keywords> keyword:
726              
727             plugin_keywords qw/
728             add_smileys
729             add_sad_kitten
730             /;
731              
732             Each of the keyword will resolve to the class method of the same name. When invoked as keyword, it'll be passed
733             the plugin object as its first argument.
734              
735             sub add_smileys {
736             my( $plugin, $text ) = @_;
737              
738             return join ' ', $text, $plugin->smiley;
739             }
740              
741             # and then in the app
742              
743             get '/' => sub {
744             add_smileys( "Hi there!" );
745             };
746              
747             You can also pass the functions directly to C<plugin_keywords>.
748              
749             plugin_keywords
750             add_smileys => sub {
751             my( $plugin, $text ) = @_;
752              
753             $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;
754              
755             return $text;
756             },
757             add_sad_kitten => sub { ... };
758              
759             Or a mix of both styles. We're easy that way:
760              
761             plugin_keywords
762             add_smileys => sub {
763             my( $plugin, $text ) = @_;
764              
765             $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;
766              
767             return $text;
768             },
769             'add_sad_kitten';
770              
771             sub add_sad_kitten {
772             ...;
773             }
774              
775             If you want several keywords to be synonyms calling the same
776             function, you can list them in an arrayref. The first
777             function of the list is taken to be the "real" method to
778             link to the keywords.
779              
780             plugin_keywords [qw/ add_smileys add_happy_face /];
781              
782             sub add_smileys { ... }
783              
784             Calls to C<plugin_keywords> are cumulative.
785              
786             =head4 Via the C<:PluginKeyword> function attribute
787              
788             For perl 5.12 and higher, keywords can also be defined by adding the C<:PluginKeyword> attribute
789             to the function you wish to export.
790              
791             For Perl 5.10, the export triggered by the sub attribute comes too late in the
792             game, and the keywords won't be exported in the application namespace.
793              
794             sub foo :PluginKeyword { ... }
795              
796             sub bar :PluginKeyword( baz quux ) { ... }
797              
798             # equivalent to
799              
800             sub foo { ... }
801             sub bar { ... }
802              
803             plugin_keywords 'foo', [ qw/ baz quux / ] => \&bar;
804              
805             =head4 For an attribute
806              
807             You can also turn an attribute of the plugin into a keyword.
808              
809             has foo => (
810             is => 'ro',
811             plugin_keyword => 1, # keyword will be 'foo'
812             );
813              
814             has bar => (
815             is => 'ro',
816             plugin_keyword => 'quux', # keyword will be 'quux'
817             );
818              
819             has baz => (
820             is => 'ro',
821             plugin_keyword => [ 'baz', 'bazz' ], # keywords will be 'baz' and 'bazz'
822             );
823              
824             =head3 Accessing the plugin configuration
825              
826             The plugin configuration is available via the C<config()> method.
827              
828             sub BUILD {
829             my $plugin = shift;
830              
831             if ( $plugin->config->{feeling_polite} ) {
832             $plugin->app->add_hook( Dancer2::Core::Hook->new(
833             name => 'after',
834             code => sub { $_[0]->content( $_[0]->content . " ... please?" ) }
835             ));
836             }
837             }
838              
839             =head3 Getting default values from config file
840              
841             Since initializing a plugin with either a default or a value passed via the configuration file,
842             like
843              
844             has smiley => (
845             is => 'ro',
846             default => sub {
847             $_[0]->config->{smiley} || ':-)'
848             }
849             );
850              
851             C<Dancer2::Plugin> allows for a C<from_config> key in the attribute definition.
852             Its value is the plugin configuration key that will be used to initialize the attribute.
853              
854             If it's given the value C<1>, the name of the attribute will be taken as the configuration key.
855              
856             Nested hash keys can also be referred to using a dot notation.
857              
858             If the plugin configuration has no value for the given key, the attribute default, if specified, will be honored.
859              
860             If the key is given a coderef as value, it's considered to be a C<default> value combo:
861              
862             has foo => (
863             is => 'ro',
864             from_config => sub { 'my default' },
865             );
866              
867              
868             # equivalent to
869             has foo => (
870             is => 'ro',
871             from_config => 'foo',
872             default => sub { 'my default' },
873             );
874              
875             For example:
876              
877             # in config.yml
878              
879             plugins:
880             Polite:
881             smiley: ':-)'
882             greeting:
883             casual: Hi!
884             formal: How do you do?
885              
886              
887             # in the plugin
888              
889             has smiley => ( # will be ':-)'
890             is => 'ro',
891             from_config => 1,
892             default => sub { ':-(' },
893             );
894              
895             has casual_greeting => ( # will be 'Hi!'
896             is => 'ro',
897             from_config => 'greeting.casual',
898             );
899              
900             has apology => ( # will be 'sorry'
901             is => 'ro',
902             from_config => 'apology',
903             default => sub { 'sorry' },
904             )
905              
906             has closing => ( # will be 'See ya!'
907             is => 'ro',
908             from_config => sub { 'See ya!' },
909             );
910              
911             =head3 Config becomes immutable
912              
913             The plugin's C<config> attribute is loaded lazily on the first call to
914             C<config>. After this first call C<config> becomes immutable so you cannot
915             do the following in a test:
916              
917             use Dancer2;
918             use Dancer2::Plugin::FooBar;
919              
920             set plugins => {
921             FooBar => {
922             wibble => 1, # this is OK
923             },
924             };
925              
926             flibble(45); # plugin keyword called which causes config read
927            
928             set plugins => {
929             FooBar => {
930             wibble => 0, # this will NOT change plugin config
931             },
932             };
933              
934             =head3 Accessing the parent Dancer app
935              
936             If the plugin is instantiated within a Dancer app, it'll be
937             accessible via the method C<app()>.
938              
939             sub BUILD {
940             my $plugin = shift;
941              
942             $plugin->app->add_route( ... );
943             }
944              
945             To use Dancer's DSL in your plugin:
946              
947             $self->dsl->debug( “Hi! I’m logging from your plugin!” );
948              
949             See L<Dancer2::Manual/"DSL KEYWORDS"> for a full list of Dancer2 DSL.
950              
951             =head2 Using the plugin within the app
952              
953             A plugin is loaded via
954              
955             use Dancer2::Plugin::Polite;
956              
957             The plugin will assume that it's loading within a Dancer module and will
958             automatically register itself against its C<app()> and export its keywords
959             to the local namespace. If you don't want this to happen, specify that you
960             don't want anything imported via empty parentheses when C<use>ing the module:
961              
962             use Dancer2::Plugin::Polite ();
963              
964             =head2 Plugins using plugins
965              
966             It's easy to use plugins from within a plugin:
967              
968             package Dancer2::Plugin::SourPuss;
969            
970             use Dancer2::Plugin;
971             use Dancer2::Plugin::Polite;
972            
973             sub my_keyword { my $smiley = smiley(); }
974              
975             1;
976              
977             This does not export C<smiley()> into your application - it is only available
978             from within your plugin. However, from the example above, you can wrap
979             DSL from other plugins and make it available from your plugin.
980              
981             =head2 Utilizing other plugins
982              
983             You can use the C<find_plugin> to locate other plugins loaded by the user,
984             in order to use them, or their information, directly:
985              
986             # MyApp.pm
987             use Dancer2;
988             use Dancer2::Plugin::Foo;
989             use Dancer2::Plugin::Bar;
990              
991             # Dancer2::Plugin::Bar;
992             ...
993              
994             sub my_keyword {
995             my $self = shift;
996             my $foo = $self->find_plugin('Dancer2::Plugin::Foo')
997             or $self->dsl->send_error('Could not find Foo');
998              
999             return $foo->foo_keyword(...);
1000             }
1001              
1002             =head2 Hooks
1003              
1004             New plugin hooks are declared via C<plugin_hooks>.
1005              
1006             plugin_hooks 'my_hook', 'my_other_hook';
1007              
1008             Hooks are prefixed with C<plugin.plugin_name>. So the plugin
1009             C<my_hook> coming from the plugin C<Dancer2::Plugin::MyPlugin> will have the hook name
1010             C<plugin.myplugin.my_hook>.
1011              
1012             Hooks are executed within the plugin by calling them via the associated I<app>.
1013              
1014             $plugin->execute_plugin_hook( 'my_hook' );
1015              
1016             You can also call any other hook if you provide the full name using the
1017             C<execute_hook> method:
1018              
1019             $plugin->app->execute_hook( 'core.app.route_exception' );
1020              
1021             Or using their alias:
1022              
1023             $plugin->app->execute_hook( 'on_route_exception' );
1024              
1025             B<Note:> If your plugin consumes a plugin that declares any hooks, those hooks
1026             are added to your application, even though DSL is not.
1027              
1028             =head2 Writing Test Gotchas
1029              
1030             =head3 Constructor for Dancer2::Plugin::Foo has been inlined and cannot be updated
1031              
1032             You'll usually get this one because you are defining both the plugin and app
1033             in your test file, and the runtime creation of Moo's attributes happens after
1034             the compile-time import voodoo dance.
1035              
1036             To get around this nightmare, wrap your plugin definition in a C<BEGIN> block.
1037              
1038             BEGIN {
1039             package Dancer2::Plugin::Foo;
1040              
1041             use Dancer2::Plugin;
1042              
1043             has bar => (
1044             is => 'ro',
1045             from_config => 1,
1046             );
1047              
1048             plugin_keywords qw/ bar /;
1049              
1050             }
1051              
1052             {
1053             package MyApp;
1054              
1055             use Dancer2;
1056             use Dancer2::Plugin::Foo;
1057              
1058             bar();
1059             }
1060              
1061             =head3 You cannot overwrite a locally defined method (bar) with a reader
1062              
1063             If you set an object attribute of your plugin to be a keyword as well, you need
1064             to call C<plugin_keywords> after the attribute definition.
1065              
1066             package Dancer2::Plugin::Foo;
1067              
1068             use Dancer2::Plugin;
1069              
1070             has bar => (
1071             is => 'ro',
1072             );
1073              
1074             plugin_keywords 'bar';
1075              
1076             =head1 AUTHOR
1077              
1078             Dancer Core Developers
1079              
1080             =head1 COPYRIGHT AND LICENSE
1081              
1082             This software is copyright (c) 2022 by Alexis Sukrieh.
1083              
1084             This is free software; you can redistribute it and/or modify it under
1085             the same terms as the Perl 5 programming language system itself.
1086              
1087             =cut